Symfony2 - From the Trenches
by Lukas Kahwe Smith,
Kris Wallsmith,
Thibault Duplessis,
Jeremy Mikola,
Jordi Boggiano,
Jonathan H. Wage,
Bulat Shakirzyanov
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
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
<?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
<?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
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
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
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/