Symfony2 from the Trenches

40
Symfony2 - From the Trenches by Lukas Kahwe Smith, Kris Wallsmith, Thibault Duplessis, Jeremy Mikola, Jordi Boggiano, Jonathan H. Wage, Bulat Shakirzyanov

description

Symfony2 from the Trenches presentation at Symfony Live 2011

Transcript of Symfony2 from the Trenches

Page 1: Symfony2 from the Trenches

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

Agenda

Getting SetupCode FlowDependency InjectionConfiguration ChoicesController ChoicesApplication ChoicesDoctrine

CachingPerformance TipsAsset ManagementTestingDeploymentThird Party BundlesResources

Page 3: Symfony2 from the Trenches

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

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

Execution Flow

Page 6: Symfony2 from the Trenches

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

Page 7: Symfony2 from the Trenches

Application Using Dependency Injection

Page 8: Symfony2 from the Trenches

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 9: Symfony2 from the Trenches

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 10: Symfony2 from the Trenches

Dependency Injection Container

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 11: Symfony2 from the Trenches

Circular Dependency Example

Page 12: Symfony2 from the Trenches

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 13: Symfony2 from the Trenches

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 14: Symfony2 from the Trenches

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 15: Symfony2 from the Trenches

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 16: Symfony2 from the Trenches

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 for YAML/PHPYAML merge key syntax to reuse pieces within a fileXSD-aware editors provide auto-completion and validationXML is recommended for Bundle/DI configurationYAML is recommended for application configurationDoctrine does not like it when you mix formats!

Page 17: Symfony2 from the Trenches

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

generateUrl(), redirect(), render()

Page 18: Symfony2 from the Trenches

Application Choices

Security system makes it possible to have just one application for both frontend and admin backendLocation of application 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 19: Symfony2 from the Trenches

Doctrine Examples

Retrieve references to entity/document without DB queriesUsing raw SQL queries with Doctrine2 ORMSimple search engine with Doctrine MongoDB ODM

Page 20: Symfony2 from the Trenches

Retrieving References w/o DB Queries

$tags = array('baseball', 'basketball');foreach ($tags as $tag) { $product->addTag($em->getReference('Tag', $tag));}

Page 21: Symfony2 from the Trenches

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 22: Symfony2 from the Trenches

Simple Search Engine

interface HasKeywords{ function setKeywords(); function getKeywords();}

Page 23: Symfony2 from the Trenches

Simple Search Engine/** @mongodb:EmbeddedDocument */class Keyword{ // ...

/** @mongodb:String @mongodb:Index */ private $keyword;

/** @mongodb:Integer */ private $score;

// ...}

Page 24: Symfony2 from the Trenches

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 25: Symfony2 from the Trenches

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 26: Symfony2 from the Trenches

Simple Search Engine

// find products by keyword$products = $dm->createQueryBuilder() ->field('keywords.keyword')->all($keywords) ->getQuery() ->execute();

Page 27: Symfony2 from the Trenches

Different Content Areas of a Page

Page 28: Symfony2 from the Trenches

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 timeouts

Varnish Reverse ProxySuper fast, PHP cannot touch the performanceCache full pages for anonymous usersNot just for HTML, also useful for JSON/XML API

Page 29: Symfony2 from the Trenches

Page Rendered Behind a Reverse Proxy

Page 30: Symfony2 from the Trenches

Performance Tips

Dump routes to apache rewrite rulesCache warmingDo not add optional services to controllersDo minimal work in the controller, let templates pull additional data as neededUse a bytecode cache with MapFileClassLoader

Page 31: Symfony2 from the Trenches

Asset Management

Go see Kris's talk later today!

Page 32: Symfony2 from the Trenches

Testing

Symfony2 rocks for unit and functional testing becauseDependency InjectionNo base classes, no static dependencies, no ActiveRecordClient fakes "real" requests for functional testing

Functional TestingPros: Tests configuration, Tests API not implementation

Unit TestingPros: Pinpoints issues, Very directed Testing

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 33: Symfony2 from the Trenches

Deployment

Debian style aka Liip Debian PackagerWrite 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) updates

Page 34: Symfony2 from the Trenches

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 35: Symfony2 from the Trenches

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)FunctionalTestBundleMultiplexBundleViewBundle

Sonata (http://github.com/sonata-project)BaseApplicationBundle

Additionally, a few sites currently index community bundles:

http://symfony2bundles.org/ http://symfohub.com/

Page 36: Symfony2 from the Trenches

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 fabpot/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 37: Symfony2 from the Trenches

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 38: Symfony2 from the Trenches

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 on upstream's master when finished

Page 39: Symfony2 from the Trenches

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 40: Symfony2 from the Trenches

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 Fabien's Symfony2 repository:http://docs.symfony-reloaded.org/master/