Slide 1

Slide 1 text

@bicpi Symfony – In Plain English Philipp Rieber #DCMuc16

Slide 2

Slide 2 text

#DCMuc16

Slide 3

Slide 3 text

#DCMuc16

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

ottonova.de We are creating a better private health insurance company for you. #DCMuc16

Slide 7

Slide 7 text

@sfugmuc Symfony User Group Munich #DCMuc16

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

$ composer require symfony/yaml Yaml Component #DCMuc16

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

$ composer require symfony/yaml require 'vendor/autoload.php'; use Symfony\Component\Yaml\Yaml; $yaml = << ['user' => 'symfony', 'pass' => 'awesome']] Yaml Component #DCMuc16

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

$ composer require symfony/finder Finder Component #DCMuc16

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

$ 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

Slide 24

Slide 24 text

$ 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

Slide 25

Slide 25 text

$ 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

Slide 26

Slide 26 text

Do one thing. 
 Make it excellent. 
 Stay out of the way. – Unix Philosophy ‘ #DCMuc16

Slide 27

Slide 27 text

• … • Console Command Line Interface • Filesystem Basic utilities for the filesystem • Translation Tools for internationalisation • … #DCMuc16

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

☺ #DCMuc16

Slide 31

Slide 31 text

Request ☺ 1 #DCMuc16

Slide 32

Slide 32 text

Request ☺ Response 2 1 #DCMuc16 ?

Slide 33

Slide 33 text

Request ☺ Response 2 3 #DCMuc16 1 ?

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

It is all about transforming a Request into a Response #DCMuc16

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

HTTP specification #DCMuc16

Slide 40

Slide 40 text

#DCMuc16 Code please.

Slide 41

Slide 41 text

Request
 /blog/article?id=34 #DCMuc16

Slide 42

Slide 42 text

Request
 /blog/article?id=34 class BlogController extends Controller { 
 
 
 
 
 
 
 } #DCMuc16

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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
 Article not found

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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
 Article 34

Slide 51

Slide 51 text

During the Request ➾ Response flow,
 many more components will assist #DCMuc16

Slide 52

Slide 52 text

• Routing Match URL to controller action • Templating Render templates • Security Decide about access • Config Load configuration • Forms Create HTML forms • … #DCMuc16

Slide 53

Slide 53 text

Who is going to configure all this?

Slide 54

Slide 54 text

Symfony Configuration #DCMuc16

Slide 55

Slide 55 text

Symfony Configuration dev prod test #DCMuc16

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

database:
 driver: pdo_mysql
 charset: utf8
 
 
 
 # … config.yml #DCMuc16

Slide 63

Slide 63 text


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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Dependency Injection #DCMuc16

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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!

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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?

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

#DCMuc16 Service Container

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

services:
 logger:
 class: FileLogger
 
 
 
 config.yml #DCMuc16

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

/** * @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

Slide 87

Slide 87 text

/** * @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

Slide 88

Slide 88 text

config.yml #DCMuc16 services:
 logger:
 class: NullLogger weather_forecast:
 class: WeatherForecast
 arguments:
 - '@logger'
 


Slide 89

Slide 89 text

config.yml #DCMuc16 services:
 logger:
 class: DatabaseLogger weather_forecast:
 class: WeatherForecast
 arguments:
 - '@logger'
 


Slide 90

Slide 90 text

#DCMuc16 Console

Slide 91

Slide 91 text

File upload script with some flags, options and arguments #DCMuc16 $ ./upload-files --overwrite cat-1.jpg cat-2.jpg cat-3.jpg 


Slide 92

Slide 92 text

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


Slide 93

Slide 93 text

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


Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

#DCMuc16 Now we gonna tackle $_SERVER['argv'] !

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

use Symfony\Component\Console\Command\Command; 
 class UploadFilesCommand extends Command { protected function configure() { // ... } } #DCMuc16

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

Anything else? #DCMuc16

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

• PSR – PHP Standards Recommendations • HTTP Specification Symfony ♥ Standards #DCMuc16

Slide 110

Slide 110 text

• 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

Slide 111

Slide 111 text

• Twig • Composer • Monolog • Swiftmailer • Silex • PHP CS Fixer
 … Symfony “by-products” #DCMuc16

Slide 112

Slide 112 text

• Outstanding Documentation • Huge ecosystem of 3rd-party bundles • Creates many “celebrities” • Conferences • Certification Symfony’s Community (and SensioLabs) #DCMuc16

Slide 113

Slide 113 text

Evolution, not Revolution Symfony 3 #DCMuc16

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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