Symfony – In Plain English

Symfony – In Plain English

For every newcomer, Symfony appears to be an invincible beast at first glance. But taking a look from the distance reveals some patterns that are quite easy to grasp. We'll take a look at Symfony's concepts and philosophy, its components, the full-stack framework and the ecosystem. In a nutshell, this talk teaches you on a beginner's level what all the Symfony fuss is about and why it became *the* top-notch PHP project it is nowadays.

http://dcmuc16.drupalcamp.de/sessions/symfony-plain-english

2ac1fe2befdfefc42caca2d5c1db35e1?s=128

Philipp Rieber

December 03, 2016
Tweet

Transcript

  1. @bicpi Symfony – In Plain English Philipp Rieber #DCMuc16

  2. #DCMuc16

  3. #DCMuc16

  4. @bicpi PHP・Symfony・DevOps・Technical Writer https://philipp-rieber.net #DCMuc16

  5. @bicpi PHP・Symfony・DevOps・Technical Writer https://philipp-rieber.net #DCMuc16

  6. ottonova.de We are creating a better private health insurance company

    for you. #DCMuc16
  7. @sfugmuc Symfony User Group Munich #DCMuc16

  8. First, Symfony2 is a reusable set of standalone, decoupled, and

    cohesive PHP components that solve common web development problems. – Fabien Potencier ‘ http://fabien.potencier.org/what-is-symfony2.html What is Symfony? #DCMuc16
  9. BrowserKit ClassLoader Config Asset Console CssSelector Debug DependencyInjection DomCrawler EventDispatcher

    ExpressionLanguage Filesystem Finder Form Guard HttpFoundation HttpKernel Icu Intl Ldap Locale OptionsResolver Process PropertyAccess PropertyInfo Routing Security Serializer Stopwatch Templating Translation Validator VarDumper Yaml Guard Workflow #DCMuc16
  10. BrowserKit ClassLoader Config Asset Console CssSelector Debug DependencyInjection DomCrawler EventDispatcher

    ExpressionLanguage Filesystem Finder Form Guard HttpFoundation HttpKernel Icu Intl Ldap Locale OptionsResolver Process PropertyAccess PropertyInfo Routing Security Serializer Stopwatch Templating Translation Validator VarDumper Yaml Guard Workflow #DCMuc16
  11. $ composer require symfony/yaml Yaml Component #DCMuc16

  12. $ composer require symfony/yaml require 'vendor/autoload.php'; Yaml Component #DCMuc16

  13. $ composer require symfony/yaml require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; Yaml Component

    #DCMuc16
  14. $ composer require symfony/yaml require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; $yaml =

    <<<YAML parameters: user: symfony pass: awesome YAML; Yaml Component #DCMuc16
  15. $ composer require symfony/yaml require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; $yaml =

    <<<YAML parameters: user: symfony pass: awesome YAML; $config = Yaml::parse($yaml); Yaml Component #DCMuc16
  16. $ composer require symfony/yaml require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; $yaml =

    <<<YAML parameters: user: symfony pass: awesome YAML; $config = Yaml::parse($yaml); // ['parameters' => ['user' => 'symfony', 'pass' => 'awesome']] Yaml Component #DCMuc16
  17. BrowserKit ClassLoader Config Asset Console CssSelector Debug DependencyInjection DomCrawler EventDispatcher

    ExpressionLanguage Filesystem Finder Form Guard HttpFoundation HttpKernel Icu Intl Ldap Locale OptionsResolver Process PropertyAccess PropertyInfo Routing Security Serializer Stopwatch Templating Translation Validator VarDumper Yaml Guard Workflow #DCMuc16
  18. $ composer require symfony/finder Finder Component #DCMuc16

  19. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    #DCMuc16
  20. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create(); #DCMuc16
  21. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create() ->in('/Users/bicpi/photos');
 #DCMuc16
  22. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create() ->in('/Users/bicpi/photos') ->files();
 #DCMuc16
  23. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create() ->in('/Users/bicpi/photos') ->files() ->name('*.jpg');
 #DCMuc16
  24. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create() ->in('/Users/bicpi/photos') ->files() ->name('*.jpg') ->size('>= 1M'); #DCMuc16
  25. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create() ->in('/Users/bicpi/photos') ->files() ->name('*.jpg') ->size('>= 1M'); foreach ($finder as $file) { echo $file->getFilename() . "\n"; } #DCMuc16
  26. Do one thing. 
 Make it excellent. 
 Stay out

    of the way. – Unix Philosophy ‘ #DCMuc16
  27. • … • Console Command Line Interface • Filesystem Basic

    utilities for the filesystem • Translation Tools for internationalisation • … #DCMuc16
  28. Then, based on these components, Symfony2 is also a full-stack

    web framework. – Fabien Potencier ‘ http://fabien.potencier.org/what-is-symfony2.html What ELSE is Symfony? #DCMuc16
  29. BrowserKit ClassLoader Config Asset Console CssSelector Debug DependencyInjection DomCrawler EventDispatcher

    ExpressionLanguage Filesystem Finder Form Guard HttpFoundation HttpKernel Icu Intl Ldap Locale OptionsResolver Process PropertyAccess PropertyInfo Routing Security Serializer Stopwatch Templating Translation Validator VarDumper Yaml Guard Workflow Your Application #DCMuc16
  30. ☺ #DCMuc16

  31. Request ☺ 1 #DCMuc16

  32. Request ☺ Response 2 1 #DCMuc16 ?

  33. Request ☺ Response 2 3 #DCMuc16 1 ?

  34. Request ☺ Symfony full-stack Response #DCMuc16 1 2 3

  35. It is all about transforming a Request into a Response

    #DCMuc16
  36. HttpFoundation\Request
 HTTP Header, $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER
 #DCMuc16

  37. HttpFoundation\Request
 HTTP Header, $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER
 HttpKernel
 Web

    framework core, “Glues” the components,
 Loads configuration and “Bundles”, Controls flow, Event Driven #DCMuc16
  38. HttpFoundation\Request
 HTTP Header, $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER
 HttpFoundation\Response
 HTTP

    Status Code, HTTP Header, Content (HTML, JSON …) HttpKernel
 Web framework core, “Glues” the components,
 Loads configuration and “Bundles”, Controls flow, Event Driven #DCMuc16
  39. HTTP specification #DCMuc16

  40. #DCMuc16 Code please.

  41. Request
 /blog/article?id=34 #DCMuc16

  42. Request
 /blog/article?id=34 class BlogController extends Controller { 
 
 


    
 
 
 
 } #DCMuc16
  43. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { 
 
 
 
 
 
 
 } } #DCMuc16
  44. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { 
 
 
 
 
 
 
 } } #DCMuc16
  45. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); 
 
 
 
 
 
 } } #DCMuc16
  46. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); $article = $database->findArticle($id); 
 
 
 
 
 } } #DCMuc16
  47. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); $article = $database->findArticle($id); if (!$article) { return new Response('Article not found.', 404); } 
 
 
 } } #DCMuc16
  48. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); $article = $database->findArticle($id); if (!$article) { return new Response('Article not found.', 404); } 
 
 
 } } #DCMuc16 Response 404
 <html>Article not found</html>
  49. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); $article = $database->findArticle($id); if (!$article) { return new Response('Article not found.', 404); } $html = $view->renderArticle($article); } } #DCMuc16
  50. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); $article = $database->findArticle($id); if (!$article) { return new Response('Article not found.', 404); } $html = $view->renderArticle($article); return new Response($html, 200); } } #DCMuc16 Response 200
 <html>Article 34</html>
  51. During the Request ➾ Response flow,
 many more components will

    assist #DCMuc16
  52. • Routing Match URL to controller action • Templating Render

    templates • Security Decide about access • Config Load configuration • Forms Create HTML forms • … #DCMuc16
  53. Who is going to configure all this?

  54. Symfony Configuration #DCMuc16

  55. Symfony Configuration dev prod test #DCMuc16

  56. Symfony Configuration dev /app_dev.php/articles /app.php/articles prod test UseCaseTest #DCMuc16

  57. Front Controller Symfony Configuration dev /app_dev.php/articles /app.php/articles prod test UseCaseTest

    #DCMuc16
  58. Symfony Configuration dev /app_dev.php/articles /app.php/articles prod test UseCaseTest Test class

    #DCMuc16
  59. Symfony Configuration dev /app_dev.php/articles /app.php/articles new AppKernel('prod', false); new AppKernel('dev',

    true); new AppKernel('test', false); prod test UseCaseTest #DCMuc16
  60. Symfony Configuration dev /app_dev.php/articles /app.php/articles new AppKernel('prod', false); new AppKernel('dev',

    true); new AppKernel('test', false); config_prod.yml config_test.yml config_dev.yml prod test UseCaseTest #DCMuc16
  61. Symfony Configuration dev /app_dev.php/articles /app.php/articles new AppKernel('prod', false); new AppKernel('dev',

    true); new AppKernel('test', false); config_prod.yml config_test.yml config_dev.yml prod test config.yml UseCaseTest #DCMuc16
  62. database:
 driver: pdo_mysql
 charset: utf8
 
 
 
 # …

    config.yml #DCMuc16
  63. 
 database:
 driver: pdo_mysql
 charset: utf8
 user: '%db_user%' password: '%db_password%'


    
 # … config.yml #DCMuc16
  64. imports: - { resource: parameters.yml } database:
 driver: pdo_mysql
 charset:

    utf8
 user: '%db_user%' password: '%db_password%'
 
 # … config.yml #DCMuc16
  65. imports: - { resource: parameters.yml } database:
 driver: pdo_mysql
 charset:

    utf8
 user: '%db_user%' password: '%db_password%'
 
 # … config.yml parameters.yml parameters:
 
 
 
 
 
 #DCMuc16
  66. imports: - { resource: parameters.yml } database:
 driver: pdo_mysql
 charset:

    utf8
 user: '%db_user%' password: '%db_password%'
 
 # … config.yml parameters.yml parameters:
 
 
 
 
 
 added to .gitgnore #DCMuc16
  67. imports: - { resource: parameters.yml } database:
 driver: pdo_mysql
 charset:

    utf8
 user: '%db_user%' password: '%db_password%'
 
 # … config.yml parameters.yml parameters:
 
 
 
 
 
 db_user: drupal db_password: ThisIsSuperS3cr3t
 
 # … added to .gitgnore #DCMuc16
  68. Dependency Injection #DCMuc16

  69. Logger Beispiel /** * @Route("/weather/at/{zip}") */ public function weatherAction(string $zip)

    { } #DCMuc16
  70. Logger Beispiel /** * @Route("/weather/at/{zip}") */ public function weatherAction(string $zip)

    { // ...
 // ... Do heavy stuff to calculate $weatherData from $zip ... // ...
 $theWeather = $this->render('weather.html.twig', $weatherData); return new Response($theWeather, 200)); } #DCMuc16 Lots of stuff happens here to prepare the weather data
  71. Logger Beispiel Let’s extract this to an independent service class!

    class WeatherForecast { public function forZip(string $zip): array { // ... // ... Do heavy stuff to calculate $weatherData from $zip ...
 // ... return $weatherData; } } #DCMuc16
  72. Logger Beispiel class WeatherForecast { public function forZip(string $zip): array

    { // ... // ... Do heavy stuff to calculate $weatherData from $zip ...
 // ... return $weatherData; } } #DCMuc16 It only does ONE thing!
  73. Logger Beispiel /** * @Route("/weather/at/{zip}") */ public function weatherAction(string $zip)

    { $weatherForecast = new WeatherForecast(); $weatherData = $weatherForecast->forZip($zip); $theWeather = $this->render('weather.html.twig', $weatherData); return new Response($theWeather, 200)); } Then use the service from the controller! #DCMuc16
  74. Logger Beispiel class WeatherForecast { public function forZip(string $zip): array

    { $logger = new FileLogger('/var/log/weather.log'); // ... // ... Things might go wrong ...
 $logger->notice('No forecast found for ZIP '.$zip);
 // ... Things might work
 $logger->info('Forecast provided for ZIP '.$zip); // ... return $weatherData; } } But now imagine a dependency to a Logger service #DCMuc16
  75. Logger Beispiel class WeatherForecast { public function forZip(string $zip): array

    { $logger = new FileLogger('/var/log/weather.log'); // ... // ... Things might go wrong ...
 $logger->notice('No forecast found for ZIP '.$zip);
 // ... Things might work
 $logger->info('Forecast provided for ZIP '.$zip); // ... return $weatherData; } } But now imagine a dependency to a Logger service #DCMuc16 • How to test this code? • How to switch the log file? • How to use a different logger?
  76. Logger Beispiel class WeatherForecast
 { private $logger; public function __construct($logger)


    { $this->logger = $logger; } public function forZip(string $zip): array 
 { // ... $this->logger->info('Forecast provided for ZIP '.$zip); // ... } } Why not inject the logger dependency? #DCMuc16
  77. Logger Beispiel class WeatherForecast
 { private $logger; public function __construct(Psr\Log\LoggerInterface

    $logger)
 { $this->logger = $logger; } public function forZip(string $zip): array 
 { // ... $this->logger->info('Forecast provided for ZIP '.$zip); // ... } } And type hint it against an interface contract? #DCMuc16
  78. Logger Beispiel class FileLogger implements Psr\Log\LoggerInterface
 {
 
 private $logFilePath;

    public function __construct(string $logFilePath)
 { $this->logFilePath = $logFilePath; } public function notice($message) { … }
 public function info($message) { … }
 
 … } #DCMuc16
  79. Logger Beispiel /** * @Route("/weather/at/{zip}") */ public function weatherAction(string $zip)

    { $logger = new FileLogger('/var/log/weather.log');
 $weatherForecast = new WeatherForecast($logger);
 // ... } Inject the logger dependency into the constructor #DCMuc16
  80. #DCMuc16 Service Container

  81. services:
 config.yml #DCMuc16 Now shift the service creation to a

    service container
  82. services:
 logger:
 class: FileLogger
 
 
 
 config.yml #DCMuc16

  83. services:
 logger:
 class: FileLogger
 arguments:
 - '/var/log/weather.log' 
 
 


    config.yml #DCMuc16
  84. services:
 logger:
 class: FileLogger
 arguments:
 - '/var/log/weather.log' weather_forecast:
 class: WeatherForecast


    config.yml #DCMuc16
  85. services:
 logger:
 class: FileLogger
 arguments:
 - '/var/log/weather.log' weather_forecast:
 class: WeatherForecast


    arguments:
 - '@logger' config.yml #DCMuc16
  86. /** * @Route("/weather/at/{zip}") */ public function weatherAction(string $zip) { $weatherForecast

    = $this->container->get('...'); $weatherData = $weatherForecast->forZip($zip); $theWeather = $this->render('weather.html.twig', $weatherData); return new Response($theWeather, 200)); } #DCMuc16
  87. /** * @Route("/weather/at/{zip}") */ public function weatherAction(string $zip) { $weatherForecast

    = $this->container->get('weather_forecast'); $weatherData = $weatherForecast->forZip($zip); $theWeather = $this->render('weather.html.twig', $weatherData); return new Response($theWeather, 200)); } #DCMuc16
  88. config.yml #DCMuc16 services:
 logger:
 class: NullLogger weather_forecast:
 class: WeatherForecast
 arguments:


    - '@logger'
 

  89. config.yml #DCMuc16 services:
 logger:
 class: DatabaseLogger weather_forecast:
 class: WeatherForecast
 arguments:


    - '@logger'
 

  90. #DCMuc16 Console

  91. File upload script with some flags, options and arguments #DCMuc16

    $ ./upload-files --overwrite cat-1.jpg cat-2.jpg cat-3.jpg 

  92. File upload script with some flags, options and arguments #DCMuc16

    $ ./upload-files --overwrite cat-1.jpg cat-2.jpg cat-3.jpg 
 Uploading 'cat-1.jpg' ...

  93. File upload script with some flags, options and arguments #DCMuc16

    $ ./upload-files --overwrite cat-1.jpg cat-2.jpg cat-3.jpg 
 Uploading 'cat-1.jpg' ...
 Uploading 'cat-2.jpg' ...

  94. File upload script with some flags, options and arguments #DCMuc16

    $ ./upload-files --overwrite cat-1.jpg cat-2.jpg cat-3.jpg 
 Uploading 'cat-1.jpg' ...
 Uploading 'cat-2.jpg' ...
 Uploading 'cat-3.jpg' ...
  95. #DCMuc16 Now we gonna tackle $_SERVER['argv'] !

  96. use Symfony\Component\Console\Command\Command; 
 class UploadFilesCommand extends Command { } #DCMuc16

    #DCMuc16
  97. use Symfony\Component\Console\Command\Command; 
 class UploadFilesCommand extends Command { protected function

    configure() { // ... } } #DCMuc16
  98. use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class UploadFilesCommand extends Command

    { protected function configure() { // ... } protected function execute(InputInterface $input, OutputInterface $output) { // ... } } #DCMuc16
  99. class UploadFilesCommand extends Command { protected function configure() { 


    } protected function execute(InputInterface $input, OutputInterface $output) { } } #DCMuc16
  100. class UploadFilesCommand extends Command { protected function configure() { $this


    ->setName('upload-files');
 } protected function execute(InputInterface $input, OutputInterface $output) { } } #DCMuc16
  101. class UploadFilesCommand extends Command { protected function configure() { $this


    ->setName('upload-files') ->addOption('overwrite', 'o', InputOption:: VALUE_NONE, 'Overwrite files?'); } protected function execute(InputInterface $input, OutputInterface $output) { } } #DCMuc16
  102. class UploadFilesCommand extends Command { protected function configure() { $this

    ->setName('upload-files') ->addOption('overwrite', 'o', InputOption:: VALUE_NONE, 'Overwrite files?') ->addArgument('filePaths', InputArgument::IS_ARRAY, 'Files to upload?'); } protected function execute(InputInterface $input, OutputInterface $output) { 
 } } #DCMuc16
  103. class UploadFilesCommand extends Command { protected function configure() { $this


    ->setName('upload-files') ->addOption('overwrite', 'o', InputOption:: VALUE_NONE, 'Overwrite files?') ->addArgument('filePaths', InputArgument::IS_ARRAY, 'Files to upload?'); } protected function execute(InputInterface $input, OutputInterface $output) { } } #DCMuc16
  104. class UploadFilesCommand extends Command { protected function configure() { $this


    ->setName('upload-files') ->addOption('overwrite', 'o', InputOption:: VALUE_NONE, 'Overwrite files?') ->addArgument('filePaths', InputArgument::IS_ARRAY, 'Files to upload?'); } protected function execute(InputInterface $input, OutputInterface $output) { $overwrite = $input->hasOption('overwrite'); } } #DCMuc16
  105. class UploadFilesCommand extends Command { protected function configure() { $this


    ->setName('upload-files') ->addOption('overwrite', 'o', InputOption:: VALUE_NONE, 'Overwrite files?') ->addArgument('filePaths', InputArgument::IS_ARRAY, 'Files to upload?'); } protected function execute(InputInterface $input, OutputInterface $output) { $overwrite = $input->hasOption('overwrite'); foreach ($input->getArgument('filePaths') as $path) { } } } #DCMuc16
  106. class UploadFilesCommand extends Command { protected function configure() { $this


    ->setName('upload-files') ->addOption('overwrite', 'o', InputOption:: VALUE_NONE, 'Overwrite files?') ->addArgument('filePaths', InputArgument::IS_ARRAY, 'Files to upload?'); } protected function execute(InputInterface $input, OutputInterface $output) { $overwrite = $input->hasOption('overwrite'); foreach ($input->getArgument('filePaths') as $path) { $output->writeLn("Uploading '{$path}' ... "); // ... do the upload depending on $overwrite } } } #DCMuc16
  107. Anything else? #DCMuc16

  108. Bundles • ~ PlugIns, but even better • Everything in

    Symfony Full-Stack is a Bundle • Yes, Symfony Full-Stack itself is a Bundle • Core and custom code is in Bundles • Registered in HttpKernel #DCMuc16
  109. • PSR – PHP Standards Recommendations • HTTP Specification Symfony

    ♥ Standards #DCMuc16
  110. • Git & GitHub • Semantic Versioning – Breaking.Feature.Fix •

    Dependency Injection • Event Driven • Popularize ORMs • New level of code quality and architecture • Command Line Tools Symfony drives Innovation … just to name a few (in the PHP world, at least …) #DCMuc16
  111. • Twig • Composer • Monolog • Swiftmailer • Silex

    • PHP CS Fixer
 … Symfony “by-products” #DCMuc16
  112. • Outstanding Documentation • Huge ecosystem of 3rd-party bundles •

    Creates many “celebrities” • Conferences • Certification Symfony’s Community (and SensioLabs) #DCMuc16
  113. Evolution, not Revolution Symfony 3 #DCMuc16

  114. @bicpi Questions? https://philipp-rieber.net #DCMuc16

  115. Links • http://symfony.com/ • https://github.com/symfony • http://fabien.potencier.org/what-is-symfony2.html • http://symfony.com/doc/current/create_framework •

    http://www.php-fig.org/ #DCMuc16