Slide 1

Slide 1 text

Don’t Reinvent the Wheel w/ Symfony Components h"p://www.flickr.com/photos/vemsteroo/7595601626/   PFCongres  2012  –  Utrecht  –  Hugo  Hamon  

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 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 6

Slide 6 text

ClassLoader http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 7

Slide 7 text

require '/path/to/UniversalClassLoader.php'; use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => __DIR__.'/vendor/symfony/src', 'Zend' => __DIR__.'/vendor/zend/src', 'Demo' => __DIR__.'/src', )); $loader->registerPrefixes(array( 'Acme' => __DIR__.'/src', )); $loader->register();

Slide 8

Slide 8 text

YAML http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 9

Slide 9 text

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

Slide 10

Slide 10 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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 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 13

Slide 13 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 a YAML File

Slide 14

Slide 14 text

use Symfony\Component\Yaml\Yaml; $yaml = Yaml::dump($config); echo $yaml; Dumping a YAML File

Slide 15

Slide 15 text

HttpFoundation http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 16

Slide 16 text

Client Server HTTP Request HTTP Response … Network …

Slide 17

Slide 17 text

http://www.flickr.com/photos/sebastian_bergmann/173880207/ PHP Way $_GET $_POST $_COOKIE $_SERVER $_SESSION setcookie() headers()

Slide 18

Slide 18 text

http://www.flickr.com/photos/amazeelabs/7843168540/ Symfony Way Request Response Session Cookie File ParameterBag HeaderBag Streaming HTTP Caching ...

Slide 19

Slide 19 text

use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->overrideGlobals(); $page = $request->query->get('page', 1); $body = $request->request->get('body'); $name = $request->cookies->get('username'); $user = $request->server->get('HTTP_AUTH_USER'); $ip = $request->getClientIp(); $lang = $request->getPreferredLanguage(); $user = $request->getUser(); $pwd = $request->getPassword(); $isPost = $request->isMethod('POST'); $isHttps = $request->isSecure(); $isAjax = $request->isXmlHttpRequest();

Slide 20

Slide 20 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();

Slide 21

Slide 21 text

use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\RedirectResponse; $cookie = new Cookie('user', 'hhamon', '+30 days'); $resp = new RedirectResponse('http://www.acme.com'); $resp->headers->setCookie($cookie); $resp->send(); Cookies and Redirects

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Routing http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 24

Slide 24 text

Bidirectional Router

Slide 25

Slide 25 text

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+') )); PHP Con guration

Slide 26

Slide 26 text

# config/routing.yml blog: pattern: /blog defaults: { controller: 'BlogController::index' } post: pattern: /blog/{id}/{slug} defaults: { controller: 'BlogController::post' } requirements: { id: \d+ } YAML Con guration

Slide 27

Slide 27 text

PHP XML YAML Closures Annotations

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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 URLs Generator

Slide 30

Slide 30 text

$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'); The All-in-One Router

Slide 31

Slide 31 text

HttpKernel http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 32

Slide 32 text

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); } The HttpKernel Interface

Slide 33

Slide 33 text

namespace Acme; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Routing\RouterInterface; class Kernel implements HttpKernelInterface { private $router; public function __construct(RouterInterface $router) { $this->router = $router; } public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) { // ... } } Designing a Kernel for a Legacy Application

Slide 34

Slide 34 text

class Kernel implements HttpKernelInterface { public function handle(Request $request, ...) { try { $uri = $request->server->get('PATH_INFO'); $params = $this->router->match($uri); } catch (ResourceNotFoundException $e) { $params['controller'] = 'Acme\Controller\ErrorController'; $params['action'] = 'error404'; } $method = sprintf('%sAction', $params['action']); $callable = array(new $params['controller'](), $method); return call_user_func_array($callable, array($request)); } }

Slide 35

Slide 35 text

namespace Acme\Controller; use Symfony\Component\HttpFoundation\Response; abstract class Controller { public function render($name, array $context = array()) { $template = sprintf(__DIR__.'/../../../views/%s.php', $name); if (!is_readable($template)) { throw new \InvalidArgumentException('No "'. $template.'" template.'); } extract($context); ob_start(); include $template; $content = ob_get_clean(); return new Response($content, 200); } } The Base Controller Class

Slide 36

Slide 36 text

namespace Acme\Controller; use Symfony\Component\HttpFoundation\Request; class BlogController extends Controller { public function indexAction(Request $request) { // Get a list of recent posts $posts = ...; return $this->render('blog/index', array('posts' => $posts)); } } The BlogController Class

Slide 37

Slide 37 text

$request = Request::createFromGlobals(); $request->overrideGlobals(); $router = new Router(...); $kernel = new Acme\Kernel($router); $response = $kernel->handle($request); $response->send(); index.php

Slide 38

Slide 38 text

Event Dispatcher http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

class Article { public function save(Database $con) { $parser = new Markdown(); $html = $parser->toHTML($this->getContent()); $this->setHtmlContent($html); $this->save($con); $this->updateLuceneIndex(); } }

Slide 41

Slide 41 text

use Symfony\Component\EventDispatcher\EventDispatcher; // Declaring listeners $l1 = array(new ArticleListener(), 'onPreSave'); $l2 = array(new LuceneListener(), 'onPostSave'); // Registering listeners $dispatcher = new EventDispatcher(); $dispatcher->addListener('article.pre_save', $l1); $dispatcher->addListener('article.post_save', $l2); Registering Listeners in the Dispatcher

Slide 42

Slide 42 text

Setting the Dispatcher use Symfony\Component\EventDispatcher\EventDispatcher; class Article { private $dispatcher; public function setDispatcher(EventDispatcher $dp) { $this->dispatcher = $dp; } }

Slide 43

Slide 43 text

Dispatching Events class Article { // ... public function save(Database $con) { $e = new ArticleEvent($this); $this->dispatcher->dispatch('article.pre_save', $e); $this->save($con); $this->dispatcher->dispatch('article.post_save', $e); } }

Slide 44

Slide 44 text

The Listener class ArticleListener { public function onPreSave(ArticleEvent $event) { $article = $event->article; $markdown = $article->getContent(); $parser = new Markdown(); $html = $parser->toHTML($markdown); $article->setHtmlContent($html); } }

Slide 45

Slide 45 text

Triggering the Registered Listeners $markdown = 'Some **markdown**'; $article = new Article(); $article->setDispatcher($dispatcher); $article->setMarkdownContent($markdown); $article->save();

Slide 46

Slide 46 text

Filesystem http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 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 50

Slide 50 text

Finder http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 51

Slide 51 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"; }

Slide 52

Slide 52 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 53

Slide 53 text

No content

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Console http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

namespace HangmanPlay\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) { // ... } }

Slide 61

Slide 61 text

namespace HangmanPlay\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) { // ... } }

Slide 62

Slide 62 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, 'The word length', 8) ->setHelp(<<%command.name% command. You can run the command like this: $ php %command.full_name% hhamon --length=6 EOF); }

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

protected function execute(InputInterface $input, OutputInterface $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 65

Slide 65 text

No content

Slide 66

Slide 66 text

Translation http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 67

Slide 67 text

$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 68

Slide 68 text

Hello %name% Bonjour %name%

Slide 69

Slide 69 text

Dependency Injection http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

mysql:host=localhost;dbname=demo argument> root secret

Slide 72

Slide 72 text

$locator = new FileLocator(__DIR__); $container = new ContainerBuilder(); $loader = new XmlFileLoader( $container, $locator ); $loader->load('services.xml'); // Get the DBAL service $db = $container->get('dbal'); $result = $db->execute('SELECT ...');

Slide 73

Slide 73 text

Validator http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 74

Slide 74 text

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 75

Slide 75 text

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 76

Slide 76 text

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

Slide 77

Slide 77 text

Process http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 78

Slide 78 text

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 79

Slide 79 text

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 80

Slide 80 text

Serializer http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 81

Slide 81 text

Serialization is the process of converting an object state into a format that can be stored and resurrected later Wikipedia  

Slide 82

Slide 82 text

use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; // Register the normalizers $normalizers[] = new GetSetMethodNormalizer(); // Register the encoders $encoders[] = new JsonEncoder(); $encoders[] = new XmlEncoder(); // Create and initialize the serializer $serializer = new Serializer($normalizers, $encoders);

Slide 83

Slide 83 text

$serializer ->serialize($object, 'xml') ;

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

Locale http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 86

Slide 86 text

use Symfony\Component\Locale\Locale; // Get the country names for a locale or get all country codes $countries = Locale::getDisplayCountries('pl'); $countryCodes = Locale::getCountries(); // Get the language names for a locale or get all language codes $languages = Locale::getDisplayLanguages('fr'); $languageCodes = Locale::getLanguages(); // Get the locale names for a given code or get all locale codes $locales = Locale::getDisplayLocales('en'); $localeCodes = Locale::getLocales(); // Get ICU versions $icuVersion = Locale::getIcuVersion(); $icuDataVersion = Locale::getIcuDataVersion();

Slide 87

Slide 87 text

CSS Selector http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

DOM Crawler http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

Slide 90

Slide 90 text

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 91

Slide 91 text

// 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')); Using the Crawler

Slide 92

Slide 92 text

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

Slide 93

Slide 93 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 94

Slide 94 text

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