You are on page 1of 135

Fabien Potencier

A bit of history
symfony 1.0 – January 2007
•  Started as a glue between existing Open-Source libraries:
– Mojavi (heavily modified), Propel, Prado i18n, …
•  Borrowed concepts from other languages and frameworks:
– Routing, CLI, functional tests, YAML, Rails helpers…
•  Added new concepts to the mix
– Web Debug Toolbar, admin generator, configuration cascade, …
symfony 1.2 – November 2008
•  Based on decoupled but cohesive components

–  Forms, Routing, Cache, YAML, ORMs, …

•  Controller still based on Mojavi

–  View, Filter Chain, …


symfony 1.4 – November 2009
•  Added some polish on existing features

•  Removed the support for deprecated features

•  Current LTS release, maintained until late 2012


Symfony Components
YAML
Dependency Injection Container
Event Dispatcher
Templating
Routing
Console
Output Escaper
Request Handler

What is Symfony 2?
Symfony 2 is the next version
of the symfony framework…

except Symfony now takes a S instead of a s


Talk about
Symfony 2
or
symfony 1
To make it clear:
Symfony 1
does not make any sense
symfony 2
does not make more sense
Symfony 2
Same philosophy,
just better
MVC
hmmm,
now that I think about it…
…it’s now probably more
a Fabien’s style framework
than anything else
Highly configurable
Highly extensible
Same Symfony Components
Same great developer tools
Full-featured
Ok, but why a major version then?
Symfony 2 has
a brand new
low-level architecture
PHP 5.3
A Quick Tour
<?php

require_once __DIR__.'/../blog/BlogKernel.php';

$kernel = new BlogKernel('prod', false);


$kernel->run();
<?php Everything is namespaced
namespace Application\HelloBundle\Controller;

use Symfony\Framework\WebBundle\Controller;

class HelloController extends Controller


{
public function indexAction($name) Variables come from the routing
{
return $this->render('HelloBundle:Hello:index', array('name' => $name));
}
}
Template name Variables to pass
to the template
Layout

<?php $view->extend('HelloBundle::layout') ?>

Hello <?php echo $name ?>!


<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
</head>
<body>
<?php $view->slots->output('_content') ?>
</body>
</html> Helpers are objects
hello:
pattern: /hello/:name
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index
hello:
pattern: /hello/:name
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index

namespace Application\HelloBundle\Controller;

class HelloController extends Controller


{
public function indexAction($name)
{
// ...
}
}
hello:
pattern: /hello/:name
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index

namespace Application\HelloBundle\Controller;

class HelloController extends Controller


{
public function indexAction($name)
{
// ...
}
}
hello:
pattern: /hello/:name
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index

namespace Application\HelloBundle\Controller;

class HelloController extends Controller


{
public function indexAction($name)
{
// ...
}
}
hello:
pattern: /hello/:name
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index

namespace Application\HelloBundle\Controller;

class HelloController extends Controller


{
public function indexAction($name)
{
// ...
}
}
hello:
pattern: /hello/:name
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index

namespace Application\HelloBundle\Controller;

class HelloController extends Controller


{
public function indexAction($name)
{
// ...
}
}
hello:
pattern: /hello/:year/:month/:slug
defaults:
_bundle: HelloBundle
_controller: Hello
_action: index

namespace Application\HelloBundle\Controller;

class HelloController extends Controller


{
public function indexAction($slug, $year)
{
// ...
}
}
Extremely
Configurable
Dependency
Injection
Container
Replaces a lot of symfony 1 “things”
sfConfig
All config handlers
sfProjectConfiguration /
sfApplicationConfiguration
sfContext (No Singleton anymore)
The configuration cache system
… and some more
in one
easy-to-master
unified
and cohesive package
Thanks to the DIC,
Configuration has never been
so easy and so flexible
Name your configuration files
the way you want
Store them where you want
Use PHP, XML, YAML, or INI
$configuration = new BuilderConfiguration();
$configuration->addResource(new FileResource(__FILE__));

$configuration
->mergeExtension('web.user',
array('default_culture' => 'fr', 'session' => array('name' => 'SYMFONY',
'type' => 'Native', 'lifetime' => 3600)))

->mergeExtension('doctrine.dbal',
array('dbname' => 'sfweb', 'username' => 'root'))

->mergeExtension('web.templating',
array('escaping' => 'htmlspecialchars', 'assets_version' =>
'SomeVersionScheme'))

->mergeExtension('swift.mailer',
array('transport' => 'gmail', 'username' => 'fabien.potencier',
'password' => 'xxxxxx')) PHP  
;
web.user:
default_culture: fr
session: { name: SYMFONY, type: Native, lifetime: 3600 }

web.templating:
escaping: htmlspecialchars
assets_version: SomeVersionScheme

doctrine.dbal: { dbname: sfweb, username: root, password: null }

swift.mailer:
transport: gmail
username: fabien.potencier
password: xxxxxxxx
YAML  
<web:user default_culture="fr">
<web:session name="SYMFONY" type="Native" lifetime="3600" />
</web:user>

<web:templating
escaping="htmlspecialchars"
assets_version="SomeVersionScheme" />

<doctrine:dbal dbname="sfweb" username="root" password="" />

<swift:mailer
transport="gmail"
username="fabien.potencier"
password="xxxxxxxx" />
XML  
$configuration->mergeExtension('swift.mailer', array(
'transport' => 'gmail',
'username' => 'fabien.potencier',
'password' => 'xxxxxx',
));

PHP  
swift.mailer:
transport: gmail
username: fabien.potencier
password: xxxxxxxx

YAML  
<swift:mailer
transport="gmail"
username="fabien.potencier"
password="xxxxxxxx" />

XML  
<?xml version="1.0" ?>

<container xmlns="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance »
xmlns:doctrine="http://www.symfony-project.org/schema/dic/doctrine"
xmlns:zend="http://www.symfony-project.org/schema/dic/zend"
xmlns:swift="http://www.symfony-project.org/schema/dic/swiftmailer"
>

XML  
<?xml version="1.0" ?>

<container xmlns="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance »
xmlns:doctrine="http://www.symfony-project.org/schema/dic/doctrine"
xmlns:zend="http://www.symfony-project.org/schema/dic/zend"
xmlns:swift="http://www.symfony-project.org/schema/dic/swiftmailer"
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services
http://www.symfony-project.org/schema/dic/services/services-1.0.xsd
http://www.symfony-project.org/schema/dic/doctrine
http://www.symfony-project.org/schema/dic/doctrine/doctrine-1.0.xsd
http://www.symfony-project.org/schema/dic/zend http://
www.symfony-project.org/schema/dic/zend/zend-1.0.xsd
http://www.symfony-project.org/schema/dic/swiftmailer
http://www.symfony-project.org/schema/dic/swiftmailer/swiftmailer-1.0.xsd »>

XML  
Inherit them as much as you want
Mix and match
configuration files written in any
format
useful when using third-party plugins
<imports>
<import resource="parent.xml" />
<import resource="config.yml" />
Mix and match formats
<import resource="parameters.ini" />
</imports>

<zend:logger
priority="debug"
path="%kernel.logs_dir%/%kernel.environment%.log"
/>

<web:debug
exception="%kernel.debug%"
toolbar="%kernel.debug%"
ide="textmate"
/>
You choose the format you want

Pros Cons
XML validation verbose (not that much)
IDE completion & help

YAML concise needs the YAML component


simple to read no validation
easy to change no IDE auto-completion
PHP flexible no validation
more expressive
Store sensitive settings
outside of your project
<doctrine:dbal
dbname="sfweb"
username="root"
password="SuperSecretPasswordThatAnyoneCanSee"
/>
in a .htaccess or httpd.conf file

SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "foobar"


%doctrine.dbal.password%
Semantic
Configuration
<swift:mailer
transport="gmail"
username="fabien.potencier"
password="xxxxxxxx" />

XML  
<swift:mailer
transport="smtp"
encryption="ssl"
auth_mode="login"
host="smtp.gmail.com"
username="fabien.potencier"
password="xxxxxxxx" />
XML  
<parameters>

<parameter key="swiftmailer.class">Swift_Mailer</parameter>
<parameter key="swiftmailer.transport.smtp.class">Swift_Transport_EsmtpTransport</parameter>

<parameter key="swiftmailer.transport.smtp.host">smtp.gmail.com</parameter>
<parameter key="swiftmailer.transport.smtp.port">25</parameter>
<parameter key="swiftmailer.transport.smtp.encryption">ssl</parameter>
<parameter key="swiftmailer.transport.smtp.username">fabien.potencier</parameter>
<parameter key="swiftmailer.transport.smtp.password">xxxxxx</parameter>
<parameter key="swiftmailer.transport.smtp.auth_mode">login</parameter>
<parameter key="swiftmailer.init_file">swift_init.php</parameter>
</parameters>

<services>

<service id="swiftmailer.mailer" class="%swiftmailer.class%">


<argument type="service" id="swiftmailer.transport" />
<file>%swiftmailer.init_file%</file>
</service>
<service id="swiftmailer.transport.smtp" class="%swiftmailer.transport.smtp.class%">
<argument type="service" id="swiftmailer.transport.buffer" />
<argument type="collection">
<argument type="service" id="swiftmailer.transport.authhandler" />
</argument>
<argument type="service" id="swiftmailer.transport.eventdispatcher" />

<call method="setHost"><argument>%swiftmailer.transport.smtp.host%</argument></call>
<call method="setPort"><argument>%swiftmailer.transport.smtp.port%</argument></call>
<call method="setEncryption"><argument>%swiftmailer.transport.smtp.encryption%</argument></call>
<call method="setUsername"><argument>%swiftmailer.transport.smtp.username%</argument></call>
<call method="setPassword"><argument>%swiftmailer.transport.smtp.password%</argument></call>
<call method="setAuthMode"><argument>%swiftmailer.transport.smtp.auth_mode%</argument></call>
</service>

<service id="swiftmailer.transport.buffer" class="Swift_Transport_StreamBuffer">


<argument type="service" id="swiftmailer.transport.replacementfactory" />
</service>

<service id="swiftmailer.transport.authhandler" class="Swift_Transport_Esmtp_AuthHandler">


<argument type="collection">
<argument type="service"><service class="Swift_Transport_Esmtp_Auth_CramMd5Authenticator" /></argument>
<argument type="service"><service class="Swift_Transport_Esmtp_Auth_LoginAuthenticator" /></argument>
<argument type="service"><service class="Swift_Transport_Esmtp_Auth_PlainAuthenticator" /></argument>
</argument>

XML  
</service>

<service id="swiftmailer.transport.eventdispatcher" class="Swift_Events_SimpleEventDispatcher" />

<service id="swiftmailer.transport.replacementfactory" class="Swift_StreamFilters_StringReplacementFilterFactory" />

<service id="swiftmailer.transport" alias="swiftmailer.transport.smtp" />


</services>
Creating DIC extensions
is insanely simple
Very Fast
thanks to a Smart
Caching mechanism
it always knows when to flush the cache
/**
* Gets the 'swiftmailer.mailer' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return Swift_Mailer A Swift_Mailer instance.
PHPDoc for auto-completion
*/
protected function getSwiftmailer_MailerService()
{
if (isset($this->shared['swiftmailer.mailer']))
return $this->shared['swiftmailer.mailer']; As fast as it could be
$instance = new Swift_Mailer($this->getSwiftmailer_Transport_SmtpService());

return $this->shared['swiftmailer.mailer'] = $instance;


}
The DIC can manage
ANY PHP object (POPO)
Plugins…
or Bundles
Plugins are first-class citizens
They are called Bundles
Everything is a bundle
Core features
Third-party code
Application code
app/
src/
web/
app/
AppKernel.php
cache/
config/
console
logs/
src/
autoload.php
Application/
Bundle/
vendor/
doctrine/
swiftmailer/
symfony/
zend/
web/
index.php
index_dev.php
.../
SomeBundle/
Bundle.php
Controller/
Model/
Resources/
config/
views/
public function registerBundleDirs()
{
return array(
'Application' => __DIR__.'/../src/Application',
'Bundle' => __DIR__.'/../src/Bundle',
'Symfony\\Framework' => __DIR__.'/../src/vendor/
symfony/src/Symfony/Framework',
);
}
$this->render('SomeBundle:Hello:index', $params)
hello:
pattern: /hello/:name
defaults: { _bundle: SomeBundle, ... }
SomeBundle can be any of

Application\SomeBundle
Bundle\SomeBundle
Symfony\Framework\SomeBundle
Less concepts…
but more powerful ones
symfony 1 View Layer
templates
layouts
slots
components
partials
component slots
Symfony 2 View Layer

templates
slots
A layout is just another template with _content as a special slot

A partial is just a template you embed in another one

A component is just another action embedded in a template


<?php $view->output('BlogBundle:Post:list', array('posts'
=> $posts)) ?>
<?php $view->actions->output('BlogBundle:Post:list', array
('limit' => 2)) ?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
</head>
<body>
<?php $view->slots->output('_content') ?>
</body>
</html>
Big and Small
Improvements
multiple level of layouts
partials can be decorated!
Better
Logs
INFO: Matched route "blog_home" (parameters: array ( '_bundle' =>
'BlogBundle', '_controller' => 'Post', '_action' => 'index', '_route' =>
'blog_home',))

INFO: Using controller "Bundle\BlogBundle\Controller


\PostController::indexAction"

INFO: SELECT s0_.id AS id0, s0_.title AS title1, s0_.html_body AS html_body2,


s0_.excerpt AS excerpt3, s0_.published_at AS published_at4 FROM sf_weblog_post
s0_ ORDER BY s0_.published_at DESC LIMIT 10 (array ())
INFO: Matched route "blog_post" (parameters: array ( '_bundle' =>
'BlogBundle', '_controller' => 'Post', '_action' => 'show', '_format' =>
'html', 'id' => '3456', '_route' => 'blog_post',))

INFO: Using controller "Bundle\BlogBundle\Controller


\PostController::showAction »

INFO: SELECT s0_.id AS id0, s0_.title AS title1, s0_.html_body AS html_body2,


s0_.excerpt AS excerpt3, s0_.published_at AS published_at4 FROM sf_weblog_post
s0_ WHERE s0_.id = ? (array ( 0 => '3456',))
ERR: Post "3456" not found! (No result was found for query although at least
one row was expected.) (uncaught Symfony\Components\RequestHandler\Exception
\NotFoundHttpException exception)

INFO: Using controller "Symfony\Framework\WebBundle\Controller


\ExceptionController::exceptionAction"
<zend:logger priority="debug" />
DEBUG: Notifying (until) event "core.request" to listener "(Symfony\Framework\WebBundle\Listener\RequestParser, resolve)"
INFO: Matched route "blog_post" (parameters: array ( '_bundle' => 'BlogBundle', '_controller' => 'Post', '_action' => 'show',
'_format' => 'html', 'id' => '3456', '_route' => 'blog_post',))
DEBUG: Notifying (until) event "core.load_controller" to listener "(Symfony\Framework\WebBundle\Listener\ControllerLoader,
resolve)"
INFO: Using controller "Bundle\BlogBundle\Controller\PostController::showAction"
DEBUG: Listener "(Symfony\Framework\WebBundle\Listener\ControllerLoader, resolve)" processed the event "core.load_controller"
INFO: Trying to get post "3456" from database
INFO: SELECT s0_.id AS id0, s0_.title AS title1, s0_.html_body AS html_body2, s0_.excerpt AS excerpt3, s0_.published_at AS
published_at4 FROM sf_weblog_post s0_ WHERE s0_.id = ? (array ( 0 => '3456',))
DEBUG: Notifying (until) event "core.exception" to listener "(Symfony\Framework\WebBundle\Listener\ExceptionHandler, handle)"
ERR: Post "3456" not found! (No result was found for query although at least one row was expected.) (uncaught Symfony\Components
\RequestHandler\Exception\NotFoundHttpException exception)
DEBUG: Notifying (until) event "core.request" to listener "(Symfony\Framework\WebBundle\Listener\RequestParser, resolve)"
DEBUG: Notifying (until) event "core.load_controller" to listener "(Symfony\Framework\WebBundle\Listener\ControllerLoader,
resolve)"
INFO: Using controller "Symfony\Framework\WebBundle\Controller\ExceptionController::exceptionAction"
DEBUG: Listener "(Symfony\Framework\WebBundle\Listener\ControllerLoader, resolve)" processed the event "core.load_controller"
DEBUG: Notifying (filter) event "core.response" to listener "(Symfony\Framework\WebBundle\Listener\ResponseFilter, filter)"
DEBUG: Notifying (filter) event "core.response" to listener "(Symfony\Framework\WebBundle\Debug\DataCollector
\DataCollectorManager, handle)"
DEBUG: Notifying (filter) event "core.response" to listener "(Symfony\Framework\WebBundle\Debug\WebDebugToolbar, handle)"
DEBUG: Listener "(Symfony\Framework\WebBundle\Listener\ExceptionHandler, handle)" processed the event "core.exception"
DEBUG: Notifying (filter) event "core.response" to listener "(Symfony\Framework\WebBundle\Listener\ResponseFilter, filter)"
DEBUG: Notifying (filter) event "core.response" to listener "(Symfony\Framework\WebBundle\Debug\DataCollector
\DataCollectorManager, handle)"
DEBUG: Notifying (filter) event "core.response" to listener "(Symfony\Framework\WebBundle\Debug\WebDebugToolbar, handle)"
Even Better
Exception Error Pages
An Event Better
Web Debug Toolbar
Everything you need is
at the bottom of the screen
Web Designer
“friendly”
app/
views/
BlogBundle/
Post/
index.php
AdminGeneratorBundle/
DefaultTheme/
list.php
edit.php
...
“Mount” Routing
Configuration
blog:
resource: BlogBundle/Resources/config/routing.yml

forum:
resource: ForumBundle/Resources/config/routing.yml
prefix: /forum
Symfony 2
is a lazy framework
Smart Autoloading
require_once __DIR__.'/vendor/symfony/src/Symfony/Foundation/UniversalClassLoader.php';

use Symfony\Foundation\UniversalClassLoader;

$loader = new UniversalClassLoader();


$loader->registerNamespaces(array(
'Symfony' => __DIR__.'/vendor/symfony/src',
'Application' => __DIR__,
'Bundle' => __DIR__,
'Doctrine' => __DIR__.'/vendor/doctrine/lib',
));
$loader->registerPrefixes(array(
'Swift_' => __DIR__.'/vendor/swiftmailer/lib/classes',
'Zend_' => __DIR__.'/vendor/zend/library',
));
$loader->register();

// for Zend Framework & SwiftMailer


set_include_path(__DIR__.'/vendor/zend/library'.PATH_SEPARATOR.__DIR__.'/vendor/
swiftmailer/lib'.PATH_SEPARATOR.get_include_path());
lazy-loading of services
lazy-loading of listeners
lazy-loading of helpers
<?php echo $view->router->generate('blog_post', array('id'
=> $post->getId())) ?>
Symfony 2
is a “cachy” framework
blog/
cache/
prod/
blogProjectContainer.php
blogUrlGenerator.php
blogUrlMatcher.php
classes.php
class blogUrlMatcher extends Symfony\Components\Routing\Matcher\UrlMatcher
{
public function __construct(array $context = array(), array $defaults = array())
{
$this->context = $context;
$this->defaults = $defaults;
}

public function match($url)


{
$url = $this->normalizeUrl($url);

if (0 === strpos($url, '/webblog') && preg_match('#^/webblog/(?


P<id>[^/\.]+?)$#x', $url, $matches))
return array_merge($this->mergeDefaults($matches, array
( '_bundle' => 'WebBundle', '_controller' => 'Redirect', '_action'
=> 'redirect', 'route' => 'blog_post',)), array('_route' =>
'old_blog_post_redirect'));
You can use Apache
for Routing matching
A Very Fast
Dev. Env.
blog/
cache/
dev/
blogProjectContainer.meta
blogProjectContainer.php
blogUrlGenerator.meta
blogUrlGenerator.php
blogUrlMatcher.meta
blogUrlMatcher.php
classes.meta
classes.php
prod/
blogProjectContainer.php
blogUrlGenerator.php
blogUrlMatcher.php
classes.php
Symfony 2
Easy to learn
Easy to use
Extensible at will
Easy to learn
Easy to use
Extensible at will
But Symfony 2 should be slow, right?
Fast as hell
Benchmark
on a simple application
2x faster
than
Solar 1.0.0
2.5x faster
than
symfony 1.4.2
3x faster
than
Zend Framework 1.10
4x faster
than
Lithium
6x faster
than
CakePHP 1.2.6
60x faster
than
Flow3
…and Symfony 2.0 uses
half the memory
needed by both symfony 1 and ZF
We have barely scratched the surface
of all the goodness of
Symfony 2.0
Controller except for the nice default pages
Autoloading
Cache via ZF - DI extension coming soon
CLI commands still missing
Configuration
Database via Doctrine DBAL
Debug except Timer and extended WDT
Escaper
Event Dispatcher
Form / Validation / Widget can use the 1.4 version as is
Admin Generator
Helpers
I18n / L10n can use the 1.4 version as is
Logger via ZF
Mailer except commands
Bundles except installing
Doctrine Plugin just the DBAL part
Propel Plugin
Request / Response
Routing no REST support, no Object support
Storage / User
Test
View
Final Release Target Date
Late 2010
If you want the bleeding edge of news, follow me

on Twitter @fabpot
on Github github.com/fabpot

http://symfony-reloaded.org/
Questions?
My slides will be available on
http://slideshare.com/fabpot
Sensio S.A.
92-98, boulevard Victor Hugo
92 115 Clichy Cedex
FRANCE
Tél. : +33 1 40 99 80 80

Contact
Fabien Potencier
fabien.potencier at sensio.com

http://www.sensiolabs.com/
http://www.symfony-project.org/
http://fabien.potencier.org/

You might also like