Symfony Components 2.0 on PHP 5.3

Post on 13-Apr-2017

24,039 views 3 download

transcript

Symfony Components What's in for you?

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 OSS:

–  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 use symfony?

symfony

•  Full-stack framework (MVC architecture) •  symfony provides the infrastructure/tools needed for 95% of the web

projects •  Open-Source (MIT License) since 2005 •  Based on

–  11 years of Sensio experience building websites for its customers

–  Existing Open-Source projects

But wait, symfony is a monolithic framework, right?

A bit of history

symfony 1.0 (January 2007) started as a glue between existing Open-Source libraries

A bit of history

symfony 1.1 (June 2008) was a big refactoring of the code base

–  Decoupled the main component: like Forms, Routing, Cache, YAML, ORMs, …

platform

sfEventDispatcher

sfRequest sfResponsesfRouting sfUser

sfStorage sfCache

sfI18N

sfForm

sfLogger

sfDatabase

sfValidator sfWidget

sfYAML

sfCoreAutoload

sfOutputEscaper

The symfony (1.2/1.3/1.4) MVC framework is based on a set of cohesive but decoupled classes, the symfony components

Symfony Components

•  Announced in May 2009 •  Standalone components •  Packaged individually •  No dependencies •  Release cycle independent of Symfony, the framework

Symfony Components

Dedicated website for each component (with code and documentation) http://components.symfony-project.org/

Dedicated Subversion and Git repository http://svn.symfony-project.com/components/

http://github.com/fabpot

Symfony Components

They have been migrated to PHP 5.3

http://svn.symfony-project.com/branches/2.0/lib/Symfony/Components

Symfony Components

Each “old” PHP 5.2 symfony component has been branched and a 1.0 version will be released soon

http://svn.symfony-project.com/components/

Symfony Components

•  Extracted from symfony 1 –  Event Dispatcher –  YAML –  Output Escaper

•  Written from scratch for Symfony 2 –  Dependency Injection Container –  Request Handler –  Templating

•  We don’t want to duplicate effort done by Zend Framework

Symfony Components

•  Coming soon… –  Request Handler –  Form –  Command Line Tools –  Routing –  Security –  Cache –  Debug – …

Symfony Components Using them in your project

Which version?

•  This presentation is about the components based on PHP 5.3

•  The stable 1.0 branch based on PHP 5.2 has the same feature set and work exactly in the same way, except:

–  The autoloader

–  The class names

Symfony 2.0 Namespaces

Symfony\Components

Symfony\Foundation

Symfony\Framework

Autoloader

$path = '/path/to/SymfonyComponents/lib';

require_once $path.'/Symfony/Foundation/ClassLoader.php';

$loader = new Symfony\Foundation\ClassLoader('Symfony', $path);

$loader->register();

PHP 5.3 technical interoperability standards

« … describes the mandatory requirements that must be adhered to

for autoloader interoperability »

http://groups.google.com/group/php-standards/web/psr-0-final-proposal

What does this mean?

•  PHP libraries will finally be 100% technically interoperable and have optimum autoloading performance

•  Symfony 2.0 + Doctrine 2.0 + Zend Framework 2.0 •  Load classes from 3 different libraries with one autoloader and one

include path •  Also able to load classes implementing the PEAR naming convention •  Share SplClassLoader implementation:

–  http://gist.github.com/221634 •  Implement SplClassLoader in C/PHP :)

Symfony Components YAML

Symfony\Components\YAML

YAML

•  YAML is a human friendly data serialization standard for all programming languages (implementations exist in Perl, Ruby, Python, Java, …)

•  YAML is a great format for your configuration files •  YAML files are as expressive as XML files and as readable as INI files

YAML

•  Used in symfony for all configuration files

•  Used in Doctrine for schema and fixtures

•  Used in PHPUnit for TAP output and for DataSet (DbUnit)

config: key: value foo: [foo, bar] bar: { foo: bar } foobar: { foo: [foo, bar] }

use Symfony\Components\YAML\YAML;

// Parse YAML $config = YAML::load(<<<'EOF' config: key: value foo: [foo, bar] bar: { foo: bar } foobar: { foo: [foo, bar] } EOF );

// Dump YAML echo YAML::dump($config);

Symfony Components Event Dispatcher

Symfony\Components\EventDispatcher

Event Dispatcher

•  Symfony Event Dispatcher is a PHP library that provides a lightweight implementation of the Observer design pattern

•  Based on the Cocoa Notification Center •  Used in symfony to provide hooks and allow customizations of default

behaviors –  Inject the Web Debug Toolbar –  Inject methods into core Objects –  I18n management – …

platform

sfEventDispatcher

sfRequest sfResponsesfRouting sfUser

sfStorage sfCache

sfI18N

sfForm

sfLogger

sfDatabase

sfValidator sfWidget

sfYAML

sfCoreAutoload

sfOutputEscaper

An object (the subject) maintains a list of its dependents (observers) and notifies them automatically of any state

changes, usually by calling one of their methods

http://en.wikipedia.org/wiki/Observer_pattern

An event dispatcher is a central object that manages connections

between subjects and observers

It is a powerful mechanism to extend applications without having to change the objects themselves

Associated words: hook, listener, event, subject, …

// an anonymous listener $listener = function (Event $event) { echo "Hello {$event['name']}\n"; };

// register the listener (any PHP callable) with the dispatcher $dispatcher->connect('foo', $listener);

// notify the event somewhere $event = new Event(null, 'foo', array('name' => 'Fabien')); $dispatcher->notify($event);

use Symfony\Components\EventDispatcher\Event; use Symfony\Components\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

Symfony Event Dispatcher

•  The implementation is simple –  No interface to implement –  No need to create an event class for each event –  An event is just a unique identifier –  Some conventions (parameter names)

Symfony Event Dispatcher

•  Advantages –  Very simple to use –  Easy to add new arguments to the listener –  Very fast –  Very easy to add a new event, just notify it with a unique name

•  Disadvantages –  The contract between the listener and the notifier is quite loose

Notifications

•  Notify : all listeners are called in turn, no feedback to the notifier –  Logging, …

•  Notify Until : all listeners are called until one « processes » the event. The listener that has processed the event can return something to the caller

–  Exceptions, Method not found in __call(), …

•  Filter : Each listener filters a given value and can change it. The filtered value is returned to the caller

–  HTML content, parameters, …

class Response { protected $content, $dispatcher;

public function __construct(EventDispatcher $dispatcher, $content) { $this->dispatcher = $dispatcher; $this->content = $content; }

public function send() { $event = new Event($this, 'response.filter_content');

$this->dispatcher->filter($event, $this->content);

echo $event->getReturnValue(); } }

$dispatcher = new EventDispatcher();

// an anonymous listener $listener = function (Event $event, $content) { return '*'.$content.'*'; };

// register the listener $dispatcher->connect('response.filter_content', $listener);

$response = new Response($dispatcher, 'Hello'); $response->send();

Dispatcher Listeners

listener connects to ‘response.filter_content’

Response notifies ‘response.filter_content’

Notifiers

1

2 Calls all listeners

listener is called

The Response object knows nothing about the listeners The listener objects know nothing about the Response one

They communicate through the Dispatcher object

Anybody can listen to the ‘response.filter_content’ event and acts accordingly

Dispatcher Listeners

listener connects to response.filter_content

Notifiers

1 Your class connects

to response.filter_content

Response notifies response.filter_content 2 Calls

all listeners listener is called

Your class callback is called

Dispatcher

Response notifies response.filter_content

Notifiers

Calls nothing

very small overhead

Symfony Components Templating

Symfony\Components\Templating

Symfony Templating

•  Thin layer on top of PHP that adds some template-oriented features

•  Can work with Template Engines (Smarty, Twig, …)

New Templating Framework

•  4 sub-components

–  Template Engine

–  Template Renderers

–  Template Loaders

–  Template Storages

use Symfony\Components\Templating\Engine; use Symfony\Components\Templating\Loader\FilesystemLoader;

$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, …

•  Built-in loaders: FilesystemLoader, ChainLoader, CacheLoader •  Template names are « logical » names:

$loader = new FilesystemLoader('/path/to/templates/%name%.php');

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(), ));

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() ?>

Template Multiple Inheritance

A layout can be decorated by another layout

Each layout can override slots

Templating A CMS example

http://fabien.potencier.org/talk/33/php-barcelona-symfony-2-0-on-PHP-5-3?position=76

Symfony Components Dependency Injection

Symfony\Components\DependencyInjection

« Dependency Injection is where components are given their dependencies through their

constructors, methods, or directly into fields. »

http://www.picocontainer.org/injection.html

class Message { public function __construct() { $this->output = new Output(); } }

class Message { public function __construct(OutputInterface $output) { $this->output = $output; } }

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" : '')); } }

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%s\033[0m", $msg); } }

DI Hello World example

$output = new FancyOutput(); $message = new Message($output, array('with_newline' => true)); $message->say('Hello World');

A DI container facilitates objects description and object relationships,

configures and instantiates objects

use Symfony\Components\DependencyInjection\Builder; use Symfony\Components\DependencyInjection\Reference;

$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!');

DI Container Hello World example

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

$message = $container->message;

$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!');

<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');

PHP

XML XML is validated against an XSD

$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!');

services: output: { class: FancyOutput } message: class: Message arguments: - @output - { with_newline: true }

$container = new Builder(); $loader = new YamlFileLoader($container); $loader->load('services.yml');

PHP

YAML

<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 Builder(); $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');

Loaders & Dumpers

•  IniFileLoader •  XmlFileLoader •  YamlFileLoader

•  XmlDumper •  YamlDumper •  PhpDumper •  GraphvizDumper

Make your container VERY fast

use Symfony\Components\DependencyInjection\Builder; use Symfony\Components\DependencyInjection\Reference; use Symfony\Components\DependencyInjection\Dumper\XmlDumper; use Symfony\Components\DependencyInjection\Dumper\YamlDumper; use Symfony\Components\DependencyInjection\Dumper\PhpDumper; use Symfony\Components\DependencyInjection\Loader\XmlFileLoader; use Symfony\Components\DependencyInjection\Loader\YamlFileLoader;

$container = new Builder();

$container->register('output', 'FancyOutput');

$container-> register('message', 'Message')-> setArguments(array(new Reference('output'), array('with_newline' => true))) ;

$dumper = new XmlDumper($container); file_put_contents(__DIR__.'/container.xml', $dumper->dump());

$loader = new XmlFileLoader($container); $loader->load(__DIR__.'/container.xml');

$dumper = new YamlDumper($container); file_put_contents(__DIR__.'/container.yml', $dumper->dump());

$loader = new YamlFileLoader($container); $loader->load(__DIR__.'/container.yml');

$dumper = new PhpDumper($container); echo $dumper->dump();

use Symfony\Components\DependencyInjection\Container; use Symfony\Components\DependencyInjection\Reference; use Symfony\Components\DependencyInjection\Parameter;

class ProjectServiceContainer extends Container { protected $shared = array();

protected function getOutputService() { if (isset($this->shared['output'])) return $this->shared['output'];

$instance = new FancyOutput();

return $this->shared['output'] = $instance; }

protected function getMessageService() { if (isset($this->shared['message'])) return $this->shared['message'];

$instance = new Message($this->getService('output'), array('with_newline' => true));

return $this->shared['message'] = $instance; } }

use Symfony\Components\DependencyInjection\Dumper\GraphvizDumper;

$dumper = new GraphvizDumper($container); echo $dumper->dump();

digraph sc { ratio="compress" node [fontsize="11" fontname="Arial" shape="record"]; edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"];

node_output [label="output\nFancyOutput\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_message [label="message\nMessage\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_service_container [label="service_container\nSymfony\\Components\\DependencyInjection\\Builder\n", shape=record, fillcolor="#9999ff", style="filled"]; node_message -> node_output [label="" style="filled"]; }

Symfony Components Output Escaper

Symfony\Components\OutputEscaper

Output Escaper •  Provides automatic XSS protection for your templates •  By wrapping template variables •  Works for

–  strings –  arrays –  objects

•  properties •  methods •  __call(), __get(), … •  Iterators, Coutables, … •  …

•  Works for deep method calls

use Symfony\Components\OutputEscaper\Escaper;

$title = 'Foo <br />';

echo Escaper::escape(ESC_SPECIALCHARS, $title);

use Symfony\Components\OutputEscaper\Escaper;

$article = array( 'title' => 'Foo <br />', 'author' => array( 'name' => 'Fabien <br/>', ) );

$article = Escaper::escape(ESC_SPECIALCHARS, $article);

echo $article['title']."\n"; echo $article['author']['name']."\n";

class Article { protected $title; protected $author;

public $full_title;

public function __construct($title, Author $author) { $this->title = $title; $this->full_title = $title; $this->author = $author; }

public function getTitle() { return $this->title; } public function getAuthor() { return $this->author; } public function __get($key) { return $this->$key; } public function __call($method, $arguments) { return $this->{'get'.$method}(); } }

public property

public method

public method returning another object

magic __get() magic __call()

class Author { protected $name;

public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } }

use Symfony\Components\OutputEscaper\Escaper;

$article = new Article( 'foo <br />', new Author('Fabien <br />') );

$article = Escaper::escape(ESC_SPECIALCHARS, $article);

echo $article->getTitle()."\n"; echo $article->getAuthor()->getName()."\n"; echo $article->full_title."\n"; echo $article->title."\n"; echo $article->title()."\n";

Escaping Strategy

echo $article->getHtmlContent(ESC_RAW);

echo $article->getTitle(ESC_JS);

explicitly ask for raw data

change the default escaping strategy

with Matthew Weier O’Pheinney

I will reveal the first alpha release of Symfony 2.0!

symfony-live.com

with Matthew Weier O’Pheinney

… with Matthew Weier O'Phinney as a special guest

symfony-live.com

Thank you!

My slides on slideshare.com/fabpot with a bonus: How to use Symfony Components with ZF!

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/

How to use Symfony Components within a Zend Framework project?

BONUS

Symfony components in a ZF project

•  Symfony Components can make your ZF application better – More configurable: symfony YAML – More flexible: symfony Event Dispatcher –  Faster: symfony Dependency Injection – More secure: symfony Output Escaper

•  … and more fun of course

Symfony Components in a ZF project

•  The examples in this presentation are just to get you started faster

•  So, be creative with them. They open all kind of opportunities for your next Zend Framework project

•  And please, give me feedback, and tell me what you do with the Symfony Components

Symfony Components Event Dispatcher

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { public function run() { require_once '/path/to/sfEventDispatcher.php'; $dispatcher = new sfEventDispatcher();

$event = new sfEvent(null, 'bootstrap.prerun'); $dispatcher->notify($event);

parent::run();

$event = new sfevent(null, 'bootstrap.postrun'); $dispatcher->notify($event); } }

Symfony Components Dependency Injection

Example from Ben Eberlei (he rocks!): http://www.whitewashing.de/blog/articles/118

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { public function getContainer() { if (null === $this->_container) { $this->setContainer($this->_initContainer()); }

return $this->_container; }

protected function _initContainer() { require_once '/path/to/sfServiceContainerAutoloader.php'; sfServiceContainerAutoloader::register();

$container = new sfServiceContainerBuilder();

$loader = new sfServiceContainerLoaderFileXml($container); $loader->load(dirname(__FILE__).'/configs/resources.xml');

return $container; } }

<?xml version="1.0" ?>

<container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="mailer.username">foo</parameter> <parameter key="mailer.password">bar</parameter> <parameter key="mailer.class">Zend_Mail</parameter> </parameters> <services> <service id="mail.transport" class="Zend_Mail_Transport_Smtp" shared="false"> <argument>smtp.gmail.com</argument> <argument type="collection"> <argument key="auth">login</argument> <argument key="username">%mailer.username%</argument> <argument key="password">%mailer.password%</argument> <argument key="ssl">ssl</argument> <argument key="port">465</argument> </argument> </service> <service id="mailer" class="%mailer.class%"> <call method="setDefaultTransport"> <argument type="service" id="mail.transport" /> </call> </service> </services> </container>

parameters: mailer.username: foo mailer.password: bar mailer.class: Zend_Mail

services: mail.transport: class: Zend_Mail_Transport_Smtp arguments: [smtp.gmail.com, { auth: login, username: %mailer.username%, password: %mailer.password%, ssl: ssl, port: 465 }] shared: false mailer: class: %mailer.class% calls: - [setDefaultTransport, [@mail.transport]]

class GuestbookController extends Zend_Controller_Action { public function indexAction() { $guestbook = new Default_Model_Guestbook(); $this->view->entries = $guestbook->fetchAll();

$container = $this->getInvokeArg('bootstrap')->getContainer(); $mailer = $container->mailer; } }

Dependency Injection Container

•  By default in ZF, new resources can be added to the container but cannot be lazy-loaded

–  All resources used by Zend_Application are loaded on every request

•  By using symfony Service Container, the resources are lazy-loaded –  Instances and their dependencies are created the first time you get them

•  Interesting for resources like DB

Symfony Components Output Escaper

require_once '/path/to/sfOutputEscaperAutoloader.php'; sfOutputEscaperAutoloader::register();

class My_View extends Zend_View { public function __set($key, $val) { if ('_' === substr($key, 0, 1)) { // throw an exception }

$this->$key = sfOutputEscaper::escape(array($this, 'escape'), $val); } }

<dl> <?php foreach ($this->entries as $entry): ?> <dt><?php echo $this->escape($entry->email) ?></dt> <dd><?php echo $this->escape($entry->comment) ?></dd> <?php endforeach ?> </dl>

<dl> <?php foreach ($this->entries as $entry): ?> <dt><?php echo $entry->email ?></dt> <dd><?php echo $entry->comment ?></dd> <?php endforeach ?> </dl>

<?php echo $entry->getRawValue()->comment ?>

<?php echo $entry->getComment(ESC_RAW) ?>