symfony2 - webexpo 2010
TRANSCRIPT
Symfony2 Fabien Potencier
How many of you have already used symfony1?
How many of you have already played with Symfony2?
What is Symfony2?
A set of decoupled and cohesive components
DependencyInjection EventDispatcher HttpFoundation OutputEscaper
DomCrawler CssSelector Templating HttpKernel BrowserKit Validator Routing Console Process Finder Form Yaml
git clone git://github.com/symfony/symfony.git
A set of decoupled and cohesive components
Autoloading
PEAR_Log > PEAR/Log.php Zend_Log > Zend/Log.php
Swift_Mime_Message > Swift/Mime/Message.php Twig_Node_For > Twig/Node/For.php
Symfony\Foundation\Kernel Symfony/Foundation/Kernel.php
Doctrine\DBAL\Driver Doctrine/DBAL/Driver.php
pdepend\reflection\ReflectionSession pdepend/reflection/ReflectionSession.php
http://groups.google.com/group/php-standards/web/psr-0-final-proposal
require_once '.../Symfony/Framework/UniversalClassLoader.php';
use Symfony\Framework\UniversalClassLoader;
$loader = new UniversalClassLoader(); $loader->register();
PHP 5.3 technical interoperability standards
$loader->registerNamespaces(array( 'Symfony' => '/path/to/symfony/src', 'Doctrine' => '/path/to/doctrine/lib', 'pdepend' => '/path/to/reflection/source', ));
$loader->registerPrefixes(array( 'Swift_' => '/path/to/swiftmailer/lib/classes', 'Zend_' => '/path/to/vendor/zend/library', ));
PEAR style
A set of decoupled and cohesive components
Process
use Symfony\Component\Process\Process;
$cmd = 'ssh 1.2.3.4 "ps waux"';
$process = new Process($cmd); $process->run();
if (!$process->isSuccessful()) { throw new \RuntimeException( $process->getErrorOutput()); }
echo $process->getOutput();
$cmd = 'ssh 1.2.3.4 "tail -f /some/log"';
$process = new Process($cmd);
$process->run(function ($type, $buffer) { echo $buffer; });
use Symfony\Component\Process\PhpProcess;
$process = new PhpProcess( '<?php echo "hello"; ?>'); $process->run();
if (!$process->isSuccessful()) { throw new \RuntimeException( $process->getErrorOutput()); }
echo $process->getOutput();
A set of decoupled and cohesive components
CssSelector
use Symfony\Component\CssSelector\Parser;
Parser::cssToXpath('h4 > a:contains("foo")');
use Symfony\Component\CssSelector\Parser;
$document = new \DOMDocument(); $document->loadHTMLFile('...'); $xpath = new \DOMXPath($document);
$expr = Parser::cssToXpath('a.smart'); $nodes = $xpath->query($expr);
foreach ($nodes as $node) { printf("%s (%s)\n", $node->nodeValue, $node->getAttribute('href')); }
A set of decoupled and cohesive components
Finder
use Symfony\Component\Finder\Finder;
$finder = new Finder(); $finder ->files() ->in(__DIR__) ->...() ->sortByName() ;
$finder ->name('*.php') ->depth('<= 1') ->date('>= yesterday') ->size('<= 1K') ->filter(function (\SplFileInfo $file) { return strlen($file->getBasename()) < 9; }) ;
foreach ($finder as $file) { print $file->getRealpath()."\n"; }
$files = iterator_to_array($finder);
$count = iterator_count($finder);
use Symfony\Component\Finder\Finder;
$s3 = new \Zend_Service_Amazon_S3($key, $sct); $s3->registerStreamWrapper("s3");
$finder = new Finder(); $finder ->name('photos*') ->size('< 100K') ->date('since 1 hour ago') ->in('s3://bucket-name') ;
A set of decoupled and cohesive components
Routing
/blog.php?section=symfony&article_id=18475
web/ index.php
/index.php/blog/2010/09/18/Symfony2-in-India
/blog/2010/09/18/Symfony2-in-India
/blog/:year/:month/:day/:slug
post: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Post:show }
<routes> <route id="post" pattern="/blog/:year/:month/:day/:slug"> <default key="_controller"> BlogBundle:Post:show </default> </route> </routes>
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$route = new Route( '/blog/:year/:month/:day/:slug', array('_controller' => 'BlogBundle:Post:show'));
$collection->addRoute('post', $route);
return $collection;
$router ->match('/blog/2010/09/18/Symfony2-in-India')
$router ->generate('post', array('slug' => '...'))
post: pattern: /post/:slug defaults: { _controller: BlogBundle:Post:show }
$router ->generate('post', array('slug' => '...'))
An Object-Oriented abstraction on top of PHP
An Object-Oriented abstraction on top of PHP
Request
use Symfony\Component\HttpFoundation\Request;
$request = new Request();
// get a $_GET parameter $request->query->get('page');
// get a $_POST parameter $request->request->get('page');
// get a $_COOKIE parameter $request->cookies->get('name');
$request->getPreferredLanguage(array('en', 'fr')); $request->isXmlHttpRequest();
// get a $_FILE parameter $f = $request->files->get('image');
// $f is an instance of // Symfony\Component\HttpFoundation\File\UploadedFile
// guess extension, based on the mime type $n = '/path/to/file'.$file->getDefaultExtension(); $f->move($n);
new Request();
new Request( $_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER );
Request::create('/hello/Fabien', 'GET');
An Object-Oriented abstraction on top of PHP
Session
$session = $request->getSession();
$session->set('foo', 'bar'); $session->get('foo');
$session->setFlash('notice', 'Congratulations!');
An Object-Oriented abstraction on top of PHP
Response
use Symfony\Component\HttpFoundation\Response;
$response = new Response('Hello World', 200, array('Content-Type' => 'text/plain')); $response->send();
$response->setHeader('Content-Type', 'text/plain'); $response->setCookie('foo', 'bar'); $response->setContent('Hello World'); $response->setStatusCode(200);
An Object-Oriented abstraction of the HTTP dialog
A MVC Web Framework
The Symfony2 MVC Philosophy
Be as easy as possible for newcomers and as flexible as possible for advanced users
MVC
http://symfony-reloaded.org/
http://symfony-reloaded.org/downloads/sandbox_2_0_PR3.zip
post: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index }
namespace Application\HelloBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller;
class HelloController extends Controller { public function indexAction($name) { return new Response('Hello '.$name); } }
namespace Application\HelloBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller;
class HelloController extends Controller { public function indexAction($name) { // Get things from the Model
return $this->render( 'HelloBundle:Hello:index', array('name' => $name) ); } }
Hello <?php echo $name ?>!
<?php $view->extend('HelloBundle::layout') ?>
Hello <?php echo $name ?>!
<html> <head> <title> <?php $view['slots']->output('title') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus, in pulvinar lectus rutrum sit amet. Duis gravida, metus in dictum eleifend, dolor risus ;ncidunt ligula, non volutpat nulla sapien in elit. Nulla rutrum erat id neque suscipit eu ultricies odio sollicitudin. Aliquam a mi vel eros placerat hendrerit. Phasellus por6tor, augue sit amet vulputate venena;s, dui leo commodo odio, a euismod turpis ligula in elit.
_content
title
layout
slot
slot
{% extends "HelloBundle::layout" %}
{% block content %} Hello {{ name }}! {% endblock %}
<html> <head> <title> {% block title %}{% endblock %} </title> </head> <body> {% block body %}{% endblock %} </body> </html>
~ PAC / HMVC
http://en.wikipedia.org/wiki/Presentation-abstraction-control
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus, in pulvinar lectus rutrum sit amet. Duis gravida, metus in dictum eleifend, dolor risus ;ncidunt ligula, non volutpat nulla sapien in elit. Nulla rutrum erat id neque suscipit eu ultricies odio sollicitudin. Aliquam a mi vel eros placerat hendrerit. Phasellus por6tor, augue sit amet vulputate venena;s, dui leo commodo odio, a euismod turpis ligula in elit.
main controller (_content slot)
embedded MVC controller
layout
public function indexAction($name) { $embedded = $this['controller_resolver'] ->render('HelloBundle:Hello:foo', array('name' => $name));
return $this->render( 'HelloBundle:Hello:index', array( 'name' => $name, 'embedded' => $embedded, ) ); }
<?php $view->extend('...:layout') ?>
Lorem ipsum...
<?php echo $view['actions'] ->render('HelloBundle:Hello:foo', array('name' => $name)) ?>
Lorem ipsum...
Bundles
.../ SomeBundle/ Controller/ Entity/ Resources/ config/ views/ SomeBundle.php Tests/
public function registerBundleDirs() { return array( 'Application' => __DIR__.'/../src/Application', 'Bundle' => __DIR__.'/../src/Bundle', 'Symfony\Bundle' => __DIR__.'/../src/vendor/symfony/src/Symfony/Bundle', ); }
$this->render('SomeBundle:Hello:index', $params)
hello: pattern: /hello/:name defaults: { _controller: SomeBundle:Hello:index }
SomeBundle can be any of
Application\SomeBundle Bundle\SomeBundle Symfony\Bundle\SomeBundle
Environments
Developers Customer End Users
Development Environment
Staging Environment
Production Environment
cache cache cache
debug debug debug
logs logs logs
stats stats stats
Development Environment
Staging Environment
Production Environment
# config/config.yml doctrine.dbal: dbname: mydbname user: root password: %doctrine.dbal_password%
swift.mailer: transport: smtp host: localhost
# config/config_dev.yml imports: - { resource: config.yml }
doctrine.dbal: password: null
swift.mailer: transport: gmail username: xxxxxxxx password: xxxxxxxx
# Doctrine Configuration doctrine.dbal: dbname: xxxxxxxx user: xxxxxxxx password: ~
# Swiftmailer Configuration swift.mailer: transport: smtp encryption: ssl auth_mode: login host: smtp.gmail.com username: xxxxxxxx password: xxxxxxxx
<!-- Doctrine Configuration --> <doctrine:dbal dbname="xxxxxxxx" user="xxxxxxxx" password="" />
<!-- Swiftmailer Configuration --> <swift:mailer transport="smtp" encryption="ssl" auth_mode="login" host="smtp.gmail.com" username="xxxxxxxx" password="xxxxxxxx" />
// Doctrine Configuration $container->loadFromExtension('doctrine', 'dbal', array( 'dbname' => 'xxxxxxxx', 'user' => 'xxxxxxxx', 'password' => '', ));
// Swiftmailer Configuration $container->loadFromExtension('swift', 'mailer', array( 'transport' => "smtp", 'encryption' => "ssl", 'auth_mode' => "login", 'host' => "smtp.gmail.com", 'username' => "xxxxxxxx", 'password' => "xxxxxxxx", ));
Dependency Injection Container
Third-party libraries integration Unified Configuration
# Twig Configuration twig.config: auto_reload: true
# Zend Logger Configuration zend.logger: priority: debug path: %kernel.root_dir%/logs/%kernel.environment%.log
Configuration formats XML, YAML, PHP, INI, or Annotations
Parameters management Fast (cached) Inheritance
Sensitive data security …
<doctrine:dbal dbname="sfweb" username="root" password="SuperSecretPasswordThatAnyoneCanSee" />
SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "foobar"
in a .htaccess or httpd.conf file
%doctrine.dbal.password%
<doctrine:dbal dbname="sfweb" username="root" password="%doctrine.dbal.password%" />
Developer Tools
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"
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)"
Security
XSS / CSRF / SQL Injection
Functional Tests
$client = $this->createClient();
$crawler = $client->request( 'GET', '/hello/Fabien');
$this->assertTrue($crawler->filter( 'html:contains("Hello Fabien")')->count());
$this->assertEquals( 10, $crawler->filter('div.hentry')->count());
$this->assertTrue( $client->getResponse()->isSuccessful());
$crawler = $client->request( 'GET', 'hello/Lucas' );
$link = $crawler->selectLink("Greet Lucas");
$client->click($link);
$form = $crawler->selectButton('submit');
$client->submit($form, array( 'name' => 'Lucas', 'country' => 'France', 'like_symfony' => true, 'photo' => '/path/to/lucas.jpg', ));
$harry = $this->createClient(); $sally = $this->createClient();
$harry->request('POST', '/say/sally/Hello'); $sally->request('GET', '/messages');
$this->assertEquals(201, $harry->getResponse()->getStatusCode());
$this->assertRegExp('/Hello/', $sally->getResponse()->getContent());
$harry = $this->createClient(); $sally = $this->createClient();
$harry->insulate(); $sally->insulate();
$harry->request('POST', '/say/sally/Hello'); $sally->request('GET', '/messages');
$this->assertEquals(201, $harry->getResponse()->getStatusCode()); $this->assertRegExp('/Hello/', $sally->getResponse()->getContent());
Caching
HTTP Expiration / HTTP Validation
$response->setSharedMaxAge(...); $response->setTtl(...); $response->setMaxAge(...); $response->setClientTtl(...); $response->setExpires(...);
$response->setETag(...); $response->setLastModified(...);
Cache-Control: s-maxage=10
public function showAction() { // ...
$response = $this->render('...', $vars);
$response->setSharedMaxAge(10);
return $response; }
Symfony 2 comes built-in with an HTTP accelerator
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus, in pulvinar lectus rutrum sit amet. Duis gravida, metus in dictum eleifend, dolor risus ;ncidunt ligula, non volutpat nulla sapien in elit. Nulla rutrum erat id neque suscipit eu ultricies odio sollicitudin. Aliquam a mi vel eros placerat hendrerit. Phasellus por6tor, augue sit amet vulputate venena;s, dui leo commodo odio, a euismod turpis ligula in elit.
cacheable for 10 seconds cacheable for 5 seconds
main controller
embedded controller
layout
<?php $view->extend('...:layout') ?>
<?php $view['slots']->start('sidebar') ?>
<?php echo $view['actions']->render('...:foo') ?>
<?php $view['slots']->stop() ?>
cacheable for 10 seconds
cacheable for 5 seconds
$view['actions']->render('HelloBundle:Hello:foo', array('name' => $name), array('standalone' => true) )
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus, in pulvinar lectus rutrum sit amet. Duis gravida, metus in dictum eleifend, dolor risus ;ncidunt ligula, non volutpat nulla sapien in elit. Nulla rutrum erat id neque suscipit eu ultricies odio sollicitudin. Aliquam a mi vel eros placerat hendrerit. Phasellus por6tor, augue sit amet vulputate venena;s, dui leo commodo odio, a euismod turpis ligula in elit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vel nulla arcu, vitae cursus nunc. Integer semper turpis et enim por6tor iaculis. Nulla facilisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vehicula ves;bulum dictum. Aenean non velit tortor. Nullam adipiscing malesuada aliquam. Mauris dignissim, urna quis iaculis tempus, justo libero por6tor est, nec eleifend est elit vitae ante. Curabitur interdum luctus metus, in pulvinar lectus rutrum sit amet. Duis gravida, metus in dictum eleifend, dolor risus ;ncidunt ligula, non volutpat nulla sapien in elit. Nulla rutrum erat id neque suscipit eu ultricies odio sollicitudin. Aliquam a mi vel eros placerat hendrerit. Phasellus por6tor, augue sit amet vulputate venena;s, dui leo commodo odio, a euismod turpis ligula in elit.
<esi:include src="..." />
ESI… or Edge Side Includes
Symf
ony2
Appli
catio
n
Reve
rse Pr
oxy
Clien
t Lorem ipsum dolor sit amet,
Lorem ipsum dolor
Lorem ipsum dolor sit amet,
Lorem ipsum dolor
1
2
3
4
Symfony2 app
Web Server
Requests
Response
Symfony2 app
Symfony2 HTTP proxy
Web Server
Requests
Response
Symfony2 app
Web Server
Reverse proxy
Requests
Response
Extensibility
Request
Response
core.controller
core.response
core.view
core.request
getController()
getArguments()
Request
Response
core.controller
core.response
core.view
core.request
getController()
getArguments()
core.exception
HttpFoundation
HttpKernel
FrameworkBundle
Routing
Console
SwiftmailerBundle
TwigBundle
DependencyInjection
...
...
Components
Bundles
Event Dispatcher
Templating
DoctrineBundle
ZendBundle
Questions?