Documente Academic
Documente Profesional
Documente Cultură
Fabien Potencier
Fabien Potencier
Serial entrepreneur and developer by passion Founder of Sensio (in 1998)
A services and consulting company specialized in Web technologies and Internet marketing (France and USA) 70 people Open-Source specialists Big corporate customers Consulting, training, development, web design, and more Sponsor of a lot of Open-Source projects like symfony and Doctrine
Fabien Potencier
Creator and lead developer of symfony and creator and lead developer of some more:
symfony components Swift Mailer : Powerful component based mailing library for PHP Twig : Fexible, fast, and secure template language for PHP Pirum : Simple PEAR Channel Server Manager Sismo : PHP continuous integration server Lime : Easy to use unit testing library for PHP Twitto : A web framework in a tweet Twittee : A Dependency Injection Container in a tweet Pimple : A small PHP 5.3 dependency injection container
Fabien Potencier
Read my technical blog: http://fabien.potencier.org/ Follow me on Twitter: @fabpot Fork my code on Github: http://github.com/fabpot/
How many of you have used symfony? 1.0? 1.1? 1.2? 1.3?
Roadmap
1.0 January 2007 1.1 June 2008 1.2 November 2008 1.3 November 2009 1.4 Last 1.X version (LTS release : 3 years of support)
same as 1.3 but without deprecated features
Symfony 2.0
PHP 5.3 only - http://sf-to.org/sf20onPHP53 Same Symfony core components (updated for PHP 5.3) Dierent controller implementation Oh! Symfony now takes a capital S!!!
Symfony Components
Standalone components Packaged individually Upcoming dedicated website
http://components.symfony-project.org/
Dedicated section for each component Dedicated documentation Dedicated Subversion repository
http://svn.symfony-project.com/components/
Git mirror
http://github.com/fabpot
Symfony Components
Existing components
will soon be released as 1.0 (for PHP 5.2) will then be migrated to PHP 5.3 (mainly introducing namespaces)
New components:
will work with PHP 5.3 only right from the start
Symfony 2 core
Symfony 2
Obviously not yet available as a full-stack MVC framework Some components have already been merged into Symfony 1
Event Dispatcher Form Framework
symfony 1 is one of the slowest framework when you test it against a simple Hello World application
1644& 1544& 1344& 1144& Hello World 1444& Benchmark ;44& :44& 944& 844& 744& 644& 544& 344& 144& 4& ()"#*& +,& !"#$%&!'!&
x 19.5
x 2.3
-./0)%.&123&
Conclusion?
Dont use symfony for your next Hello World website Use PHP ;)
By the way, the fastest implemention of a Hello World application with PHP:
die('Hello World');
Second most popular video sharing website One of the top 30 websites in the world 59 million unique users in July 2009
Symfony 2 Faster?
Symfony 2 core is so light and exible that its raw performance is outstanding
class HelloApplication { public function __construct() { $this->dispatcher = new sfEventDispatcher(); $this->dispatcher->connect('application.load_controller', array($this, 'loadController')); } public function run() { $request = new sfWebRequest($this->dispatcher); $handler = new sfRequestHandler($this->dispatcher); $response = $handler->handle($request); return $response; }
OUTDATED
public function loadController(sfEvent $event) { $event->setReturnValue(array(array($this, 'hello'), array($this->dispatcher, $event['request']))); return true; } public function hello($dispatcher, $request) { $response = new sfWebResponse($dispatcher); $response->setContent('Hello World'); return $response; }
require __DIR__.'/lib/Symfony/Core/ClassLoader.php'; use use use use use Symfony\Components\EventDispatcher\Event; Symfony\Components\EventDispatcher\EventDispatcher; Symfony\Components\RequestHandler\RequestHandler; Symfony\Components\RequestHandler\Request; Symfony\Components\RequestHandler\Response;
$dispatcher = new EventDispatcher(); $dispatcher->connect('core.load_controller', function (Event $event) { $event->setReturnValue(array( function (Request $request) { return new Response('Hello '.$request->getQueryParameter('name')); }, array($event['request']) )); return true; }); $request = new Request(); $request->setQueryParameters(array('name' => 'Fabien')); $handler = new RequestHandler($dispatcher); $response = $handler->handle($request); $response->send();
7922& 7822& 7022& 7722& 7222& >22& =22& <22& ;22& :22& 922& 822& 022& 722& 2& ,-./+%-&012&
Symfony 2.0 on PHP 5.3
!"#$%&!'!&
()$*+&
x 10 x 3 x 7
10 times faster?!
You wont have such a dierence for real applications as most of the time, the limiting factor is not the framework itself
10 times faster?!
Raw speed matters because
It demonstrates that the core kernel is very light It allows you to use several Symfony frameworks within a single application with the same behavior but dierent optimizations:
One full-stack framework optimized for ease of use (think symfony 1) One light framework optimized for speed (think Rails Metal ;))
\
the new PHP developer best friend
Namespaces
from sfRequest to Symfony\Core\Request use Symfony\Core\Request; $request = new Request();
Anonymous Functions
$a = function ($msg) { echo $msg; } $a('Hello World');
EventDispatcher
Observer Design Pattern Based on Cocoa Notication Center
// sfI18N $callback = array($this, 'listenToChangeCultureEvent'); $dispatcher->connect('user.change_culture', $callback); // sfUser $event = new Event($this, 'user.change_culture', array('culture' => $culture)); $dispatcher->notify($event);
I18N and User are decoupled An event is dened by a unique string Anybody can listen to any event You can notify existing events or create new ones easily
require __DIR__.'/lib/Symfony/Core/ClassLoader.php'; use Symfony\Components\EventDispatcher\Event; use Symfony\Components\EventDispatcher\EventDispatcher; $classLoader = new ClassLoader('Symfony', __DIR__.'/lib'); $classLoader->register(); $dispatcher = new EventDispatcher(); $listener = function ($event) { echo sprintf("Hello %s!\n", $event['name']); }; Can be any PHP callable $dispatcher->connect('foo', $listener); $event = new Event(null, 'foo', array('name' => 'Fabien')); $dispatcher->notify($event);
The Request object can be anything you want The Response object must implement a send() method
The framework is responsible for choosing the controller The controller is responsible for the conversion of the Request to a Response
namespace Symfony\Components\RequestHandler; use Symfony\Components\EventDispatcher\Event; use Symfony\Components\EventDispatcher\EventDispatcher; class RequestHandler { protected $dispatcher = null; public function __construct(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; } public function handle(RequestInterface $request) { try { return $this->handleRaw($request); } catch (\Exception $e) { // exception $event = $this->dispatcher->notifyUntil(new Event($this, 'core.exception', array('request' => $request, 'exception' => $e))); if ($event->isProcessed()) { return $this->filterResponse($event->getReturnValue(), 'An "core.exception" listener returned a non response object.'); } throw $e; } } public function handleRaw(RequestInterface $request) { // request $event = $this->dispatcher->notifyUntil(new Event($this, 'core.request', array('request' => $request))); if ($event->isProcessed()) { return $this->filterResponse($event->getReturnValue(), 'An "core.request" listener returned a non response object.'); } // load controller $event = $this->dispatcher->notifyUntil(new Event($this, 'core.load_controller', array('request' => $request))); if (!$event->isProcessed()) { throw new \LogicException('Unable to load the controller.'); } list($controller, $arguments) = $event->getReturnValue(); // controller must be a callable if (!is_callable($controller)) { throw new \LogicException(sprintf('The controller must be a callable (%s).', var_export($controller, true))); } // controller $event = $this->dispatcher->notifyUntil(new Event($this, 'core.controller', array('request' => $request, 'controller' => &$controller, 'arguments' => &$arguments))); if ($event->isProcessed()) { try { return $this->filterResponse($event->getReturnValue(), 'An "core.controller" listener returned a non response object.'); } catch (\Exception $e) { $retval = $event->getReturnValue(); } } else { // call controller $retval = call_user_func_array($controller, $arguments); } // view $event = $this->dispatcher->filter(new Event($this, 'core.view'), $retval); return $this->filterResponse($event->getReturnValue(), sprintf('The controller must return a response (instead of %s).', is_object($event->getReturnValue()) ? 'an object of class '.get_class($event->getReturnValue()) : (string) $event->getReturnValue())); } protected function filterResponse($response, $message) { if (!$response instanceof ResponseInterface) { throw new \RuntimeException($message); } $event = $this->dispatcher->filter(new Event($this, 'core.response'), $response); $response = $event->getReturnValue(); if (!$response instanceof ResponseInterface) { throw new \RuntimeException('An "core.response" listener returned a non response object.'); } return $response; } public function getEventDispatcher() { return $this->dispatcher; } }
core.response
As the very last event notied, a listener can modify the Response object just before it is returned to the user
core.response
$dispatcher->connect('core.response', function (Event $event, Response $response) { $response->setContent( $response->getContent(). 'The Web Debug Toolbar' ); return $response; } );
Content of the response is changed
core.request
The very rst event notied It can act as a short-circuit event If one listener returns a Response object, it stops the processing
core.request
$dispatcher->connect('core.request', function (Event $event) { $event->setReturnValue( new Response('Website unavailable...') );
Dont go any further!
return true; } );
core.load_controller
The only event for which at least one listener must be connected to A listener must return
A PHP callable (the controller) The arguments to pass to the callable
core.load_controller
$dispatcher->connect('core.load_controller', function (Event $event) { $event->setReturnValue(array( function () { return new Response('Hello World'); }, array() Whatever the request, )); always return Hello World return true; } );
core.view
The controller must return a Response object except if a listener can convert the controller returned value to a Response
core.view
$dispatcher->connect('core.load_controller', function (Event $event) { $event->setReturnValue(array( function () { return 'Hello World'; }, array() )); The controller returns a string, return true; } ); not a Response
core.view
$dispatcher->connect('core.view', function (Event $event, $value) { if (is_string($value)) { return new Response($value); } return $value; } ); If not, it just returns the original value and it lets another view handle it if needed The value returned by the controller
The view convert the controller returned value to a Response if its a string
core.exception
The request handler catches all exceptions and give a chance to listeners to return a Response object
core.exception
$dispatcher->connect('core.exception', function (Event $event) { $event->setReturnValue( new Response( 'An error occurred: '.$event['exception']->getMessage() ) ); Mask the exception and return a nice message to the user return true; } );
Request Handler
Several listeners can be attached to a single event Listeners are called in turn
require __DIR__.'/lib/Symfony/Core/ClassLoader.php'; use use use use use Symfony\Components\EventDispatcher\Event; Symfony\Components\EventDispatcher\EventDispatcher; Symfony\Components\RequestHandler\RequestHandler; Symfony\Components\RequestHandler\Request; Symfony\Components\RequestHandler\Response;
$classLoader = new ClassLoader('Symfony', __DIR__.'/lib'); $classLoader->register(); $dispatcher = new EventDispatcher(); $dispatcher->connect('core.load_controller', function (Event $event) { $event->setReturnValue(array( function (Request $request) { return new Response('Hello '.$request->getQueryParameter('name')); }, array($event['request']) )); return true; }); $request = new Request(); $request->setQueryParameters(array('name' => 'Fabien')); $handler = new RequestHandler($dispatcher); $response = $handler->handle($request); $response->send();
require __DIR__.'/lib/Symfony/Core/ClassLoader.php';
use use use use use Symfony\Components\EventDispatcher\Event; Symfony\Components\EventDispatcher\EventDispatcher; Symfony\Components\RequestHandler\RequestHandler; Symfony\Components\RequestHandler\Request; Symfony\Components\RequestHandler\Response;
$controllerLoader = function (Event $event) { $event->setReturnValue(array( Can be any PHP callable $controller, array($event['request']) ); } Arguments to pass
to the controller
$controller = function (Request $request) { $name = $request->getQueryParameter('name'); return new Response('Hello '.$name); };
A controller should return a Response
$request = new Request(); $request->setQueryParameters(array('name' => 'Fabien')); $handler = new RequestHandler($dispatcher); $response = $handler->handle($request); $response->send();
Sends the response to the browser
Independant library
require __DIR__.'/lib/Symfony/Core/ClassLoader.php'; use Symfony\Components\Templating\Engine; use Symfony\Components\Templating\Loader\FilesystemLoader; $classLoader = new ClassLoader('Symfony', __DIR__.'/lib'); $classLoader->register(); $loader = new FilesystemLoader( '/path/to/templates/%name%.php' ); $t = new Engine($loader); echo $t->render('index', array('name' => 'Fabien'));
Template Loaders
No assumption about where and how templates are to be found
Filesystem Database Memory,
Template Renderers
No assumption about the format of the templates Template names are prexed with the renderer name:
index == php:index user:index
$t = new Engine($loader, array( 'user' => new ProjectTemplateRenderer(), 'php' => new PhpRenderer(), ));
Template Embedding
Hello <?php echo $name ?> <?php $this->render('embedded', array('name' => $name)) ?> <?php $this->render('smarty:embedded') ?>
Template Inheritance
<?php $this->extend('layout') ?> Hello <?php echo $name ?> <html> <head> </head> <body> <?php $this->output('content') ?> </body> </html>
Template Slots
<html> <head> <title><?php $this->output('title') ?></title> </head> <body> <?php $this->output('content') ?> </body> </html> <?php $this->set('title', 'Hello World! ') ?> <?php $this->start('title') ?> Hello World! <?php $this->stop() ?>
A layout can be decorated by another layout Each layout can override slots
CMS Templating
Imagine a CMS with the following features:
The CMS comes bundled with default templates The developer can override default templates for a specic project The webmaster can override some templates
The CMS and developer templates are stored on the lesystem and are written with pure PHP code The webmaster templates are stored in a database and are written in a simple templating language: Hello {{ name }}
CMS Templating
The CMS has several built-in sections and pages
Each page is decorated by a layout, depending on the section Each section layout is decorated by a base layout cms/templates/ base.php articles/ layout.php article.php project/templates/ base.php articles/ layout.php article.php content.php
articles/content.php
articles/article.php
<?php $this->extend('articles/layout') ?> <?php $this->set('title', $title) ?> <?php echo $this->render( 'user:articles/content', array('title' => $title, 'content' => $content) ) ?>
articles/layout.php
<?php $this->extend ('base') ?> <?php $this->set('title', 'Articles | '.$this->get('title')) ?> <?php $this->start('head') ?> <?php $this->output('head') ?> <link rel="stylesheet" type="text/css" media="all" href="/css/ articles.css" /> <?php $this->stop() ?> <?php $this->output('content') ?>
articles/layout.php
<?php $this->extend('base') ?> <?php $this->set('title', 'Articles | '.$this->get('title')) ?> <?php $this->stylesheets->add('/css/articles.css') ?> <?php $this->output('content') ?>
base.php
<html> <head> <title> <?php $this->output('title') ?> </title> <?php $this->output('head') ?> </head> <body> <?php $this->output('content') ?> </body> </html>
Template Renderer
$t = new Engine($loader, array( 'user' => new ProjectTemplateRenderer(), 'php' => new PhpRenderer(), ));
Template Renderer
use Symfony\Components\Templating\Renderer\Renderer;
class ProjectTemplateRenderer extends Renderer { public function evaluate(Storage $template, array $parameters = array()) { if ($template instanceof FileStorage) { $template = file_get_contents($template); } $this->parameters = $parameters; return preg_replace_callback('/{{\s*(.+?)\s*}}/', array($this, 'replaceParameters'), $template); } public function replaceParameters($matches) { return isset($this->parameters[$matches[1]]) ? $this->parameters[$matches[1]] : null; } }
Template Loaders
$loader = new ChainLoader(array( new ProjectTemplateLoader($pdo), new FilesystemLoader(array( '/path/to/project/templates/%name%.php', '/path/to/cms/templates/%name%.php', )), ));
$pdo = new \PDO('sqlite::memory:'); $pdo->exec('CREATE TABLE tpl (name, tpl)'); $pdo->exec('INSERT INTO tpl (name, tpl) VALUES ("articles/ content", "{{ title }} {{ name }}")');
public function load($template, $renderer = 'php') { $stmt = $this->pdo->prepare('SELECT tpl FROM tpl WHERE name = :name'); try { $stmt->execute(array('name' => $template)); if (count($rows = $stmt->fetchAll(PDO::FETCH_NUM))) { return $rows[0][0]; } } catch (\PDOException $e) { } return false; } }
$pdo = new \PDO('sqlite::memory:'); $pdo->exec('CREATE TABLE tpl (name, tpl)'); $pdo->exec('INSERT INTO tpl (name, tpl) VALUES ("articles/content", "{{ title }} {{ name }}")'); $loader = new CacheLoader( new ChainLoader(array( new ProjectTemplateLoader($pdo), new FilesystemLoader(array( '/path/to/project/templates/%name%.php', '/path/to/cms/templates/%name%.php', )), )), 'path/to/cache' ); $t = new Engine($loader, array( 'user' => new ProjectTemplateRenderer() )); $t->render('articles/article', array('title' => 'Title', 'content' => 'Lorem...'));
Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into elds.
http://www.picocontainer.org/injection.html
The Symfony 2 dependency injection container replaces several symfony 1 concepts into one integrated system:
sfContext sfConguration sfCong factories.yml settings.yml / logging.yml / i18n.yml and some more
$output = new FancyOutput(); $message = new Message($output, array('with_newline' => true)); $message->say('Hello World');
A DI container facilitates objects description and object relationships, congures and instantiates objects
$message = $container->message;
Get the conguration for the message service The Message constructor must be given an output service Get the output object from the container Create a Message object by passing the constructor arguments
$message = $container->message;
is roughly equivalent to
$output = new FancyOutput(); $message = new Message($output, array('with_newline' => true));!
$container = new Builder(); $container->register('output', 'FancyOutput'); $container-> register('message', 'Message')-> setArguments(array(new Reference('output'), array('with_newline' => true))) ; $container->message->say('Hello World!');
PHP XML
XML is validated against an XSD
<container xmlns="http://symfony-project.org/2.0/container"> <services> <service id="output" class="FancyOutput" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument type="collection"> <argument key="with_newline">true</argument> </argument> </service> </services> </container>
$container = new Builder(); $loader = new XmlFileLoader($container); $loader->load('services.xml');
$container = new Builder(); $container->register('output', 'FancyOutput'); $container-> register('message', 'Message')-> setArguments(array(new sfServiceReference('output'), array('with_newline' => true))) ; $container->message->say('Hello World!');
PHP YAML
services: output: { class: FancyOutput } message: class: Message arguments: - @output - { with_newline: true }
$container = new Builder(); $loader = new YamlFileLoader($container); $loader->load('services.yml');
<container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="output.class">FancyOutput</parameter> <parameter key="message.options" type="collection"> <parameter key="with_newline">true</parameter> </parameter> </parameters> <services> <service id="output" class="%output.class%" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument>%message.options%</argument> </service> </services> </container> $container = new rBuilder(); $loader = new XmlFileLoader($container); $loader->load('services.xml');
<container xmlns="http://symfony-project.org/2.0/container"> <imports> <import resource="config.xml" /> </imports> <services> <service id="output" class="%output.class%" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument>%message.options%</argument> </service> </services> </container> <container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="output.class">FancyOutput</parameter> <parameter key="message.options" type="collection"> <parameter key="with_newline">true</parameter> </parameter> </parameters> </container> $container = new Builder(); $loader = new FileXmlFileLoader($container); $loader->load('services.xml');
<services> <import resource="config.yml" class="Symfony\Components\DependencyInjection\Loader \YamlFileLoader" /> <service id="output" class="%output.class%" /> <service id="message" class="Message"> <argument type="service" id="output" /> <argument>%message.options%</argument> </service> </services> parameters: output.class: FancyOutput message.options: { with_newline: true } $container = new Builder(); $loader = new XmlFileLoader($container); $loader->load('services.xml');
$pdo = new \PDO('sqlite::memory:'); $pdo->exec('CREATE TABLE tpl (name, tpl)'); $pdo->exec('INSERT INTO tpl (name, tpl) VALUES ("articles/content", "{{ title }} {{ name }}")'); $loader = new CacheLoader( new ChainLoader(array( new ProjectTemplateLoader($pdo), new FilesystemLoader(array( '/path/to/project/templates/%name%.php', '/path/to/cms/templates/%name%.php', )), )), 'path/to/cache' ); $t = new Engine($loader, array( 'user' => new ProjectTemplateRenderer() )); $t->render('articles/article', array('title' => 'Title', 'content' => 'Lorem...'));
$container = new Builder(); $loader = new FileXmlLoader($container); $loader->load('cms.xml'); $pdo->exec('CREATE TABLE tpl (name, tpl)'); $pdo->exec('INSERT INTO tpl (name, tpl) VALUES ("articles/content", "{{ title }} {{ name }}")'); echo $container->template->render('articles/article', array('title' => 'Title', 'content' => 'Lorem...'));
<container xmlns="http://symfony-project.org/2.0/container">
<imports> <import resource="config.yml" class="Symfony\Components\DependencyInjection\Loader \YamlFileLoader" /> <import resource="template_loader.xml" /> </imports> <services> <service id="pdo" class="PDO"> <argument>sqlite::memory:</argument> </service> <service id="template_renderer" class="ProjectTemplateRenderer" /> <service id="template" class="Symfony\Components\Templating\Engine"> <argument type="service" id="template_loader" /> <argument type="collection"> <argument type="service" key="user" id="template_renderer" /> </argument> </service> </services> </container>
<services> <service id="template_loader_project" class="ProjectTemplateLoader"> <argument type="service" key="pdo" id="pdo" /> </service> <service id="template_loader_filesystem" class="Symfony\Components\Templating\Loader \FilesystemLoader"> <argument>%template.filesystem_pattern%</argument> </service> <service id="template_loader_chain" class="Symfony\Components\Templating\Loader\ChainLoader"> <argument type="collection"> <argument type="service" id="template_loader_project" /> <argument type="service" id="template_loader_filesystem" /> </argument> </service> <service id="template_loader" class="Symfony\Components\Templating\Loader\CacheLoader"> <argument type="service" id="template_loader_chain" /> <argument>%application.dir%/cache</argument> </service> </services>
<services> <service id="template_loader" class="Symfony\Components\Templating\Loader\CacheLoader"> <argument type="service"> <service class="Symfony\Components\Templating\Loader\ChainLoader"> <argument type="collection"> <argument type="service"> <service class="ProjectTemplateLoader"> <argument type="service" key="pdo" id="pdo" /> </service> </argument> <argument type="service"> <service class="Symfony\Components\Templating\Loader\FilesystemLoader"> <argument>%template.filesystem_patterns%</argument> </service> </argument> </argument> </service> </argument> <argument>%application.dir%/cache</argument> </service> </services>
symfony-live.com
symfony-live.com
Questions?
Sensio S.A. 92-98, boulevard Victor Hugo 92 115 Clichy Cedex FRANCE Tl. : +33 1 40 99 80 80 Contact Fabien Potencier fabien.potencier at sensio.com