2. 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
3. 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
4. Fabien Potencier
• Read my technical blog: http://fabien.potencier.org/
• Follow me on Twitter: @fabpot
• Fork my code on Github: http://github.com/fabpot/
5. How many of you have used symfony?
1.0? 1.1? 1.2? 1.3?
6. 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, …
7. symfony 1.2 – November 2008
• Decoupled but cohesive components: the symfony platform
– Forms, Routing, Cache, YAML, ORMs, …
• Controller still based on Mojavi
– View, Filter Chain, …
8. 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
• Version 2.0 – Hopefully at the end of 2010
9. Symfony 2.0
• PHP 5.3 only - http://sf-to.org/sf20onPHP53
• Same Symfony core components (updated for PHP 5.3)
• Different controller implementation
• Oh! Symfony now takes a capital S!!!
11. 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
14. 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
• Other new components will be released as standalone components:
– Controller Handling
– Output Escaping will be
migrated to
– Forms
PHP 5.3 soon
– Routing
– Templating Framework (done)
– Dependency Injection Container (done)
27. Symfony 2 core is so light and flexible
that its raw performance
is outstanding
28. require_once dirname(__FILE__).'/sf20/autoload2/sfCore2Autoload.class.php';
sfCore2Autoload::register();
Hello World
$app = new HelloApplication();
$app->run()->send();
0
class HelloApplication wit h Symfony 2.
{
public function __construct()
{
$this->dispatcher = new sfEventDispatcher();
$this->dispatcher->connect('application.load_controller', array($this, 'loadController'));
}
public function run()
OUTDATED
{
$request = new sfWebRequest($this->dispatcher);
$handler = new sfRequestHandler($this->dispatcher);
$response = $handler->handle($request);
return $response;
}
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;
}
29. require __DIR__.'/lib/Symfony/Core/ClassLoader.php';
use SymfonyComponentsEventDispatcherEvent;
Hello World
use SymfonyComponentsEventDispatcherEventDispatcher;
use SymfonyComponentsRequestHandlerRequestHandler;
ymfony 2.0
use SymfonyComponentsRequestHandlerRequest;
use SymfonyComponentsRequestHandlerResponse;
with S
$classLoader = new ClassLoader('Symfony', __DIR__.'/lib');
$classLoader->register(); on PHP 5.3
$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();
30. 7922&
7822& !"#$%&!'!& Hello World
Benchmark
()$*+&
7022&
7722&
7222&
>22&
=22&
<22& Symfony 2.0 on PHP 5.3
;22&
:22&
,-./+%-&012&
x 10
922&
x 3 x 7
822&
022&
3+"#4&
722& 56& ,-./+%-&710&
2&
based on numbers from http://paul-m-jones.com/?p=315
31. 10 times faster?!
You won’t have such a difference for real applications
as most of the time, the limiting factor
is not the framework itself
32. … and PHP 5.3 helps
• The Hello World code on PHP 5.3 is 50% faster than the one on PHP 5.2
(for the same code!)
• All frameworks will benefit from some speed improvements
• Again, real applications won’t have such a speed difference just by
upgrading to PHP 5.3
!
Upgrad e to PHP 5.3
e
It mean s performanc
improveme nts for FREE
33. 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 different optimizations:
• One full-stack framework optimized for ease of use (think symfony 1)
• One light framework optimized for speed (think Rails Metal ;))
39. EventDispatcher
• Observer Design Pattern
• Based on Cocoa Notification 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 defined by a unique string
• « Anybody » can listen to any event
• You can notify existing events or create new ones easily
40. require __DIR__.'/lib/Symfony/Core/ClassLoader.php';
use SymfonyComponentsEventDispatcherEvent;
use SymfonyComponentsEventDispatcherEventDispatcher;
$classLoader = new ClassLoader('Symfony', __DIR__.'/lib');
$classLoader->register();
$dispatcher = new EventDispatcher();
$listener = function ($event)
{
echo sprintf("Hello %s!n", $event['name']);
};
$dispatcher->connect('foo', $listener); Can be any PHP callable
$event = new Event(null, 'foo', array('name' => 'Fabien'));
$dispatcher->notify($event);
42. The Request Handler
• The backbone of Symfony 2 controller implementation
• Class to build web frameworks, not only MVC ones
• Based on a simple assumption:
– The input is a Request object
– The output is a Response object
• The Request object can be anything you want
• The Response object must implement a send() method
43. The Request Handler
use SymfonyComponentsRequestHandlerRequestHandler;
use SymfonyComponentsRequestHandlerRequest;
$handler = new RequestHandler($dispatcher);
$request = new Request();
$response = $handler->handle($request);
$response->send();
44. The Request Handler
• The RequestHandler does several things:
– Notifies events
– Executes a callable (the controller)
– Ensures that the Request is converted to a Response object
• The framework is responsible for choosing the controller
• The controller is responsible for the conversion of the Request to a
Response
45. namespace SymfonyComponentsRequestHandler;
use SymfonyComponentsEventDispatcherEvent;
use SymfonyComponentsEventDispatcherEventDispatcher;
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
RequestHandler is
$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.');
}
less than 100 lines
throw $e;
}
}
public function handleRaw(RequestInterface $request)
{
of PHP code!
// 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;
}
}
49. core.request
• The very first event notified
• It can act as a short-circuit event
• If one listener returns a Response object, it stops the processing
51. 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
55. core.view
The value returned
$dispatcher->connect('core.view',
by the controller
function (Event $event, $value)
{
if (is_string($value))
{ The view “convert” the
return new Response($value); controller returned value to a
} Response if it’s a string
return $value;
}
If not, it just returns the
);
original value and it lets
another view handle it if
needed
57. 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
return true; “nice” message to the user
}
);
58. Request Handler
• Several listeners can be attached to a single event
• Listeners are called in turn
59. require __DIR__.'/lib/Symfony/Core/ClassLoader.php';
Hello World
use SymfonyComponentsEventDispatcherEvent;
use SymfonyComponentsEventDispatcherEventDispatcher;
use SymfonyComponentsRequestHandlerRequestHandler;
with
use SymfonyComponentsRequestHandlerRequest;
ts
use SymfonyComponentsRequestHandlerResponse;
Symfony 2.0 Componen
$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();
60. require __DIR__.'/lib/Symfony/Core/ClassLoader.php';
use SymfonyComponentsEventDispatcherEvent;
use SymfonyComponentsEventDispatcherEventDispatcher;
use SymfonyComponentsRequestHandlerRequestHandler;
use SymfonyComponentsRequestHandlerRequest;
use SymfonyComponentsRequestHandlerResponse;
$classLoader = new ClassLoader('Symfony', __DIR__.'/lib');
$classLoader->register();
61. Autoloading in PHP 5.3
• Will be hopefully standardized
for major/all PHP 5.3 frameworks/libraries
• Main goal:
– provide a better interoperability between PHP frameworks/libraries
• A group of people is discussing this “standard” (nothing decided yet)
http://groups.google.com/group/php-standards
62. Autoloading in PHP 5.3
• Current proposal
http://groups.google.com/group/php-standards/web/php-coding-
standard-version-2
• Used for this presentation examples
• Tested by real-world projects:
– Doctrine 2
– Lithium
– Symfony 2 Components
63. $dispatcher = new EventDispatcher();
$dispatcher->connect(
'core.load_controller',
$controllerLoader
);
64. $controllerLoader = function (Event $event)
{
$event->setReturnValue(array(
$controller, Can be any PHP callable
array($event['request'])
);
} Arguments to pass
to the controller
65. Arguments passed
to the controller
$controller = function (Request $request)
{
$name = $request->getQueryParameter('name');
return new Response('Hello '.$name);
};
A controller should
return a Response
66. $request = new Request();
$request->setQueryParameters(array('name' => 'Fabien'));
$handler = new RequestHandler($dispatcher);
$response = $handler->handle($request);
$response->send();
Sends the response
to the browser
70. Template Loaders
• No assumption about where and how templates are to be found
– Filesystem
– Database
– Memory, …
• Template names are « logical » names:
$loader = new FilesystemLoader('/path/to/templates/%name%.php');
71. Template Renderers
• No assumption about the format of the templates
• Template names are prefixed with the renderer name:
– index == php:index
– user:index
$t = new Engine($loader, array(
'user' => new ProjectTemplateRenderer(),
'php' => new PhpRenderer(),
));
77. CMS Templating
• Imagine a CMS with the following features:
– The CMS comes bundled with default templates
– The developer can override default templates for a specific project
– The webmaster can override some templates
• The CMS and developer templates are stored on the filesystem 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 }}
78. 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/ project/templates/
base.php base.php
articles/ articles/
layout.php layout.php
article.php article.php
content.php
93. « Dependency Injection is where components
are given their dependencies through their
constructors, methods, or directly into fields. »
http://www.picocontainer.org/injection.html
94. The Symfony 2 dependency injection container
replaces several symfony 1 concepts
into one integrated system:
– sfContext
– sfConfiguration
– sfConfig
– factories.yml
– settings.yml / logging.yml / i18n.yml
– … and some more
95. DI Hello World example
class Message
{
public function __construct(OutputInterface $output, array $options)
{
$this->output = $output;
$this->options = array_merge(array('with_newline' => false), $options);
}
public function say($msg)
{
$this->output->render($msg.($this->options['with_newline'] ? "n" : ''));
}
}
96. DI Hello World example
interface OutputInterface
{
public function render($msg);
}
class Output implements OutputInterface
{
public function render($msg)
{
echo $msg;
}
}
class FancyOutput implements OutputInterface
{
public function render($msg)
{
echo sprintf("033[33m%s033[0m", $msg);
}
}
97. DI Hello World example
$output = new FancyOutput();
$message = new Message($output, array('with_newline' => true));
$message->say('Hello World');
98. A DI container facilitates
objects description and object relationships,
configures and instantiates objects
99. DI Container Hello World example
require __DIR__.'/lib/Symfony/Core/ClassLoader.php';
use SymfonyComponentsDependencyInjectionBuilder;
use SymfonyComponentsDependencyInjectionReference;
$classLoader = new ClassLoader('Symfony', __DIR__.'/lib');
$classLoader->register();
$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!');
100. $message = $container->message;
Get the configuration 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
101. $message = $container->message;
is roughly equivalent to
$output = new FancyOutput();
$message = new Message($output, array('with_newline' => true));!
102. $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
<container xmlns="http://symfony-project.org/2.0/container">
<services>
<service id="output" class="FancyOutput" /> XML
<service id="message" class="Message"> XML is validated
<argument type="service" id="output" />
<argument type="collection">
against an XSD
<argument key="with_newline">true</argument>
</argument>
</service>
</services>
</container>
$container = new Builder();
$loader = new XmlFileLoader($container);
$loader->load('services.xml');