Symfony2 - From the Trenches by Lukas Kahwe Smith, Kris Wallsmith, Thibault Duplessis, Jeremy Mikola, Jordi Boggiano, Jonathan H. Wage, Bulat Shakirzyanov
Agenda Getting Setup Caching Code Flow Performance Tips Dependency Injection Asset Management Configuration Choices Testing Controller Choices Deployment Application Choices Third Party Bundles Doctrine Resources
Getting the works) PHP 5.3+ with ext/intl (compat lib is in setup Read check.php for details (dev/prod php.ini's from Liip) Using OSX? php53-intl from liangzhenjing or build-entropy-php from chregu Blog post on installing PHP 5.3 with intl from Justin Hileman Initial setup symfony-sandbox symfony-bootstrap Symfony2Project Read the Coding Style Guide (Code Sniffer Rules) Managing external dependencies Submodule: not everything is in git (svn, mercurial, etc.) Vendor install/update scripts: risk of getting out of sync MR (not cross platform)
Code Flow 1. Frontend Controller (web/app[_dev].php) Loads autoloader Creates/boots kernel Creates request (from globals) and passes to kernel 2. Kernel Loads app config (app/config/config_[prod|dev|test]) Resolves URL path to a controller (go to 3.) Outputs response returned by the controller 3. Controller Loads model and view Potentially creates a sub-request (go to 2.) Creates response and returns it
Execution Flow
Dependency Injection All objects are instantiated in one of two ways: Using the "new" operator Using an object factory All objects get collaborators in one of two ways Passed to object constructor Set using a setter
Application Using Dependency Injection
<?xml version="1.0" encoding="utf-8" ?> <container xmlns="http://www.symfony-project.org/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <parameters> <parameter key="sitemap.class">BundleAvalancheSitemapBundleSitemap</parameter> </parameters> <services> <service id="sitemap" class="%sitemap.class%" /> </services> </container> Describe Your Services
<?php class cachedDevDebugProjectContainer extends Container { /** * Gets the 'sitemap' service. * * @return BundleAvalancheSitemapBundleSitemap */ protected function getSitemapService() { return $this->services['sitemap'] = new BundleAvalancheSitemapBundleSitemap(); } /** * Gets the default parameters. * * @return array An array of the default parameters */ protected function getDefaultParameters() { return array( 'sitemap.class' => 'BundleAvalancheSitemapBundleSitemap' ); } } Service Definitions Are Dumped to Raw PHP
Dependency Injection Container Benefits: No performance loss Lazy instantiation Readable service configurations Gotchas: Can become hard to work with if the DI extension tries to do too much Be aware of circular dependencies Might lead to code that cannot be used outside of DIC
Circular Dependency Example
<?php class SomeClass { private $container; public function __construct(ContainerInterface $container) <service id="some_service" class="SomeClass"> { <argument type="service" id="service_container" /> $this->container = $container; </service> } <!-- or --> // or <service id="some_service" class="SomeClass"> public function setContainer(ContainerInterface $container) <call method="setContainer"> { <argument type="service" id="service_container" /> $this->container = $container; </call> } </service> public function getDocumentManager() { return $this->container->get('document_manager'); } } Container Injection
<?php class SomeClass { private $documentManager; public function __construct(DocumentManager $documentManager) { $this->documentManager = $documentManager; } public function getDocumentManager() { return $this->documentManager; } } <service id="some_service" class="SomeClass"> <argument type="service" id="document_manager" /> </service> Constructor Injection
<?php class SomeClass { private $documentManager; public function setDocumentManager(DocumentManager $documentManager) { $this->documentManager = $documentManager; } public function getDocumentManager() { return $this->documentManager; } } <service id="some_service" class="SomeClass"> <call method="setDocumentManager"> <argument type="service" id="document_manager" /> </call> </service> Setter Injection
<?php interface SomeInterface { function setDocumentManager(DocumentManager $documentManager); } class SomeClass implements SomeInterface { private $documentManager; public function setDocumentManager(DocumentManager $documentManager) { $this->documentManager = $documentManager; } public function getDocumentManager() { return $this->documentManager; } } <interface id="some_service" class="SomeInterface"> <call method="setDocumentManager"> <argument type="service" id="document_manager" /> </call> </interface> <service id="some_service" class="SomeClass" /> Interface Injection
Configuration Choices Symfony supports XML, YAML and PHP for configuration YAML and PHP uses underscore to separate words XML uses dashes to separate words XML attributes usually map to array values for YAML/PHP YAML merge key syntax to reuse pieces within a file XSD-aware editors provide auto-completion and validation XML is recommended for Bundle/DI configuration YAML is recommended for application configuration Doctrine does not like it when you mix formats!
Controller Choices Defining Controllers as services is optional Non-service controllers must use container injection Create a Bundle Extension to load Bundle services It's recommended to not extend from the base Controller The base controller is mainly a tool for beginners It provides convenience methods that invoke services generateUrl(), redirect(), render()
Application Choices Security system makes it possible to have just one application for both frontend and admin backend Location of application is totally flexible, just update the frontend controllers accordingly Large projects should use multiple applications Better separation when multiple teams work Facilitate step-by-step updating and refactoring For example: main, mobile, API, admin
Doctrine Examples Retrieve references to entity/document without DB queries Using raw SQL queries with Doctrine2 ORM Simple search engine with Doctrine MongoDB ODM
Retrieving References w/o DB Queries $tags = array('baseball', 'basketball'); foreach ($tags as $tag) { $product->addTag($em->getReference('Tag', $tag)); }
Raw SQL Queries $rsm = new ResultSetMapping; $rsm->addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); http://www.doctrine-project.org/docs/orm/2.0/en/reference/native-sql.html
Simple Search Engine interface HasKeywords { function setKeywords(); function getKeywords(); }
Simple Search Engine /** @mongodb:EmbeddedDocument */ class Keyword { // ... /** @mongodb:String @mongodb:Index */ private $keyword; /** @mongodb:Integer */ private $score; // ... }
Simple Search Engine /** @mongodb:Document */ class Product implements HasKeywords { /** @mongodb:Id */ private $id; /** @mongodb:String */ private $title; /** @mongodb:EmbedMany(targetDocument="Keyword") */ private $keywords = array(); // ... }
Simple Search Engine class KeywordListener { public function preUpdate(PreUpdateEventArgs $eventArgs) { $entity = $eventArgs->getEntity(); if ($entity instanceof HasKeywords) { $entity->setKeywords($this->buildKeywords($entity)); } } private function buildKeywords(HasKeywords $entity) { $keywords = array(); // build keywords from title, description, etc. return $keywords; } }
Simple Search Engine // find products by keyword $products = $dm->createQueryBuilder() ->field('keywords.keyword')->all($keywords) ->getQuery() ->execute();
Different Content Areas of a Page
Caching with Edge Side Includes Symfony2 provides support for Edge Side Includes (ESI) Proxy assembles page from snippets of HTML Snippets can have different cache rules Develop without ESI, test with Symfony2 internal ESI proxy, deploy using ultra fast Varnish Proxy Break up page into different controller actions based on cache invalidation rules Do not worry about overhead from multiple render calls Never mix content that has different cache timeouts Varnish Reverse Proxy Super fast, PHP cannot touch the performance Cache full pages for anonymous users Not just for HTML, also useful for JSON/XML API
Page Rendered Behind a Reverse Proxy
Performance Tips Dump routes to apache rewrite rules Cache warming Do not add optional services to controllers Do minimal work in the controller, let templates pull additional data as needed Use a bytecode cache with MapFileClassLoader
Asset Management Go see Kris's talk later today!
Testing Symfony2 rocks for unit and functional testing because Dependency Injection No base classes, no static dependencies, no ActiveRecord Client fakes "real" requests for functional testing Functional Testing Pros: Tests configuration, Tests API not implementation Unit Testing Pros: Pinpoints issues, Very directed Testing Recommendation: Functional testing is recommended for controller actions Symfony2 provides WebTestCase and BrowserKit Unit testing for complex algorithms, third party API's too hard to mock Use LiipFunctionalTesting to load fixtures, validate HTML5
Deployment Debian style aka Liip Debian Packager Write a manifest in YAML Build Debian packages with MAKE Install with apt-get install Server specific settings are asked during install, change later with dpkg-reconfigure Maintain a global overview of all application dependencies in case of (security) updates
Third Party Bundles @weaverryan Ryan Weaver Here's a new year's resolution: to *always* work on an existing Symfony2 bundle and never recreate my own. #focus #teamwork 27 Dec http://twitter.com/weaverryan/status/19565706752299009
Third Party Bundles Many vendors have already published bundles: FriendsOfSymfony (http://github.com/friendsofsymfony) UserBundle (forked from knplabs' DoctrineUserBundle) FacebookBundle (forked from kriswallsmith) Liip (http://github.com/liip) FunctionalTestBundle MultiplexBundle ViewBundle Sonata (http://github.com/sonata-project) BaseApplicationBundle Additionally, a few sites currently index community bundles: http://symfony2bundles.org/ http://symfohub.com/
Third Party Bundles Bundles should follow the best practices No version-tagging or official package manager (yet) Use bundles by adding git submodules to your project Maintain your own fork and "own" what you use Not all bundles are equally maintained Symfony2 API changes => broken bundles If you track fabpot/symfony, learn to migrate bundles Avoid rewriting a bundle's services/parameters directly The bundle's DI extension should allow for such configuration; if not, submit a pull request If absolutely necessary, a CompilerPass is cleaner
Contributing to Third Party Bundles Similar to Symfony2's own patch guidlines Fork and add remote repository Merge regularly to keep up-to-date Avoid committing directly to your master Merges from upstream should be fast-forwards Once upstream changes are stable, bump your project's submodule pointer
Contributing to Third Party Bundles Create branches for patches and new features Can't wait to use this in your project? Temporarily change your project submodule to point to your branch until your pull request is accepted. Help ensure that your pull request merges cleanly Create feature branch based on upstream's master Rebase on upstream's master when finished
Contributing to Third Party Bundles Was your pull request accepted? Congratulations! Don't merge your feature branch into master! Doing so would cause your master to divert Merge upstream's master into your master Delete your feature branch Update your project's submodule to point to master
Resources If you want to jump in and contribute: http://docs.symfony-reloaded.org/master/contributing/community/other. html If you are still fuzzy on Dependency Injection: http://fabien.potencier.org/article/11/what-is-dependency-injection If you keep up with Fabien's Symfony2 repository: http://docs.symfony-reloaded.org/master/

Symfony2 from the Trenches

  • 1.
    Symfony2 - Fromthe Trenches by Lukas Kahwe Smith, Kris Wallsmith, Thibault Duplessis, Jeremy Mikola, Jordi Boggiano, Jonathan H. Wage, Bulat Shakirzyanov
  • 2.
    Agenda Getting Setup Caching Code Flow Performance Tips Dependency Injection Asset Management Configuration Choices Testing Controller Choices Deployment Application Choices Third Party Bundles Doctrine Resources
  • 3.
    Getting the works) PHP5.3+ with ext/intl (compat lib is in setup Read check.php for details (dev/prod php.ini's from Liip) Using OSX? php53-intl from liangzhenjing or build-entropy-php from chregu Blog post on installing PHP 5.3 with intl from Justin Hileman Initial setup symfony-sandbox symfony-bootstrap Symfony2Project Read the Coding Style Guide (Code Sniffer Rules) Managing external dependencies Submodule: not everything is in git (svn, mercurial, etc.) Vendor install/update scripts: risk of getting out of sync MR (not cross platform)
  • 4.
    Code Flow 1. FrontendController (web/app[_dev].php) Loads autoloader Creates/boots kernel Creates request (from globals) and passes to kernel 2. Kernel Loads app config (app/config/config_[prod|dev|test]) Resolves URL path to a controller (go to 3.) Outputs response returned by the controller 3. Controller Loads model and view Potentially creates a sub-request (go to 2.) Creates response and returns it
  • 5.
  • 6.
    Dependency Injection All objectsare instantiated in one of two ways: Using the "new" operator Using an object factory All objects get collaborators in one of two ways Passed to object constructor Set using a setter
  • 7.
  • 8.
    <?xml version="1.0" encoding="utf-8"?> <container xmlns="http://www.symfony-project.org/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <parameters> <parameter key="sitemap.class">BundleAvalancheSitemapBundleSitemap</parameter> </parameters> <services> <service id="sitemap" class="%sitemap.class%" /> </services> </container> Describe Your Services
  • 9.
    <?php class cachedDevDebugProjectContainer extendsContainer { /** * Gets the 'sitemap' service. * * @return BundleAvalancheSitemapBundleSitemap */ protected function getSitemapService() { return $this->services['sitemap'] = new BundleAvalancheSitemapBundleSitemap(); } /** * Gets the default parameters. * * @return array An array of the default parameters */ protected function getDefaultParameters() { return array( 'sitemap.class' => 'BundleAvalancheSitemapBundleSitemap' ); } } Service Definitions Are Dumped to Raw PHP
  • 10.
    Dependency Injection Container Benefits: No performance loss Lazy instantiation Readable service configurations Gotchas: Can become hard to work with if the DI extension tries to do too much Be aware of circular dependencies Might lead to code that cannot be used outside of DIC
  • 11.
  • 12.
    <?php class SomeClass { private $container; public function __construct(ContainerInterface $container) <service id="some_service" class="SomeClass"> { <argument type="service" id="service_container" /> $this->container = $container; </service> } <!-- or --> // or <service id="some_service" class="SomeClass"> public function setContainer(ContainerInterface $container) <call method="setContainer"> { <argument type="service" id="service_container" /> $this->container = $container; </call> } </service> public function getDocumentManager() { return $this->container->get('document_manager'); } } Container Injection
  • 13.
    <?php class SomeClass { private $documentManager; public function __construct(DocumentManager $documentManager) { $this->documentManager = $documentManager; } public function getDocumentManager() { return $this->documentManager; } } <service id="some_service" class="SomeClass"> <argument type="service" id="document_manager" /> </service> Constructor Injection
  • 14.
    <?php class SomeClass { private $documentManager; public function setDocumentManager(DocumentManager $documentManager) { $this->documentManager = $documentManager; } public function getDocumentManager() { return $this->documentManager; } } <service id="some_service" class="SomeClass"> <call method="setDocumentManager"> <argument type="service" id="document_manager" /> </call> </service> Setter Injection
  • 15.
    <?php interface SomeInterface { function setDocumentManager(DocumentManager $documentManager); } class SomeClass implements SomeInterface { private $documentManager; public function setDocumentManager(DocumentManager $documentManager) { $this->documentManager = $documentManager; } public function getDocumentManager() { return $this->documentManager; } } <interface id="some_service" class="SomeInterface"> <call method="setDocumentManager"> <argument type="service" id="document_manager" /> </call> </interface> <service id="some_service" class="SomeClass" /> Interface Injection
  • 16.
    Configuration Choices Symfony supportsXML, YAML and PHP for configuration YAML and PHP uses underscore to separate words XML uses dashes to separate words XML attributes usually map to array values for YAML/PHP YAML merge key syntax to reuse pieces within a file XSD-aware editors provide auto-completion and validation XML is recommended for Bundle/DI configuration YAML is recommended for application configuration Doctrine does not like it when you mix formats!
  • 17.
    Controller Choices Defining Controllersas services is optional Non-service controllers must use container injection Create a Bundle Extension to load Bundle services It's recommended to not extend from the base Controller The base controller is mainly a tool for beginners It provides convenience methods that invoke services generateUrl(), redirect(), render()
  • 18.
    Application Choices Security systemmakes it possible to have just one application for both frontend and admin backend Location of application is totally flexible, just update the frontend controllers accordingly Large projects should use multiple applications Better separation when multiple teams work Facilitate step-by-step updating and refactoring For example: main, mobile, API, admin
  • 19.
    Doctrine Examples Retrieve referencesto entity/document without DB queries Using raw SQL queries with Doctrine2 ORM Simple search engine with Doctrine MongoDB ODM
  • 20.
    Retrieving References w/oDB Queries $tags = array('baseball', 'basketball'); foreach ($tags as $tag) { $product->addTag($em->getReference('Tag', $tag)); }
  • 21.
    Raw SQL Queries $rsm= new ResultSetMapping; $rsm->addEntityResult('User', 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'name', 'name'); $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); $query->setParameter(1, 'romanb'); $users = $query->getResult(); http://www.doctrine-project.org/docs/orm/2.0/en/reference/native-sql.html
  • 22.
    Simple Search Engine interfaceHasKeywords { function setKeywords(); function getKeywords(); }
  • 23.
    Simple Search Engine /**@mongodb:EmbeddedDocument */ class Keyword { // ... /** @mongodb:String @mongodb:Index */ private $keyword; /** @mongodb:Integer */ private $score; // ... }
  • 24.
    Simple Search Engine /**@mongodb:Document */ class Product implements HasKeywords { /** @mongodb:Id */ private $id; /** @mongodb:String */ private $title; /** @mongodb:EmbedMany(targetDocument="Keyword") */ private $keywords = array(); // ... }
  • 25.
    Simple Search Engine classKeywordListener { public function preUpdate(PreUpdateEventArgs $eventArgs) { $entity = $eventArgs->getEntity(); if ($entity instanceof HasKeywords) { $entity->setKeywords($this->buildKeywords($entity)); } } private function buildKeywords(HasKeywords $entity) { $keywords = array(); // build keywords from title, description, etc. return $keywords; } }
  • 26.
    Simple Search Engine //find products by keyword $products = $dm->createQueryBuilder() ->field('keywords.keyword')->all($keywords) ->getQuery() ->execute();
  • 27.
  • 28.
    Caching with EdgeSide Includes Symfony2 provides support for Edge Side Includes (ESI) Proxy assembles page from snippets of HTML Snippets can have different cache rules Develop without ESI, test with Symfony2 internal ESI proxy, deploy using ultra fast Varnish Proxy Break up page into different controller actions based on cache invalidation rules Do not worry about overhead from multiple render calls Never mix content that has different cache timeouts Varnish Reverse Proxy Super fast, PHP cannot touch the performance Cache full pages for anonymous users Not just for HTML, also useful for JSON/XML API
  • 29.
    Page Rendered Behinda Reverse Proxy
  • 30.
    Performance Tips Dump routesto apache rewrite rules Cache warming Do not add optional services to controllers Do minimal work in the controller, let templates pull additional data as needed Use a bytecode cache with MapFileClassLoader
  • 31.
    Asset Management Go seeKris's talk later today!
  • 32.
    Testing Symfony2 rocks forunit and functional testing because Dependency Injection No base classes, no static dependencies, no ActiveRecord Client fakes "real" requests for functional testing Functional Testing Pros: Tests configuration, Tests API not implementation Unit Testing Pros: Pinpoints issues, Very directed Testing Recommendation: Functional testing is recommended for controller actions Symfony2 provides WebTestCase and BrowserKit Unit testing for complex algorithms, third party API's too hard to mock Use LiipFunctionalTesting to load fixtures, validate HTML5
  • 33.
    Deployment Debian style akaLiip Debian Packager Write a manifest in YAML Build Debian packages with MAKE Install with apt-get install Server specific settings are asked during install, change later with dpkg-reconfigure Maintain a global overview of all application dependencies in case of (security) updates
  • 34.
    Third Party Bundles @weaverryan Ryan Weaver Here's a new year's resolution: to *always* work on an existing Symfony2 bundle and never recreate my own. #focus #teamwork 27 Dec http://twitter.com/weaverryan/status/19565706752299009
  • 35.
    Third Party Bundles Manyvendors have already published bundles: FriendsOfSymfony (http://github.com/friendsofsymfony) UserBundle (forked from knplabs' DoctrineUserBundle) FacebookBundle (forked from kriswallsmith) Liip (http://github.com/liip) FunctionalTestBundle MultiplexBundle ViewBundle Sonata (http://github.com/sonata-project) BaseApplicationBundle Additionally, a few sites currently index community bundles: http://symfony2bundles.org/ http://symfohub.com/
  • 36.
    Third Party Bundles Bundlesshould follow the best practices No version-tagging or official package manager (yet) Use bundles by adding git submodules to your project Maintain your own fork and "own" what you use Not all bundles are equally maintained Symfony2 API changes => broken bundles If you track fabpot/symfony, learn to migrate bundles Avoid rewriting a bundle's services/parameters directly The bundle's DI extension should allow for such configuration; if not, submit a pull request If absolutely necessary, a CompilerPass is cleaner
  • 37.
    Contributing to ThirdParty Bundles Similar to Symfony2's own patch guidlines Fork and add remote repository Merge regularly to keep up-to-date Avoid committing directly to your master Merges from upstream should be fast-forwards Once upstream changes are stable, bump your project's submodule pointer
  • 38.
    Contributing to ThirdParty Bundles Create branches for patches and new features Can't wait to use this in your project? Temporarily change your project submodule to point to your branch until your pull request is accepted. Help ensure that your pull request merges cleanly Create feature branch based on upstream's master Rebase on upstream's master when finished
  • 39.
    Contributing to ThirdParty Bundles Was your pull request accepted? Congratulations! Don't merge your feature branch into master! Doing so would cause your master to divert Merge upstream's master into your master Delete your feature branch Update your project's submodule to point to master
  • 40.
    Resources If you wantto jump in and contribute: http://docs.symfony-reloaded.org/master/contributing/community/other. html If you are still fuzzy on Dependency Injection: http://fabien.potencier.org/article/11/what-is-dependency-injection If you keep up with Fabien's Symfony2 repository: http://docs.symfony-reloaded.org/master/