symfony2 - from the trenches
DESCRIPTION
This talk represents the combined experience from several web development teams who have been using Symfony2 since months already to create high profile production applications. The aim is to give the audience real world advice on how to best leverage Symfony2, the current rough spots and how to work around them. Aside from covering how to implement functionality in Symfony2, this talk will also cover topics such as how to best integrate 3rd party bundles and where to find them as well as how to deploy the code and integrate into the entire server setup.TRANSCRIPT
![Page 1: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/1.jpg)
Symfony2 - From the Trenches
by Lukas Kahwe Smith,Kris Wallsmith,
Thibault Duplessis,Jeremy Mikola,Jordi Boggiano,
Jonathan H. Wage,Bulat Shakirzyanov
![Page 2: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/2.jpg)
Agenda
Getting SetupCode FlowDependency InjectionConfiguration ChoicesController ChoicesApplication ChoicesDoctrine
CachingPerformance TipsAsset ManagementTestingDeploymentThird Party BundlesResources
![Page 3: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/3.jpg)
Getting setupPHP 5.3+ with ext/intl (compat lib is in the works)
Read check.php for details (dev/prod php.ini's from Liip)Using OSX?
php53-intl from liangzhenjing or build-entropy-php from chreguBlog post on installing PHP 5.3 with intl from Justin Hileman
Initial setupsymfony-sandboxsymfony-bootstrapSymfony2Project
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 syncMR (not cross platform)
![Page 4: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/4.jpg)
Code Flow1. Frontend Controller (web/app[_dev].php)
Loads autoloaderCreates/boots kernelCreates request (from globals) and passes to kernel
2. KernelLoads app config (app/config/config_[prod|dev|test])Resolves URL path to a controller (go to 3)Outputs response returned by the controller
3. ControllerLoads model and viewPotentially creates a sub-request (go to 2)Creates response and returns it
![Page 5: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/5.jpg)
Execution Flow
![Page 6: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/6.jpg)
Describe Your Services
<?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">Bundle\Avalanche\SitemapBundle\Sitemap</parameter> </parameters>
<services> <service id="sitemap" class="%sitemap.class%" /> </services>
</container>
![Page 7: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/7.jpg)
Service Definitions Are Dumped to Raw PHP
<?phpclass cachedDevDebugProjectContainer extends Container{ /** * Gets the 'sitemap' service. * * @return Bundle\Avalanche\SitemapBundle\Sitemap */ protected function getSitemapService() { return $this->services['sitemap'] = new \Bundle\Avalanche\SitemapBundle\Sitemap(); }
/** * Gets the default parameters. * * @return array An array of the default parameters */ protected function getDefaultParameters() { return array( 'sitemap.class' => 'Bundle\Avalanche\SitemapBundle\Sitemap' ); }}
![Page 8: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/8.jpg)
Service Container (aka DIC)
Benefits:No performance lossLazy instantiationReadable service configurations
Gotchas:Can become hard to work with if the DI extension tries to do too muchBe aware of circular dependenciesMight lead to code that cannot be used outside of DIC
![Page 9: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/9.jpg)
Container Injection
<?phpclass SomeClass{ private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } // or public function setContainer(ContainerInterface $container) { $this->container = $container; }
public function getDocumentManager() { return $this->container->get('document_manager'); }}
<service id="some_service" class="SomeClass"> <argument type="service" id="service_container" /></service><!-- or --><service id="some_service" class="SomeClass"> <call method="setContainer"> <argument type="service" id="service_container" /> </call></service>
![Page 10: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/10.jpg)
Constructor Injection
<?phpclass 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>
![Page 11: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/11.jpg)
Setter Injection
<?phpclass 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>
![Page 12: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/12.jpg)
Interface Injection
<?phpinterface 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" />
![Page 13: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/13.jpg)
Configuration Choices
Symfony supports XML, YAML and PHP for configurationYAML and PHP uses underscore to separate wordsXML uses dashes to separate wordsXML attributes usually map to array values in YAML/PHPYAML merge key syntax to reuse pieces within a fileXSD-aware editors provide auto-completion/validation
XML is recommended for Bundle/DI configurationYAML is recommended for application configurationBundle extensions can optionally utilize the Config component to normalize/parse configurations in any format
See FrameworkExtension, SecurityExtension, TwigExtension
![Page 14: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/14.jpg)
Controller Choices
Defining Controllers as services is optionalNon-service controllers must use container injectionCreate a Bundle extension to load Bundle services
It's recommended to not extend from the base ControllerThe base controller is mainly a tool for beginnersIt provides convenience methods that invoke services, such as generateUrl(), redirect(), render()
![Page 15: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/15.jpg)
Application Choices
Security system makes it possible to have just one application for both frontend and admin backendLocation of AppKernel is totally flexible, just update the frontend controllers accordinglyLarge projects should use multiple applications
Better separation when multiple teams workFacilitate step-by-step updating and refactoringFor example: main, mobile, API, admin
![Page 16: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/16.jpg)
Doctrine Examples
Retrieve references to entity/document without DB queriesUsing raw SQL queries with Doctrine2 ORMSimple search engine with Doctrine MongoDB ODM
![Page 17: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/17.jpg)
Retrieving References w/o DB Queries
$tags = array('baseball', 'basketball');foreach ($tags as $tag) { $product->addTag($em->getReference('Tag', $tag));}
![Page 18: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/18.jpg)
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
![Page 19: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/19.jpg)
Simple Search Engine
interface HasKeywords{ function setKeywords(); function getKeywords();}
![Page 20: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/20.jpg)
Simple Search Engine/** @mongodb:EmbeddedDocument */class Keyword{ // ...
/** @mongodb:String @mongodb:Index */ private $keyword;
/** @mongodb:Integer */ private $score;
// ...}
![Page 21: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/21.jpg)
Simple Search Engine/** @mongodb:Document */class Product implements HasKeywords{ /** @mongodb:Id */ private $id;
/** @mongodb:String */ private $title;
/** @mongodb:EmbedMany(targetDocument="Keyword") */ private $keywords = array();
// ...}
![Page 22: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/22.jpg)
Simple Search Engineclass 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; }}
![Page 23: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/23.jpg)
Simple Search Engine
// find products by keyword$products = $dm->createQueryBuilder() ->field('keywords.keyword')->all($keywords) ->getQuery() ->execute();
![Page 24: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/24.jpg)
Doctrine in the Real World
Go see Jon Wage's talk at later today!
![Page 25: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/25.jpg)
Database Migrations
Deploy DB schema changes before the codePrevent DB schema BC breaksUse DoctrineMigrationBundle
app/console doctrine:migrations:diffapp/console doctrine:migrations:migrateDo not use entities in migration scripts ever!
Write fixtures as migrations or make the fixtures able to update existing data gracefully
app/console doctrine:data:load --fixtures=app/fixtures
![Page 26: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/26.jpg)
Different Content Areas of a Page
![Page 27: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/27.jpg)
Caching with Edge Side Includes
Symfony2 provides support for Edge Side Includes (ESI)Proxy assembles page from snippets of HTMLSnippets can have different cache rulesDevelop 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 callsNever mix content that has different cache timeoutsConsider caching user specific content in the client
Varnish Reverse ProxySuper fast, PHP cannot match its performanceCache full pages for anonymous usersNot just for HTML, also useful for JSON/XML API's
![Page 28: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/28.jpg)
Page Rendered Behind a Reverse Proxy
![Page 29: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/29.jpg)
Asset Management
Go see Kris Wallsmith's talk next!
![Page 30: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/30.jpg)
Performance Tips
Dump routes to Apache rewrite rulesapp/console router:dump-apache
Write custom cache warmersDo not explicitly inject optional services to controllers
If your controller receives many services, which are optional or unused by some actions, that's probably a hint that you should break it up into multiple controllers
Do minimal work in the controller, let templates pull additional data as neededUse a bytecode cache with MapFileClassLoader
![Page 31: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/31.jpg)
Testing
Symfony2 rocks for unit and functional testingDependency Injection, core classes have interfaces (easy mocking)No base classes, no static dependencies, no ActiveRecordClient fakes "real" requests for functional testing (BrowserKit component)
Functional TestingPros: tests configuration, tests API not implementation
Unit TestingPros: pinpoints issues, very directed testinghttp://www.slideshare.net/avalanche123/clean-code-5609451
Recommendation:Functional testing is recommended for controller actions
Symfony2 provides WebTestCase and BrowserKitUnit testing for complex algorithms, third party API's too hard to mock
Use Liip\FunctionalTesting to load fixtures, validate HTML5
![Page 32: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/32.jpg)
Deployment
Debian style (aka Liip Debian Packager)Write a manifest in YAMLBuild Debian packages with MAKEInstall with apt-get installServer specific settings are asked during install, change later with dpkg-reconfigureMaintain a global overview of all application dependencies in case of (security) updatesWatch Lukas' unconference talk later today!
Fabric (used at OpenSky)Repository managed with git-flowClone tagged release branch, init submodulesFix permissions (e.g. cache, log), delete dev/test controllersReplace password/API-key placeholders with prod values in config filesUpload in parallel to production nodes, swap "current" symlink
![Page 33: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/33.jpg)
Third Party Bundles
Here's a new year's resolution: to *always* work on an existing Symfony2 bundle and never recreate my own. #focus #teamwork
@weaverryanRyan Weaver
27 Dec http://twitter.com/weaverryan/status/19565706752299009
![Page 34: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/34.jpg)
Third Party BundlesMany vendors have already published bundles:
FriendsOfSymfony (http://github.com/friendsofsymfony)UserBundle (forked from knplabs' DoctrineUserBundle)FacebookBundle (forked from kriswallsmith)
Liip (http://github.com/liip)FunctionalTestBundleViewBundle
OpenSky (http://github.com/opensky)LdapBundle
Sonata (http://github.com/sonata-project)AdminBundle
Additionally, a couple sites currently index community bundles:
http://symfony2bundles.org/ http://symfohub.com/
![Page 35: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/35.jpg)
Third Party Bundles
Bundles should follow the best practicesNo version-tagging or official package manager (yet)Use bundles by adding git submodules to your projectMaintain your own fork and "own" what you useNot all bundles are equally maintained
Symfony2 API changes => broken bundlesIf you track symfony/symfony, learn to migrate bundles
Avoid rewriting a bundle's services/parameters directlyThe bundle's DI extension should allow for such configuration; if not, submit a pull requestIf absolutely necessary, a CompilerPass is cleaner
![Page 36: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/36.jpg)
Contributing to Third Party Bundles
Similar to Symfony2's own patch guidlinesFork and add remote repositoryMerge regularly to keep up-to-date
Avoid committing directly to your masterMerges from upstream should be fast-forwards
Once upstream changes are stable, bump your project's submodule pointer
![Page 37: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/37.jpg)
Contributing to Third Party Bundles
Create branches for patches and new featuresCan'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 cleanlyCreate feature branch based on upstream's master Rebase or merge upstream's master when finished
![Page 38: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/38.jpg)
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 masterDelete your feature branchUpdate your project's submodule to point to master
![Page 39: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/39.jpg)
Resources
If you want to jump in and contribute:http://docs.symfony-reloaded.org/master/contributing/community/other.htmlIf you are still fuzzy on Dependency Injection:http://fabien.potencier.org/article/11/what-is-dependency-injectionIf you keep up with Symfony2 repository on github:http://docs.symfony-reloaded.org/master/
![Page 40: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/40.jpg)
![Page 41: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/41.jpg)
Application Using Dependency Injection
![Page 42: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/42.jpg)
Circular Dependency Example
![Page 43: Symfony2 - from the trenches](https://reader034.vdocuments.net/reader034/viewer/2022052307/554f7757b4c9052a518b4817/html5/thumbnails/43.jpg)
Dependency Injection
All objects are instantiated in one of two ways:Using the "new" operatorUsing an object factory
All objects get collaborators in one of two waysPassed to object constructorSet using a setter