Upload
fabien-potencier
View
35.807
Download
0
Embed Size (px)
DESCRIPTION
Symfony2 revealed
Citation preview
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
namespace Application\HelloBundle\Controller;
use Symfony\Framework\WebBundle\Controller;
class HelloController extends Controller { public function indexAction($name) { return $this->render('HelloBundle:Hello:index', array('name' => $name)); } }
Everything is namespaced
Template name Variables to pass to the template
Variables come from the routing
<?php $view->extend('HelloBundle::layout') ?>
Hello <?php echo $name ?>!
Layout
<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" /> <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" />
Mix and match formats
You choose the format you want
Pros Cons XML validation
IDE completion & help verbose (not that much)
YAML concise simple to read easy to change
needs the YAML component no validation no IDE auto-completion
PHP flexible more expressive
no validation
Store sensitive settings outside of your project
<doctrine:dbal dbname="sfweb" username="root" password="SuperSecretPasswordThatAnyoneCanSee" />
SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "foobar"
in a .htaccess or httpd.conf file
%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> </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>
XML
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. */ protected function getSwiftmailer_MailerService() { if (isset($this->shared['swiftmailer.mailer'])) return $this->shared['swiftmailer.mailer'];
$instance = new Swift_Mailer($this->getSwiftmailer_Transport_SmtpService());
return $this->shared['swiftmailer.mailer'] = $instance; }
PHPDoc for auto-completion
As fast as it could be
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/