Slide 1

Slide 1 text

Don’t Reinvent the Wheel with Symfony Components h"p://www.flickr.com/photos/25947610@N04/2546779304  

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

0. What is Symfony2?

Slide 4

Slide 4 text

Symfony2 is a set of standalone, decoupled and cohesive components. On top of these components, Symfony2 is also a full-stack framework.

Slide 5

Slide 5 text

http://www.flickr.com/photos/alshepmcr/4298593016/

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

{ "require": { "symfony/http-kernel": "2.1.*", "symfony/config": "2.1.*", "symfony/routing": "2.1.*", "symfony/yaml": "2.1.*", "symfony/filesystem": "2.1.*", "symfony/finder": "2.1.*", "symfony/process": "2.1.*", ... } } Composer Installation

Slide 8

Slide 8 text

HttpKernel HttpFoundation Routing EventDispatcher Dependency Injection CssSelector DomCrawler BrowserKit Config Yaml Security OptionsResolver Console Filesystem Finder Locale Process Serializer Templating Form Validator Translation

Slide 9

Slide 9 text

They already use them!

Slide 10

Slide 10 text

1. HttpFoundation

Slide 11

Slide 11 text

GET /hello/hugo?p=2 HTTP/1.1 Host: say.hello.com Accept: text/html Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Cookie: username=hhamon User-Agent: Mozilla/5.0 Firefox/16.0 $path = $_SERVER['REQUEST_URI']; $page = isset($_GET['p']) ? $_GET['p'] : 1; $cookie = $_COOKIE['username']; $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE']; $agent = $_SERVER['HTTP_USER_AGENT']; Request

Slide 12

Slide 12 text

GET /hello/hugo?p=2 HTTP/1.1 Host: say.hello.com Accept: text/html Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Cookie: username=hhamon User-Agent: Mozilla/5.0 Firefox/16.0 use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $path = $request->getPathInfo(); $page = $request->query->get('p', 1); $cookie = $request->cookies->get('username'); $lang = $request->getPreferredLanguage(); $agent = $request->server->get('USER_AGENT'); Request

Slide 13

Slide 13 text

HTTP/1.1 200 OK Date: Fri, 02 Nov 2012 02:14:17 GMT Cache-Control: private, max-age=90 Set-Cookie: username=anonymous; Content-Type: text/html; charset=UTF-8 Hello Hugo! Response header('HTTP/1.1 200 OK'); header('Cache-Control: private, max-age=90'); setcookie('username', 'anonymous'); echo 'Hello Hugo!';

Slide 14

Slide 14 text

HTTP/1.1 200 OK Date: Fri, 02 Nov 2012 02:14:17 GMT Cache-Control: private, max-age=90 Set-Cookie: username=anonymous; Content-Type: text/html; charset=UTF-8 Hello Hugo! Response use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Response; $username = new Cookie('username', $name); $response = new Response('Hello Hugo!'); $response->setMaxAge(90); $response->headers->setCookie($username); $response->send();

Slide 15

Slide 15 text

use Symfony\Component\HttpFoundation\Response; $response = new Response(); $response->setCharset('UTF-8'); $response->setEtag('abcdef'); $response->headers->set('Content-Type', 'application/json'); if ($response->isNotModified($request)) { $response->send(); } $response->setContent('{ "name": "John Doe"}'); $response->setStatusCode(200); $response->setPublic(); $response->setSharedMaxAge(3600); $response->prepare($request); $response->send(); Caching

Slide 16

Slide 16 text

Session Management use Symfony\Component\HttpFoundation\Session; $storage = new Storage\Handler\NativeSessionStorage(); $session = new Session\Session($storage); $session->start(); // Saving data into the session $session->set('cart', array(1, 2, 3)); $cart = $session->get('cart'); $session->save();

Slide 17

Slide 17 text

2. Routing

Slide 18

Slide 18 text

Bidirectional Router

Slide 19

Slide 19 text

Router in action Router R1   R2   R3   R3   R   Route /hello/hugo /home /about /hello/{name}

Slide 20

Slide 20 text

Configuring Routes with PHP use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $routes = new RouteCollection(); $routes->add('post', new Route( '/blog/{id}/{slug}', array('controller' => 'BlogController::post'), array('id' => '\d+') ));

Slide 21

Slide 21 text

Configuring Routes inYAML # config/routing.yml blog: pattern: /blog defaults: { controller: 'BlogController::index' } post: pattern: /blog/{id}/{slug} defaults: { controller: 'BlogController::post' } requirements: { id: \d+ }

Slide 22

Slide 22 text

Matching a Route use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $context = new RequestContext($_SERVER['REQUEST_URI']); $matcher = new UrlMatcher($routes, $context); $params = $matcher->match('/blog/42/symfony-2');

Slide 23

Slide 23 text

Generating URLs use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Generator\UrlGenerator; // ... $context = new RequestContext($_SERVER['REQUEST_URI']); $generator = new UrlGenerator($routes, $context); echo $generator->generate('post', array( 'id' => '42', 'slug' => 'symfony-components', 'page' => '2', )); // Generates /blog/42/symfony-components?page=2

Slide 24

Slide 24 text

The all-in-one Router $locator = new FileLocator(array(__DIR__.'/config')); $router = new Router( new YamlFileLoader($locator), 'routing.yml', array('cache_dir' => __DIR__.'/cache'), new RequestContext($_SERVER['REQUEST_URI']), ); $params = $router->match('/blog/42/symfony'); $url = $router->generate('blog');

Slide 25

Slide 25 text

3. Http Kernel

Slide 26

Slide 26 text

Kernel in action Kernel Response Request Where the magic happens!

Slide 27

Slide 27 text

Kernel in action // index.php use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->overrideGlobals(); $kernel = new DrupalKernel(...); $response = $kernel->handle($request); $response->prepare($request); $response->send();

Slide 28

Slide 28 text

The HttpKernelInterface namespace Symfony\Component\HttpKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; interface HttpKernelInterface { const MASTER_REQUEST = 1; const SUB_REQUEST = 2; public function handle( Request $request, $type = self::MASTER_REQUEST, $catch = true); }

Slide 29

Slide 29 text

Wrapping a legacy application // index.php require_once 'config.php'; $module = !empty($_GET['module']) ? $_GET['module'] : ''; $action = !empty($_GET['action']) ? $_GET['action'] : ''; if (!controller_exists($module, $action)) { header('HTTP/1.0 404 Page Not Found'); echo render_view('error', '404'); exit; } echo include_controller($module, $action);

Slide 30

Slide 30 text

use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class LegacyKernel implements HttpKernelInterface { public function handle(Request $request, ...) { $module = $request->query->get('module'); $action = $request->query->get('action'); require_once 'config.php'; if (!controller_exists($module, $action)) { $html = render_view('error', '404'); return new Response($html, 404); } $html = include_controller($module, $action); return new Response($html, 200); } }

Slide 31

Slide 31 text

Wrapping a legacy application // index.php use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->overrideGlobals(); $kernel = new LegacyKernel(); $response = $kernel->handle($request); $response->prepare($request); $response->send();

Slide 32

Slide 32 text

4. Event Dispatcher

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Real World Example $order = new Order(); $order->add('Cheeseburger', 3); $order->add('Caesar Salad', 1); $order->add('Coffee', 4); $cashier = new Cashier(); $cashier->dispatch($order);

Slide 35

Slide 35 text

public function dispatch(Order $order) { if ($order->has('Cheeseburger')) { $this->cookCheesburger($order->getQty('Cheeseburger')); } if ($order->has('Caesar Salad')) { $this->cookCaesarSalad($order->getQty('Caesar Salad')); } if ($order->has('Coffee')) { $this->cookCoffee($order->getQty('Coffee')); } // ... }

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

public function dispatch(Order $order) { // ... if ($order->has('Pizza')) { $qty = $order->getQty('Pizza'); $this->cookPizza($qty); } // ... }

Slide 38

Slide 38 text

use Symfony\Component\EventDispatcher\EventDispatcher; $cashier = new EventDispatcher(); $listeners[] = array(new CheesburgerCooker(), 'onMealCook'); $listeners[] = array(new CaesarSaladCooker(), 'onMealCook'); $listeners[] = array(new CoffeeCooker(), 'onMealCook'); $listeners[] = array(new PizzaCooker(), 'onMealCook'); foreach ($listeners as $listener) { $cashier->addListener('meal.cook', $listener); } Using the Event Dispatcher

Slide 39

Slide 39 text

$cashier = new EventDispatcher(); // ... $order = new Order(); $order->add('Cheeseburger', 3); $order->add('Caesar Salad', 1); $order->add('Coffee', 4); $order->add('Pizza', 1); $event = new MealEvent($order); $cashier->dispatch('meal.cook', $event);

Slide 40

Slide 40 text

use Symfony\Component\EventDispatcher\Event; class MealEvent extends Event { public $order; public function __construct(Order $order) { $this->order = $order; } } Designing an event

Slide 41

Slide 41 text

class PizzaCooker { public function onMealCook(MealEvent $event) { if (!$event->order->has('Pizza')) { return; } $quantity = $event->order->getQuantity('Pizza'); // ... cook the pizza!!! } } Designing a listener (cooker)

Slide 42

Slide 42 text

5. YAML

Slide 43

Slide 43 text

http://www.flickr.com/photos/drymek/4310204250/

Slide 44

Slide 44 text

# parameters.yml parameters: database_host: localhost database_port: 3306 database_name: demo database_user: root database_pwd: ~ credentials: { user: john, password: secret } recaptcha: public_key: 6LdZPNYSAAAAAICZ43AfeMfEsJVQXr_CXh4f3mWy private_key: 6LdZPNYSAAAAAB8ALTjfVr72iGYODjKAzic1KuoC Designing a listener (cooker)

Slide 45

Slide 45 text

use Symfony\Component\Yaml\Yaml; $config = Yaml::parse('parameters.yml'); echo '
';
print_r($config);
echo '
'; Loading a YAML file

Slide 46

Slide 46 text

Array ( [parameters] => Array ( [database_host] => localhost [database_port] => 3306 [database_name] => demo [database_user] => root [database_pwd] => [credentials] => Array ( [user] => john [password] => secret ) [recaptcha] => Array ( [public_key] => 6LdZPNYSAAAAAICZ43AfeMfEsJVQXr_CXh4f3mWy [private_key] => 6LdZPNYSAAAAAB8ALTjfVr72iGYODjKAzic1KuoC ) ) )

Slide 47

Slide 47 text

$config = array('parameters' => array( 'database_host' => 'localhost', 'database_port' => 3306, 'database_name' => 'root', 'database_user' => 'root', 'database_pwd' => null, 'credentials' => array( 'user' => 'john', 'password' => 'secret', ), 'recaptcha' => array( 'public_key' => '6LdZPNYSAAAAAICZ43AfeMfEsJVQXr_CXh4f3m', 'private_key' => '6LdZPNYSAAAAAB8ALTjfVr72iGYODjKAzic1Ku', ), )); Dumping an array to YAML

Slide 48

Slide 48 text

use Symfony\Component\Yaml\Yaml; $yaml = Yaml::dump($config); file_put_contents( __DIR__.'/config.yml', $yaml ); Dumping an array to YAML

Slide 49

Slide 49 text

6. Filesystem

Slide 50

Slide 50 text

http://www.flickr.com/photos/sebastian_bergmann/173880207/ PHP Way mkdir() copy() delete() unlink() chmod() chown() ...

Slide 51

Slide 51 text

http://www.flickr.com/photos/amazeelabs/7843168540/ Symfony Way Filesystem

Slide 52

Slide 52 text

$cache1 = __DIR__.'/tmp/cache1'; $cache2 = __DIR__.'/tmp/cache2'; $fs = new Symfony\Component\Filesystem\Filesystem(); $fs->mkdir(array($cache1, $cache2)); $fs->touch($cache1.'/routing.php'); $fs->copy( $cache1.'/routing.php', $cache1.'/routing.old' ); $fs->remove($cache1); The Filesystem object

Slide 53

Slide 53 text

abstract class AbstractCommand extends Command { // ... protected function createDirectory($directory) { $filesystem = $this->getFilesystem(); try { $filesystem->mkdir($directory); } catch (IOException $e) { throw new \RuntimeException('...', 0, $e); } } } Example taken from Propel2

Slide 54

Slide 54 text

7. Finder

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

use Symfony\Component\Finder\Finder; $finder = new Finder(); $finder ->files() ->in(__DIR__.'/src/Symfony/Bundle/FrameworkBundle') ->exclude('Tests') ->ignoreVCS(true) ->name('/Controller/') ; foreach ($finder->sortByName() as $file) { echo $file->getFilename()."\n"; } Using the Finder Component

Slide 57

Slide 57 text

// Filter by name $finder->directories()->name('Tests'); $finder->files()->name('*.php'); $finder->files()->name('/\.php$/'); $finder->files()->notName('*.rb'); // Filter by size $finder->files()->size('< 1.5K'); $finder->files()->size('>= 1K')->size('<= 2K'); // Filter by date $finder->date('since yesterday'); // Limiting the depth $finder->depth('== 0'); $finder->depth('< 3');

Slide 58

Slide 58 text

$filter = function (\SplFileInfo $file) { if (strlen($file) > 10) { return false; } }; $finder->files()->filter($filter); Custom filters

Slide 59

Slide 59 text

class MagentoFinder extends Symfony\Component\Finder\Finder { public function __construct() { parent::__construct(); $this ->name('*.php') ->name('*.phtml') ->name('*.xml') ->exclude(array( 'lib', 'shell', 'app/Mage.php', 'app/code/core', 'app/code/community', 'app/design/frontend/default', 'app/design/frontend/enterprise/default', 'app/design/frontend/base', 'app/design/adminhtml/default') ) ; } }

Slide 60

Slide 60 text

8. Console

Slide 61

Slide 61 text

http://www.flickr.com/photos/ideonexus/7012997141/lightbox/

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

#!/usr/bin/env php $loader = require __DIR__.'/vendor/autoload.php'; $loader->add('Hangman', __DIR__.'/src'); use Symfony\Component\Console\Application; use Acme\Command\HangmanPlayCommand; $application = new Application(); $application->add(new HangmanPlayCommand()); $application->run(); The Console Script

Slide 65

Slide 65 text

namespace Acme\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class HangmanPlayCommand extends Command { protected function configure() { ... } protected function execute( InputInterface $input, OutputInterface $output ) { ... } } Creating a new Command Class

Slide 66

Slide 66 text

protected function configure() { $this ->setName('hangman:play') ->setDescription('Play the famous Hangman game') ->addArgument('name', InputArgument::REQUIRED, 'Your name') ->addOption('length', 'l', InputOption::VALUE_REQUIRED) ->setHelp('The complete manual ...'); } Documenting the Command

Slide 67

Slide 67 text

protected function execute($input, $output) { $words = new WordList(); $words->load(__DIR__.'/../../../data/words.txt'); $length = $input->getOption('length', 8); $name = $input->getArgument('name'); $game = new Game($words->getRandomWord($length)); // ... } Adding the business logic

Slide 68

Slide 68 text

protected function execute($input, $output) { // ... $dialog = $this->getHelperSet()->get('dialog'); $output->writeln('Welcome '.$name.' to the Hangman Game!'); while (!$game->isOver()) { $letter = $dialog->ask($output, 'Type a letter: '); $game->tryLetter($letter); $output->writeLn(implode(' ', $game->getSolution())); } if ($game->isWon()) { $output->writeln('Congratulations!'); } else { $output->writeln('Game Over!'); } // ... }

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

Some purposes Generating les Deploying les Dumping con guration Clearing caches Generating documentation Running a web server Sending batch emails…

Slide 71

Slide 71 text

9. Translation

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

Basic Usage $selector = new MessageSelector(); $trans = new Translator('fr', $selector); $trans->setFallbackLocale('en'); $loader = new XliffFileLoader(); $trans->addLoader('xliff', $loader); $trans->addResource('yaml', '/fr.yml', 'fr'); $trans->trans('Hello %name%', array( '%name%' => $name ));

Slide 74

Slide 74 text

XLIFF Support Hello %name% Bonjour %name%

Slide 75

Slide 75 text

Usage in Behat class TranslatedLoader implements LoaderInterface { private $translator; // ... public function load(ContextInterface $context) { foreach ($context->getTranslationResources() as $path) { $extension = pathinfo($path, PATHINFO_EXTENSION); if ('yml' === $extension) { $this->translator->addResource( 'yaml', $path, basename($path, '.yml'), 'behat.definitions' ); } // ... } } }

Slide 76

Slide 76 text

10. Dependency Injection

Slide 77

Slide 77 text

What’s dependency injection? « Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into elds. » http://picocontainer.org/injection.html

Slide 78

Slide 78 text

Real World Example class Database { private $dbh; function __construct(PDO $dbh) { $this->dbh = $dbh; } function execute($query) { // ... } }

Slide 79

Slide 79 text

$container->setParameter('foo', 'bar'); $container->getParameter('dbal.dsn'); $dbal = $container->get('dbal'); The Service Container

Slide 80

Slide 80 text

Real World Example class Database { private $dbh; function __construct(PDO $dbh) { $this->dbh = $dbh; } function execute($query) { // ... } }

Slide 81

Slide 81 text

Initializing the Container $container = new ContainerBuilder(); $params = array( 'dbal.dsn' => 'mysql:host=localhost;dbname=demo', 'dbal.user' => 'root', 'dbal.password' => 'secret', ); foreach ($params as $name => $value) { $container->setParameter($name, $value); }

Slide 82

Slide 82 text

Registering a service $container = new ContainerBuilder(); // ... $container ->register('dbal.pdo_handler', 'PDO') ->addArgument('%dbal.dsn%') ->addArgument('%dbal.user%') ->addArgument('%dbal.password%') ;

Slide 83

Slide 83 text

Injecting service references $container = new ContainerBuilder(); // ... $reference = new Reference('dbal.pdo_handler'); $container ->register('dbal', 'Database') ->addArgument($reference) ;

Slide 84

Slide 84 text

Describing services with XML mysql:... root secret %dbal.dsn% %dbal.user% %dbal.password%

Slide 85

Slide 85 text

Describing services with XML

Slide 86

Slide 86 text

12. Validator

Slide 87

Slide 87 text

The Validator Component use Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Mapping\ClassMetadataFactory; use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Constraints\Email; $validator = new Validator( new ClassMetadataFactory(new StaticMethodLoader()), new ConstraintValidatorFactory() ); $validator->validateValue('[email protected]', new Email());

Slide 88

Slide 88 text

Validating PHP Objects use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints; class Post { public $title; public $body; static public function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('title', new Constraints\NotNull()); $metadata->addPropertyConstraint('title', new Constraints\NotBlank()); $metadata->addPropertyConstraint('body', new Constraints\Length(array( 'max' => 10 ))); } }

Slide 89

Slide 89 text

Validating PHP Objects $post = new Post(); $post->title = 'Lorem ipsum...'; $post->body = 'Lorem ipsum...'; $violations = $validator ->validate($post) ;

Slide 90

Slide 90 text

12. Process

Slide 91

Slide 91 text

Executing command lines use Symfony\Component\Process\Process; $proc = new Process('ls -lah'); $proc->setWorkingDirectory(__DIR__.'/..'); $proc->setTimeout(3600); $proc->run(); if (!$proc->isSuccessful()) { throw new RuntimeException( $proc->getErrorOutput() ); } print $proc->getOutput();

Slide 92

Slide 92 text

Executing command lines total 0 drwxr-xr-x 11 Hugo staff 374B Sep 8 13:38 . drwxr-xr-x 8 Hugo staff 272B Sep 8 13:39 .. drwxr-xr-x 4 Hugo staff 136B Aug 30 11:31 01-ClassLoader drwxr-xr-x 2 Hugo staff 68B Aug 30 11:30 02-HttpFoundation drwxr-xr-x 4 Hugo staff 136B Sep 4 22:06 03-Routing drwxr-xr-x 6 Hugo staff 204B Sep 2 11:49 04-HttpKernel drwxr-xr-x 4 Hugo staff 136B Sep 2 14:07 05-Filesystem drwxr-xr-x 3 Hugo staff 102B Sep 4 22:40 06-Finder drwxr-xr-x 5 Hugo staff 170B Sep 8 10:24 07-Console drwxr-xr-x 5 Hugo staff 170B Sep 8 12:55 08-Yaml drwxr-xr-x 3 Hugo staff 102B Sep 8 13:41 09-Process

Slide 93

Slide 93 text

13. CSS Selector

Slide 94

Slide 94 text

CSS Selectors to XPath use Symfony\Component\CssSelector\CssSelector; print CssSelector::toXPath('div.item > h4 > a'); descendant-or-self::div[contains(concat(' ',normalize-space(@class), ' '), ' item ')]/ h4/a

Slide 95

Slide 95 text

14. DOM Crawler

Slide 96

Slide 96 text

Using the DOM Crawler use Symfony\Component\DomCrawler\Crawler; $html = <<<'HTML'

Hello World!

Hello Crawler!

HTML; $crawler = new Crawler($html); $crawler = $crawler->filterXPath('descendant-or-self::body/p'); $crawler = $crawler->filter('body > p');

Slide 97

Slide 97 text

The DOM Crawler API // Filtering with CSS or XPath selectors $tweets = $crawler->filter('#sidebar .tweet'); $tweets = $crawler->filterXPath('//p[class="tweet"]'); // Traversing $first = $tweets->first(); $third = $tweets->eq(2); $last = $tweets->last(); // Extracting $text = $first->text(); $class = $first->attr('class'); $infos = $first->extract(array('_text', 'class'));

Slide 98

Slide 98 text

Reducing the DOM Crawler $filter = function ($node, $i) { $content = (string) $node->textContent; if (!preg_match('/symfony/i', $content)) { return false; } }; $tweets = $crawler->reduce($filter);

Slide 99

Slide 99 text

Finding links Finding forms $link = $crawler->selectLink('Click me')->link(); $crawler = $client->click($link); $form = $crawler->selectButton('send')->form(); $client->submit($form, array('name' => 'Foo'));

Slide 100

Slide 100 text

DOM Crawler in Goutte use Goutte\Client; $client = new Client(); $crawler = $client->request('GET', 'http://www.symfony.com'); $download = $crawler ->filter('#header .download_button') ->link() ; $crawler = $client->click($download);

Slide 101

Slide 101 text

https://joind.in/talk/view/7413