Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Philipp Rieber

December 03, 2016
Tweet

More Decks by Philipp Rieber

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. $ composer require symfony/yaml require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; $yaml =

    <<<YAML parameters: user: symfony pass: awesome YAML; Yaml Component #DCMuc16
  5. $ 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
  6. $ 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
  7. 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
  8. $ composer require symfony/finder Finder Component require 'vendor/autoload.php'; use Symfony\Component\Finder\Finder;

    $finder = Finder::create() ->in('/Users/bicpi/photos') ->files();
 #DCMuc16
  9. $ 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
  10. $ 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
  11. $ 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
  12. Do one thing. 
 Make it excellent. 
 Stay out

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

    utilities for the filesystem • Translation Tools for internationalisation • … #DCMuc16
  14. 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
  15. 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
  16. 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
  17. 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
  18. Request
 /blog/article?id=34 class BlogController extends Controller { /** * @Route("/blog/article")

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

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

    */ public function articleAction(Request $request) { $id = $request->query->get('id'); 
 
 
 
 
 
 } } #DCMuc16
  21. 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
  22. 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
  23. 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>
  24. 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
  25. 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>
  26. • Routing Match URL to controller action • Templating Render

    templates • Security Decide about access • Config Load configuration • Forms Create HTML forms • … #DCMuc16
  27. 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
  28. 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
  29. imports: - { resource: parameters.yml } database:
 driver: pdo_mysql
 charset:

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

    utf8
 user: '%db_user%' password: '%db_password%'
 
 # … config.yml parameters.yml parameters:
 
 
 
 
 
 #DCMuc16
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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!
  36. 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
  37. 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
  38. 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?
  39. 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
  40. 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
  41. 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
  42. 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
  43. /** * @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
  44. /** * @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
  45. File upload script with some flags, options and arguments #DCMuc16

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

  46. 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' ...

  47. 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' ...

  48. 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' ...
  49. 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
  50. class UploadFilesCommand extends Command { protected function configure() { 


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


    ->setName('upload-files');
 } protected function execute(InputInterface $input, OutputInterface $output) { } } #DCMuc16
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. • 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
  60. • Twig • Composer • Monolog • Swiftmailer • Silex

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

    Creates many “celebrities” • Conferences • Certification Symfony’s Community (and SensioLabs) #DCMuc16