Silex al límite

265
20-22 junio 2013 Madrid SILEX AL LÍMITE Javier Eguiluz de Symfony

description

 

Transcript of Silex al límite

20-22 junio 2013 Madrid

SILEX AL LÍMITEJavier Eguiluz

deSymfony

¡muchas gracias a nuestros patrocinadores!

deSymfony

ME PRESENTO

Sobre mí

Javier EguiluzFormador y programador entusiasta de Symfony.javiereguiluz.com

2011

NachoMartín

Microframework SilexNacho Martín

JORNADAS SYMFONY 2011 1-3 JULIO, CASTELLÓN DESYMFONY.COM

deSymfony 2011

AGENDA

RENDIMIENTOAL LÍMITE

CARACTERÍSTICASAL LÍMITE

RENDIMIENTOAL LÍMITE

CARACTERÍSTICASAL LÍMITE

RENDIMIENTOAL LÍMITE

CARACTERÍSTICASAL LÍMITE

PRIMERA PARTE

RENDIMIENTOAL LÍMITE

PASOS PREVIOS

1. El backend «casi» no importa

BACKEND -10 ms BBDD-200 ms PHP -5 ms Apache

215 ms

FRONTEND -5 seg imágenes -1 seg CSS -2 seg JavaScript

8 seg

1. El backend «casi» no importa

BACKEND -10 ms BBDD-200 ms PHP -5 ms Apache

215 ms

FRONTEND -5 seg imágenes -1 seg CSS -2 seg JavaScript

8 seg8.000 ms

1. El backend «casi» no importa

BACKEND215 ms

FRONTEND

-5 seg imágenes -1 seg CSS -2 seg JavaScript

8.000 ms

no te pierdas esta charla

MiquelCompany

2. Tu aplicación «casi» no importa

TU APLICACIÓN

S.O.

BBDD SERVIDOR WEB

PHPAPC

VARNISH

2. Tu aplicación «casi» no importa

TU APLICACIÓN

S.O.

BBDD SERVIDOR WEB

PHPAPC

VARNISHTU APLICACIÓN

no te pierdas esta charla

RicardClau

y tampoco te pierdas su blog: ricardclau.com

3. Nada importa si no lo miras

MySQL Slow Query Log

APC log

¿cada cuánto miráis estos archivos?

3. Nada importa si no lo miras

MySQL Slow Query Log

APC log

¿cada cuánto miráis estos archivos?

¿los miráis alguna vez?

APC log

APC log

HttpCache\ESI.php

APC log

APC log

'http_cache.esi' => null

CristinaQuintana

no te pierdas esta charla

SILEXINTERNALS

Una aplicación Silex típicarequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );

$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

$app->run();

Una aplicación Silex típicarequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );

$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

$app->run();

1

2

3

4

5

Mejorando el rendimientorequire_once __DIR__.'/../vendor/autoload.php';1

Mejorando el rendimientorequire_once __DIR__.'/../vendor/autoload.php';1

$ composer install --optimize-autoloader

Mejorando el rendimientorequire_once __DIR__.'/../vendor/autoload.php';1

$ composer install --optimize-autoloader

$ composer dump-autoload --optimize

Mejorando el rendimientorequire_once __DIR__.'/../vendor/autoload.php';1

$vendorDir = dirname(dirname(__FILE__));$baseDir = dirname($vendorDir);

return array( 'SessionHandlerInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Resources/stubs/SessionHandlerInterface.php',

);

vendor/composer/autoload_classmap.php original

Mejorando el rendimientorequire_once __DIR__.'/../vendor/autoload.php';1

// autoload_classmap.php generated by Composer

$vendorDir = dirname(dirname(__FILE__));$baseDir = dirname($vendorDir);

return array( 'Doctrine\\DBAL\\Cache\\ArrayStatement' => $vendorDir . '/doctrine/dbal/ ... /ArrayStatement.php', 'Doctrine\\DBAL\\Cache\\CacheException' => $vendorDir . '/doctrine/dbal/ ... /CacheException.php', // ... 'Monolog\\Formatter\\ChromePHPFormatter' => $vendorDir . '/monolog/... /ChromePHPFormatter.php', 'Monolog\\Formatter\\FormatterInterface' => $vendorDir . '/monolog/ ... /FormatterInterface.php', // ... 'Pimple' => $vendorDir . '/pimple/pimple/lib/Pimple.php', 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',

vendor/composer/autoload_classmap.php optimizado

1.762 elementos

Revisa tu composer.jsonrequire_once __DIR__.'/../vendor/autoload.php';1

{ "require": { "php": ">=5.3.3", "silex/silex": "~1.0", "silex/web-profiler": "1.0.*", "symfony/stopwatch": "~2.2", "twig/twig": "~1.13", "symfony/form": "~2.2", "symfony/translation": "~2.2", "symfony/validator": "~2.2", ... }}

Revisa tu composer.jsonrequire_once __DIR__.'/../vendor/autoload.php';1

{ "require": { "php": ">=5.3.3", "silex/silex": "~1.0", "silex/web-profiler": "1.0.*", "symfony/stopwatch": "~2.2", "twig/twig": "~1.13", "symfony/form": "~2.2", "symfony/translation": "~2.2", "symfony/validator": "~2.2", ... }}

¿realmente los usas?

Una aplicación Silex típicarequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );

$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

$app->run();

1

2

3

4

5

Mejorando el rendimiento$app = new Silex\Application(); 2

Mejorando el rendimiento$app = new Silex\Application(); 2

class Application extends \Pimple implements HttpKernelInterface, TerminableInterface {

public function __construct() { $this['logger'] = ... $this['routes'] = ... $this['controllers'] = ... $this['route_factory'] = ... $this['exception_handler'] = ... $this['dispatcher'] = ... }}

Mejorando el rendimiento$app = new Silex\Application(); 2

class Application extends \Pimple implements HttpKernelInterface, TerminableInterface {

public function __construct() { $this['logger'] = ... $this['routes'] = ... $this['controllers'] = ... $this['route_factory'] = ... $this['exception_handler'] = ... $this['dispatcher'] = ... }}

~100 líneas~12 servicios

mini truco

Configurando la aplicación$app = new Silex\Application();

$app = new Silex\Application([ 'request.http_port' => 80]);

Configurando la aplicación$app = new Silex\Application();

$app = new Silex\Application([ 'request.http_port' => 80]);

$app = new Silex\Application([ 'request.http_port' => 80, 'request.https_port' => 443, 'debug' => false, 'charset' => 'UTF-8', 'locale' => 'en',]);

Configurando la aplicación$app = new Silex\Application();

$app = new Silex\Application([ 'request.http_port' => 80, 'mi_app.mi_opcion_1' => '...', 'mi_app.mi_opcion_2' => '...',]);

Mejorando el rendimiento$app->register( ... );3

Mejorando el rendimiento$app->register( ... );3

class Application extends \Pimple implements HttpKernelInterface, TerminableInterface {

public function register($provider, $values) { $this->providers[] = $provider;

$provider->register($this);

foreach ($values as $key => $value) { $this[$key] = $value; }}

Mejorando el rendimiento$app->register( ... );3

class Application extends \Pimple implements HttpKernelInterface, TerminableInterface {

public function register($provider, $values) { $this->providers[] = $provider;

$provider->register($this);

foreach ($values as $key => $value) { $this[$key] = $value; }}

Mejorando el rendimiento$app->register( ... );3

class Application extends \Pimple implements HttpKernelInterface, TerminableInterface {

public function register($provider, $values) { $this->providers[] = $provider;

$provider->register($this);

foreach ($values as $key => $value) { $this[$key] = $value; }}

Mejorando el rendimiento$app->register( ... );3

$app->register( new Silex\Provider\DoctrineServiceProvider(), [ 'db.options' => [ 'driver' => 'pdo_mysql', 'host' => 'localhost', 'dbname' => '...', 'user' => '...', 'password' => '...', 'charset' => 'utf8', ]]);

afecta a la memoria consumida

Mejorando el rendimiento$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

4

Mejorando el rendimiento$app->run();5

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

$this->boot();

$response = $this['kernel']->handle($request, $type, $catch);

return $response;

Mejorando el rendimiento$app->run();5

public function run(Request $request = null) { if (null === $request) { $request = Request::createFromGlobals(); }

$response = $this->handle($request); $response->send(); $this->terminate($request, $response); }

$this->boot();

$response = $this['kernel']->handle($request, $type, $catch);

return $response;

foreach ($this->providers as $provider) { $provider->boot();}

En resumen1. Se carga un array de ~1.500 elementos.

2. Se crea un objeto Silex\Application.

3. Se ejecuta el register() de todos los servicios.

4. Se crea un objeto Request.

5. Se ejecuta el boot() de todos los servicios.

6. Se crea un objeto Response (con tu controlador).

7. Se envía la respuesta al usuario.

8. Se ejecuta el terminate() de la aplicación.

App. Silex

mini truco

ini_set('display_errors', 0);

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

if ($app['debug']) { $app->run();} else { $app['http_cache']->run();}

Típico controlador de producción

ini_set('display_errors', 0);

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

if ($app['debug']) { $app->run();} else { $app['http_cache']->run();}

Típico controlador de producción

innecesario

ini_set('display_errors', 0);

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

if ($app['debug']) { $app->run();} else { $app['http_cache']->run();}

Típico controlador de producción

innecesario

muy mal

ini_set('display_errors', 0);

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

if ($app['debug']) { $app->run();} else { $app['http_cache']->run();}

Típico controlador de producción

innecesario

muy malPHP_FUNCTION(ini_set) { char *varname, *new_value; int varname_len, new_value_len; char *old_value;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &varname, &varname_len, &new_value, &new_value_len) == FAILURE) { return; }

old_value = zend_ini_string(varname, varname_len + 1, 0);

// ...

TIPOS DE RESPUESTAS

Tipos de respuestas Silex / Sf2

Response

RedirectResponse

StreamedResponse

JsonResponse

BinaryFileResponse

Response vs. JsonResponse$app->get('/', function () use ($app) {

return new Response( json_encode( $datos ), 200, array('Content-Type' => 'application/json') );});

Response vs. JsonResponse$app->get('/', function () use ($app) {

return new Response( json_encode( $datos ), 200, array('Content-Type' => 'application/json') );});

$app->get('/', function () use ($app) {

return new JsonResponse($datos);});

Response vs. JsonResponse$app->get('/', function () use ($app) {

return new Response( json_encode( $datos ), 200, array('Content-Type' => 'application/json') );});

$app->get('/', function () use ($app) {

return new JsonResponse($datos);});

$app->get('/', function () use ($app) {

return $app->json($datos);});

Response vs. JsonResponse$app->get('/', function () use ($app) {

return new Response( json_encode( $datos ), ... );});

class JsonResponse extends Response{ // RFC4627-compliant JSON $this->data = json_encode($datos, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT );

Tipos de respuestas Silex / Sf2

Response

RedirectResponse

StreamedResponse

JsonResponse

BinaryFileResponse

Response vs. BinaryFileResponse$app->get('/', function () use ($app) {

return new Response( file_get_contents($archivo) );});

Response vs. BinaryFileResponse$app->get('/', function () use ($app) {

return new Response( file_get_contents($archivo) );});

Rendimiento

Response vs. BinaryFileResponse$app->get('/', function () use ($app) {

return new Response( file_get_contents($archivo) );});

Rendimiento

$app->get('/', function () use ($app) {

return new BinaryFileResponse($archivo);});

Response vs. BinaryFileResponse$app->get('/', function () use ($app) {

return new Response( file_get_contents($archivo) );});

Rendimiento

$app->get('/', function () use ($app) {

return new BinaryFileResponse($archivo);});

$app->get('/', function () use ($app) {

return $app->sendFile($archivo);});

Response vs. BinaryFileResponse

X-Sendfile-Type

X-Accel-Redirect

X-Accel-Mapping

Response vs. BinaryFileResponse

APLICACIÓN

APACHE / NGINX

KERNEL

X-Sendfile-Type

X-Accel-Redirect

X-Accel-Mapping

SILEX

Response vs. BinaryFileResponse

APLICACIÓN

APACHE / NGINX

KERNELsendfile()

X-Sendfile-Type

X-Accel-Redirect

X-Accel-Mapping

SILEX

REUTILIZANDO APLICACIONES

usuarios

web/index.php

/.../noticias/

Una aplicación Silex típica

usuarios

web/index.phpadministrador

/.../noticias/

Una aplicación Silex típica

usuarios

web/index.phpadministrador

/.../noticias/

Una aplicación Silex típica

/admin/admin/.../.../noticias/

Mezclando frontend y backend$app->get('/admin', function() { ... });$app->get('/admin/noticias', function() { ... });$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

Mezclando frontend y backend$app->get('/admin', function() { ... });$app->get('/admin/noticias', function() { ... });$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

¡horrible!

Mezclando frontend y backend$app->mount( '/admin', new AdminControllerProvider());

$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

Mezclando frontend y backend

$app->mount( '/admin', new AdminControllerProvider());

$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

$app->register( new Silex\Provider\ServiceControllerServiceProvider()); penaliza el

rendimiento

Mezclando frontend y backend$app->mount( '/admin', new AdminControllerProvider());

$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

Mezclando frontend y backend$app->mount( '/admin', new AdminControllerProvider());

$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

class AdminControllerProvider implements ControllerProviderInterface { public function connect(Application $app) { $controllers = $app['controllers_factory'];

$controllers->before(function() use ($app) { // seguridad });

$controllers->get('/', function (Application $app) { ... }); }}

Mezclando frontend y backend$app->mount( '/admin', new AdminControllerProvider());

$app->get('/noticias', function() { ... });$app->get('/', function() { ... });

class AdminControllerProvider implements ControllerProviderInterface { public function connect(Application $app) { $controllers = $app['controllers_factory'];

$controllers->before(function() use ($app) { // seguridad });

$controllers->get('/', function (Application $app) { ... }); }}

BACKEND

usuarios administrador

FRONTEND

Seguridad obligatoria en frontend

BACKEND

usuarios administrador

FRONTEND

Seguridad obligatoria en frontend

BACKEND

usuarios administrador

FRONTEND

Seguridad obligatoria en frontend

BACKEND

usuarios administrador

FRONTEND

Seguridad obligatoria en frontend

penaliza mucho el rendimiento

1 proyecto

2 aplicaciones

N módulos

frontend y backend

1 proyecto

1 aplicación

N bundles

Aplicación backend

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

$app['http_cache']->run();

web/index.php

Aplicación backend

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

$app['http_cache']->run();

web/index.php

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/backend.php';$app->run();

web/backend.php

Aplicación backend

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/app.php';require __DIR__.'/../config/prod.php';require __DIR__.'/../src/controllers.php';

$app['http_cache']->run();

web/index.php

require_once __DIR__.'/../vendor/autoload.php';

$app = require __DIR__.'/../src/backend.php';$app->run();

web/backend.php

src/backend.php$app = require __DIR__.'/app.php';

$app->register(new Silex\Provider\SecurityServiceProvider());// ... configurar seguridad ...

require __DIR__.'/../config/dev.php';require __DIR__.'/../src/controllers.php';

$app['monolog.logfile'] = __DIR__.'/../logs/backend.log';

$app->mount('/admin', new AdminControllerProvider());

return $app;

src/backend.php$app = require __DIR__.'/app.php';

$app->register(new Silex\Provider\SecurityServiceProvider());// ... configurar seguridad ...

require __DIR__.'/../config/dev.php';require __DIR__.'/../src/controllers.php';

$app['monolog.logfile'] = __DIR__.'/../logs/backend.log';

$app->mount('/admin', new AdminControllerProvider());

return $app;

src/backend.php$app = require __DIR__.'/app.php';

$app->register(new Silex\Provider\SecurityServiceProvider());// ... configurar seguridad ...

require __DIR__.'/../config/dev.php';require __DIR__.'/../src/controllers.php';

$app['monolog.logfile'] = __DIR__.'/../logs/backend.log';

$app->mount('/admin', new AdminControllerProvider());

return $app;

src/backend.php$app = require __DIR__.'/app.php';

$app->register(new Silex\Provider\SecurityServiceProvider());// ... configurar seguridad ...

require __DIR__.'/../config/dev.php';require __DIR__.'/../src/controllers.php';

$app['monolog.logfile'] = __DIR__.'/../logs/backend.log';

$app->mount('/admin', new AdminControllerProvider());

return $app;

src/backend.php$app = require __DIR__.'/app.php';

$app->register(new Silex\Provider\SecurityServiceProvider());// ... configurar seguridad ...

require __DIR__.'/../config/dev.php';require __DIR__.'/../src/controllers.php';

$app['monolog.logfile'] = __DIR__.'/../logs/backend.log';

$app->mount('/admin', new AdminControllerProvider());

return $app;

src/backend.php$app = require __DIR__.'/app.php';

$app->register(new Silex\Provider\SecurityServiceProvider());// ... configurar seguridad ...

require __DIR__.'/../config/dev.php';require __DIR__.'/../src/controllers.php';

$app['monolog.logfile'] = __DIR__.'/../logs/backend.log';

$app->mount('/admin', new AdminControllerProvider());

return $app;

/admin/admin/.../.../noticias/

usuarios

administrador

web/index.php

/.../noticias/

Aplicaciones Silex compartidas

web/backend.php

Ventajas de reutilizar aplicaciones• Sólo se aplica la seguridad en la

aplicación backend.

• El rendimiento de la parte pública no se ve afectado.

• En el backend están disponibles todas las rutas y controladores públicos.

MIDDLEWARES

Middlewares de aplicación

$app->before(function ($request) { // ...});

$app->after(function ($request, $response) { // ...});

$app->finish(function ($request, $response) { // ...});

Middlewares de controlador$before = function ($request) use ($app) { // ...};$after = function ($request, $response) use ($app) { // ...}; $app->get('...', function () { // ...})->before($before)->after($after);

El middleware before()public function before($callback, $priority = 0){ $this->on( KernelEvents::REQUEST, function ($event) use ($callback) { $ret = call_user_func($callback, $event->getRequest());

if ($ret instanceof Response) { $event->setResponse($ret); } }, $priority );}

El middleware before()public function before($callback, $priority = 0){ $this->on( KernelEvents::REQUEST, function ($event) use ($callback) { $ret = call_user_func($callback, $event->getRequest());

if ($ret instanceof Response) { $event->setResponse($ret); } }, $priority );}

public function on($eventName, $callback, $priority = 0){ // ... $dispatcher->addListener($eventName, $callback, $priority);}

Middlewares «obligatorios» $app->before(function() use ($app) { if (!$app['security']->isGranted('ROLE_ADMIN')) { return $app->redirect('/'); }});

Middlewares «prescindibles» $app->before(function(Request $request) { $request->query->set('token', '...'); $request->server->set('SERVER_ADDR', '::1');});

Mismo ejemplo sin middlewaresrequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );

$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

$app->run();

Mismo ejemplo sin middlewaresrequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );

$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

$app->run();

public function run(Request $request = null){ if (null === $request) { $request = Request::createFromGlobals(); } // ...}

Mismo ejemplo sin middlewaresrequire_once __DIR__.'/../vendor/autoload.php'; $app = new Silex\Application();

$app->register( ... );

$app->get('/', function() use($app) { return $app['twig']->render('portada.twig'),});

$request = Request::createFromGlobals();$request->query->set('token', '...');$request->server->set('SERVER_ADDR', '::1');

$app->run($request);

Middlewares «prescindibles» $app->before(function() use ($app) { $app['time.start'] = microtime(true);});

$app->after(function() use ($app) { $app['time.end'] = microtime(true);});

$app->finish(function() use ($app) { $elapsed = $app['time.start'] - $app['time.end']; $app['monolog']->addDebug(...);});

Mismo ejemplo «sin middlewares»$app->finish(function(Request $request) use ($app) { $elapsed = 1000 * ( microtime(true) - $request->server->get('REQUEST_TIME_FLOAT') );

$app['monolog']->addDebug(...);});

SERVICE PROVIDERS

Anatomía de un ServiceProvideruse Silex\ServiceProviderInterface;

class XXXServiceProvider implements ServiceProviderInterface{

public function register(Application $app) { $app['xxx'] = $app->share(function () use ($app) { // ... }); $app['yyy'] = $app->share(function () { // ... }); }

public function boot(Application $app) { $app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onEarlyKernelRequest'), 128); $app->before(function (Request $request) use ($app) { // ... }); $app->error(function (\Exception $e) use ($app) { // ... }, 255); $app->after(function (Request $request, Response $response) use ($app) { // ... }); }}

Anatomía de un ServiceProvideruse Silex\ServiceProviderInterface;

class XXXServiceProvider implements ServiceProviderInterface{

public function register(Application $app) { $app['xxx'] = $app->share(function () use ($app) { // ... }); $app['yyy'] = $app->share(function () { // ... }); }

public function boot(Application $app) { $app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onEarlyKernelRequest'), 128); $app->before(function (Request $request) use ($app) { // ... }); $app->error(function (\Exception $e) use ($app) { // ... }, 255); $app->after(function (Request $request, Response $response) use ($app) { // ... }); }}

defines los servicios

Anatomía de un ServiceProvideruse Silex\ServiceProviderInterface;

class XXXServiceProvider implements ServiceProviderInterface{

public function register(Application $app) { $app['xxx'] = $app->share(function () use ($app) { // ... }); $app['yyy'] = $app->share(function () { // ... }); }

public function boot(Application $app) { $app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onEarlyKernelRequest'), 128); $app->before(function (Request $request) use ($app) { // ... }); $app->error(function (\Exception $e) use ($app) { // ... }, 255); $app->after(function (Request $request, Response $response) use ($app) { // ... }); }}

defines los servicios

modificas la aplicación

Servicio básico$app['logger'] = function () { return new Logger();};

se crea una nueva instancia cada vez que lo usas

Servicio compartido$app['logger'] = $app->share(function () { return new Logger();}); se reutiliza la

misma instancia una y otra vez

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = function () { $logger = new Logger(); $logger->metodoXXX();

return $logger;};

lo que tengo

lo que quiero

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = $app->extend('logger', function($logger, $app) { $logger->metodoXXX();

return $logger;});

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = $app->extend('logger', function($logger, $app) { $logger->metodoXXX();

return $logger;});

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = $app->extend('logger', function($logger, $app) { $logger->metodoXXX();

return $logger;});

modifica este servicio

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = $app->extend('logger', function($logger, $app) { $logger->metodoXXX();

return $logger;});

modifica este servicio

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = $app->extend('logger', function($logger, $app) { $logger->metodoXXX();

return $logger;});

Extendiendo servicios$app['logger'] = function () { return new Logger();};

$app['logger'] = $app->extend('logger', function($logger, $app) { $logger->metodoXXX();

return $logger;});

lo que devuelva

Extendiendo servicios compartidos$app['logger'] = $app->share(function () { return new Logger();});

$app['logger'] = $app->share( $app->extend('logger', function($logger, $app) { $logger->metodoXXX(); return $logger;}));

Extendiendo servicios compartidos$app['logger'] = $app->share(function () { return new Logger();});

$app['logger'] = $app->share( $app->extend('logger', function($logger, $app) { $logger->metodoXXX(); return $logger;}));

Ejemplo extensión serviciosclass ServiceControllerServiceProvider implements ServiceProviderInterface{ public function register(Application $app) { $app['resolver'] = $app->share($app->extend('resolver', function ($resolver, $app) { return new ServiceControllerResolver($resolver, $app); })); }

public function boot(Application $app) { // ... }}

Ejemplo extensión serviciosclass ServiceControllerServiceProvider implements ServiceProviderInterface{ public function register(Application $app) { $app['resolver'] = $app->share($app->extend('resolver', function ($resolver, $app) { return new ServiceControllerResolver($resolver, $app); })); }

public function boot(Application $app) { // ... }}

SILEX 1.1

Silex 1.0

public function before($callback, $priority = 0){ $this['dispatcher']->addListener( KernelEvents::REQUEST, function (GetResponseEvent $event) use ($callback) { // ...}

Los métodos on(), before(), after(), error() fuerzan la creación del dispatcher y todas sus dependencias.

Lazy dispatcher

public function before($callback, $priority = 0){ $this['dispatcher'] = $this->share($this->extend('dispatcher', function ($dispatcher, $app) use ($callback, $priority) { $dispatcher->addListener( KernelEvents::REQUEST, $callback, $priority ); return $dispatcher; }));}

En Silex 1.1, los métodos on(), before(), after(), error() crean el dispatcher en el último momento.

SEGUNDA PARTE

CARACTERÍSTICASAL LÍMITE

CARACTERÍSTICASAL LÍMITE

INSTALACIÓN

No instales Silex así

composer.json has been updatedLoading composer repositories with package informationUpdating dependencies (including require-dev) - Installing psr/log (1.0.0) - Installing symfony/routing (v2.3.0) - Installing symfony/debug (v2.3.1) - Installing symfony/http-foundation (v2.3.1) - Installing symfony/event-dispatcher (v2.3.0) - Installing symfony/http-kernel (v2.3.0) - Installing pimple/pimple (v1.0.2) - Installing silex/silex (v1.0.0)

Writing lock fileGenerating autoload files

$ cd mi_proyecto$ composer require silex/silex 1.0

silex/silex 1.0

silex/silex 1.0

¿controladores?¿configuración?

¿index.php?

MICRO DOES NOT MEAN LESS CODE. MICRO MEANS LESS STRUCTURE AND LESS DECISIONS MADE FOR YOU.FABIEN POTENCIERCREADOR DE SYMFONY, SILEX Y TWIG

Instala una «distribución» de Silex

$ composer create-project fabpot/Silex-Skeleton mi_proyecto 1.0 - Installing psr/log (1.0.0) - Installing twig/twig (v1.13.1) - Installing symfony/icu (v1.2.0) - Installing symfony/intl (v2.3.1) - Installing symfony/stopwatch (v2.3.1) - Installing symfony/twig-bridge (v2.3.1) - Installing symfony/routing (v2.3.0) - Installing symfony/debug (v2.3.1) - Installing symfony/http-foundation (v2.3.1) - Installing symfony/event-dispatcher (v2.3.0) - Installing symfony/http-kernel (v2.3.0) - Installing symfony/web-profiler-bundle (v2.3.1) - Installing pimple/pimple (v1.0.2) - Installing silex/silex (1.0.x-dev a6dde11) - Installing silex/web-profiler (v1.0.0) - Installing symfony/dom-crawler (v2.3.1)

- Installing symfony/browser-kit (v2.3.1) - Installing symfony/class-loader (v2.3.1) - Installing symfony/filesystem (v2.3.1) - Installing symfony/config (v2.3.1) - Installing symfony/console (v2.3.1) - Installing symfony/css-selector (v2.3.0) - Installing symfony/finder (v2.3.1) - Installing symfony/property-access (v2.3.1) - Installing symfony/options-resolver (v2.3.1) - Installing symfony/form (v2.3.1) - Installing monolog/monolog (1.5.0) - Installing symfony/monolog-bridge (v2.3.1) - Installing symfony/process (v2.3.1) - Installing symfony/security (v2.3.1) - Installing symfony/translation (v2.3.0) - Installing symfony/validator (v2.3.1)

fabpot/Silex-Skeleton

fabpot/Silex-Skeleton

Controladores frontales

fabpot/Silex-Skeleton

Tu código

fabpot/Silex-Skeleton

Plantillas

fabpot/Silex-Skeleton Configuración y registro de servicios

fabpot/Silex-Skeleton

Igual que Symfony2

Silex Kitchen Sink Edition

$ composer create-project lyrixx/Silex-Kitchen-Edition mi_proyecto 1.0

Silex Kitchen Sink Edition

+ + + AsseticDoctrine

Silex HTML5 Boilerplate

Twitter Bootstrap

$ composer create-project lyrixx/Silex-Kitchen-Edition mi_proyecto 1.0

mini truco

Controladores frontales

index.php index_dev.php

Controladores frontales

index.php index_dev.php

Para las aplicaciones complejas usamos seis entornos de ejecución:

DEV PROD TEST BETA STAGING ROLLOUT

Fuente: http://37signals.com/svn/posts/3535-beyond-the-default-rails-environments

37signals

Controladores frontales

index.php index_dev.php

prod.php dev.php

Controladores frontales

index.php index_dev.php

prod.php dev.php beta.php staging.php

TRAITS

TRAITS IS JUST COMPILER-ASSISTED COPY-AND-PASTE

STEFAN MARRSOFTWARE LANGUAGES LAB / VRIJE UNIVERSITEIT

Traits en PHPtrait metodosComunes { function metodo1() { ... } function metodo2() { ... }}

class miClase { use metodosComunes; // ...}

Traits en PHPtrait metodosComunes { function metodo1() { ... } function metodo2() { ... }}

class miClase { use metodosComunes; // ...}

$objeto = new MiClase();$objeto->metodo1();$objeto->metodo2();

Traits en PHPtrait metodosComunes { function metodo1() { ... } function metodo2() { ... }}

class miClase { use metodosComunes; // ...}

$objeto = new MiClase();$objeto->metodo1();$objeto->metodo2();

PHP 5.4o superior

FormTraitMonologTrait

SecurityTrait

SwiftmailerTrait

TranslationTrait

TwigTraitUrlGeneratorTrait

traits

Traits en aplicaciones Silexrequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );$app->get(...);

$app->run();

Traits en aplicaciones Silexrequire_once __DIR__.'/../vendor/autoload.php';

$app = new Silex\Application();

$app->register( ... );$app->get(...);

$app->run();

class Application extends Silex\Application { use Silex\Application\TwigTrait; use Silex\Application\UrlGeneratorTrait;}

$app = new Application();

Sin usar UrlGeneratorTrait$app->get('/', function () use ($app) { $url = $this['url_generator'] ->generate($ruta, $params);

$urlAbsoluta = $this['url_generator'] ->generate($ruta, $params, true);});

Usando UrlGeneratorTrait$app->get('/', function () use ($app) {

$url = $app->path($ruta, $params);

$urlAbsoluta = $app->url($ruta, $params);

});

Sin usar TwigTrait$app->get('/', function () use ($app) { return new Response( $app['twig']->render('plantilla.twig', $params), 200, ['Cache-Control' => 'public, s-maxage=3600'] );});

Usando TwigTrait$app->get('/', function () use ($app) { return $app->render( 'plantilla.twig', $params, (new Response)->setTtl(3600) );});

RESPONSIBLE CONTROLLERS

APIs con múltiples formatos$app->get('/api/...', function(Request $request) { $datos = ...

if ('json' == $request->getRequestFormat()) { return new JsonResponse($datos); } elseif ('xml' == $request->getRequestFormat()) { return new Response( $datosEnXml, 200, ['Content-Type' => 'application/xml'] ); }});

APIs con múltiples formatos$app->get('/api/...', function(Request $request) { $datos = ...

return $datos;});

Tobias Sjösten

github.com/tobiassjosten/ResponsibleServiceProvider

ResponsibleServiceProviderFormateo automágico de la respuesta en función del formato de la petición (JSON o XML).

ResponsibleServiceProvider

$ composer require tobiassjosten/ResponsibleServiceProvider

$app->register(new Tobiassjosten\ Silex\ResponsibleServiceProvider());

APIs con múltiples formatos$app->get('/api/...', function(Request $request) { $datos = ...

return $datos;});

ResponsibleServiceProviderclass ResponsibleServiceProvider implements ServiceProviderInterface{ public function register(Application $app) { if (empty($app['serializer'])) { $app->register(new SerializerServiceProvider()); } }

public function boot(Application $app) { $app['dispatcher']->addSubscriber( new ResponsibleListener($app['serializer']) ); }}

ResponsibleListenerclass ResponsibleListener implements EventSubscriberInterface{ public static function getSubscribedEvents() {

return array(KernelEvents::VIEW => array('onKernelView', -10)); }

public function onKernelView(GetResponseForControllerResultEvent $event) {

$result = $event->getControllerResult();

$supported = array('json', 'xml'); $accepted = $request->getAcceptableContentTypes(); // ...

$event->setResponse(new Response( $this->encoder->encode($result, $format), 200, array('Content-Type' => $type) ));

// ...

ResponsibleListenerclass ResponsibleListener implements EventSubscriberInterface{ public static function getSubscribedEvents() {

return array(KernelEvents::VIEW => array('onKernelView', -10)); }

public function onKernelView(GetResponseForControllerResultEvent $event) {

$result = $event->getControllerResult();

$supported = array('json', 'xml'); $accepted = $request->getAcceptableContentTypes(); // ...

$event->setResponse(new Response( $this->encoder->encode($result, $format), 200, array('Content-Type' => $type) ));

// ...

ResponsibleListenerclass ResponsibleListener implements EventSubscriberInterface{ public static function getSubscribedEvents() {

return array(KernelEvents::VIEW => array('onKernelView', -10)); }

public function onKernelView(GetResponseForControllerResultEvent $event) {

$result = $event->getControllerResult();

$supported = array('json', 'xml'); $accepted = $request->getAcceptableContentTypes(); // ...

$event->setResponse(new Response( $this->encoder->encode($result, $format), 200, array('Content-Type' => $type) ));

// ...

ResponsibleListenerclass ResponsibleListener implements EventSubscriberInterface{ public static function getSubscribedEvents() {

return array(KernelEvents::VIEW => array('onKernelView', -10)); }

public function onKernelView(GetResponseForControllerResultEvent $event) {

$result = $event->getControllerResult();

$supported = array('json', 'xml'); $accepted = $request->getAcceptableContentTypes(); // ...

$event->setResponse(new Response( $this->encoder->encode($result, $format), 200, array('Content-Type' => $type) ));

// ...

CONFIGURACIÓN

Configuración en Silex-Skeleton

$app['db.options'] = array( 'dbname' => '...', 'user' => '...', 'password' => '...');$app['debug'] = false;

config/prod.php

config/dev.phprequire __DIR__.'/prod.php';

$app['db.options'] = array( 'dbname' => '...', 'user' => '...', 'password' => '...');$app['debug'] = true;

Igor Wiedler

github.com/igorw/ConfigServiceProvider

ConfigServiceProviderAñade un servicio de configuración a Silex con soporte para archivos YAML, JSON, PHP, TOML.

igorw / ConfigServiceProvider

$ composer require igorw/ConfigServiceProvider

$app->register(new Igorw\Silex\ ConfigServiceProvider("config.json"));

igorw / ConfigServiceProvider

$ composer require igorw/ConfigServiceProvider

$app->register(new Igorw\Silex\ ConfigServiceProvider("config.json"));

{ "debug": true}

$app['debug'] = true;

"config.yml""config.php""config.toml"

$app->register(new Igorw\Silex\ ConfigServiceProvider("config.json"));

Múltiples formatos

$app->register(new Igorw\Silex\

ConfigServiceProvider("config.yml"));$app->register(new Igorw\Silex\

ConfigServiceProvider("params.php"));$app->register(new Igorw\Silex\

ConfigServiceProvider("dev.json"));

Múltiples archivos

$app->register(new Igorw\Silex\ConfigServiceProvider( __DIR__."/config/config.json", array('debug' => true, 'version' => '2')));

Configurar en ejecución

config/config.json{ "enable_cache": %debug%, "api_endpoint": "http://api.acme.org/%version%"}

$app->register(new Igorw\Silex\ConfigServiceProvider( __DIR__."/config/config.json", array('debug' => true, 'version' => '2')));

Configurar en ejecución

config/config.json{ "enable_cache": %debug%, "api_endpoint": "http://api.acme.org/%version%"}

$app->register(new Igorw\Silex\ConfigServiceProvider( __DIR__."/config/config.json", array('debug' => true, 'version' => '2')));

Configurar en ejecución

config/config.json{ "enable_cache": %debug%, "api_endpoint": "http://api.acme.org/%version%"}

public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load( __DIR__.'/config/config_'.$this->getEnvironment().'.yml' );}

Configuración por entorno (Sf2)app/AppKernel.php

public function getEnvironment(){ return $this->container->getParameter('kernel.environment');}

$env = getenv('APP_ENV') ?: 'prod';$app->register(new Igorw\Silex\ConfigServiceProvider( __DIR__."/../config/$env.json"));

Configuración por entorno (Silex)app.php

httpd-vhosts.conf<VirtualHost *:80> ServerName acme.dev SetEnv APP_ENV dev ...</VirtualHost>

EXCEPCIONES

Excepciones clásicas en Silex$app->error(function (\Exception $e, $code) use ($app) { if ($app['debug']) { return; }

$page = 404 == $code ? '404.html' : '500.html';

return new Response( $app['twig']->render($page, array('code' => $code)), $code );});

Excepciones clásicas en Silex$app->error(function (\Exception $e, $code) use ($app) { if ($app['debug']) { return; }

$page = 404 == $code ? '404.html' : '500.html';

return new Response( $app['twig']->render($page, array('code' => $code)), $code );});

INBOXStarredDraftsAll mailSpam

INBOXStarredDraftsAll mailSpam

1

INBOXStarredDraftsAll mailSpam

12

Google y los códigos de estado

50X40X20X

Excepciones que devuelven 404$app->error(function (\Exception $e, $code) use ($app) { if ($app['debug']) { return; }

$page = 404 == $code ? '404.html' : '500.html';

return new Response( $app['twig']->render($page, array('code' => $code)),

404 );});

APLICACIONES DESACOPLADAS

APLICACIÓN PHP

SILEXSILEX

TU APLICACIÓN

Aplicaciones acopladasdependes totalmente de Silex

SILEX

TU APLICACIÓN

SILEX

APLICACIÓN PHP

Aplicaciones acopladasdependes totalmente de Silex aplicación PHP

desacoplada de Silex

Controladores Silex clásicos$app->get('/', function() use($app) { // ...

return $app['twig']->render('portada.twig');});

Controladores Silex en clases$app->get('/', 'Acme\Controladores::index');

use Silex\Application;use Symfony\Component\HttpFoundation\Request; namespace Acme { class Controladores { public function index(Application $app, Request $request) { // ... return $app['twig']->render('portada.twig'), } }}

Controladores Silex en clases$app->get('/', 'Acme\Controladores::index');

use Silex\Application;use Symfony\Component\HttpFoundation\Request; namespace Acme { class Controladores { public function index(Application $app, Request $request) { // ... return $app['twig']->render('portada.twig'), } }}

Controladores Silex en clases$app->get('/', 'Acme\Controladores::index');

use Silex\Application;use Symfony\Component\HttpFoundation\Request; namespace Acme { class Controladores { public function index(Application $app, Request $request) { // ... return $app['twig']->render('portada.twig'), } }}

Controladores Silex en servicios$app->get('/', "controladores:indexAction");use Acme\Controladores;

$app['controladores'] = $app->share(function(Request $request) use ($app) { return new Controladores($app, $request);});

use Silex\Application;use Symfony\Component\HttpFoundation\Request; namespace Acme { class Controladores { protected $app; protected $request;

public function __construct(Application $app, Request $request) { $this->app = $app; $this->request = $request; }

public function indexAction() { // ...

return $app['twig']->render('portada.twig'), } }}

Controladores Silex en servicios$app->get('/', "controladores:indexAction");use Acme\Controladores;

$app['controladores'] = $app->share(function(Request $request) use ($app) { return new Controladores($app, $request);});

use Silex\Application;use Symfony\Component\HttpFoundation\Request; namespace Acme { class Controladores { protected $app; protected $request;

public function __construct(Application $app, Request $request) { $this->app = $app; $this->request = $request; }

public function indexAction() { // ...

return $app['twig']->render('portada.twig'), } }}

Controladores Silex en servicios$app->get('/', "controladores:indexAction");use Acme\Controladores;

$app['controladores'] = $app->share(function(Request $request) use ($app) { return new Controladores($app, $request);});

use Silex\Application;use Symfony\Component\HttpFoundation\Request; namespace Acme { class Controladores { protected $app; protected $request;

public function __construct(Application $app, Request $request) { $this->app = $app; $this->request = $request; }

public function indexAction() { // ...

return $app['twig']->render('portada.twig'), } }}

La aplicación no está desacoplada

La aplicación sigue dependiendo de los objetos de Silex ($app, $request). class Controladores { public function __construct(Application $app, Request $request) { $this->app = $app; $this->request = $request; } public function indexAction() { // ... return $app['twig']->render('portada.twig'), }}

La aplicación no está desacoplada

La aplicación sigue dependiendo de los objetos de Silex ($app, $request). class Controladores { public function __construct(Application $app, Request $request) { $this->app = $app; $this->request = $request; } public function indexAction() { // ... return $app['twig']->render('portada.twig'), }}

IgorWiedler

github.com/igorw/doucheswag

DaveMarshall

speakerdeck.com/igorw/silex-an-implementation-detail

TU APLICACIÓN SILEXSILEX

TU APLICACIÓN PHP

Aplicaciones desacopladas

SILEX

TU APLICACIÓN PHP

Aplicaciones desacopladas

TU APLICACIÓN SILEX

desacoplada de Silex

interfaz entre tu aplicación y Silex

Controlador clásico$app->get('/auction/{id}', function() use($app) { $datos = $this->app['db']->fetchAssoc(...);

return $app['twig']->render('auction_view.twig');});

Controlador desacoplado$app->get('/auction/{id}', 'interactor.auction_view') ->value('controller', 'auction_view') ->convert('request', function ($_, Request $request) { $id = $request->attributes->get('id') return new AuctionViewRequest($id); });

Controlador desacoplado$app->get('/auction/{id}', 'interactor.auction_view') ->value('controller', 'auction_view') ->convert('request', function ($_, Request $request) { $id = $request->attributes->get('id') return new AuctionViewRequest($id); });

1

2

3

1. Controladores como servicios$app->get('/auction/{id}', 'interactor.auction_view')

$app['resolver'] = $app->share($app->extend('resolver', function ($resolver, $app) { $resolver = new ControllerResolver($resolver, $app);

return $resolver;}));

1. ControllerResolver propioclass ControllerResolver implements ControllerResolverInterface{ public function getController(Request $request) { $controller = $request->attributes->get('_controller');

if (!is_string($controller) || !isset($this->container[$controller])) { return $this->resolver->getController($request); }

return $this->container[$controller]; }}

1. Definiendo los controladoresclass ServiceProvider implements ServiceProviderInterface{ public function register(Application $app) { $app['interactor.auction_view'] = $app->share(function () { return new AuctionView( ... ); });

// ... }

public function boot(Application $app) { ... }}

1. Definiendo los controladoresclass ServiceProvider implements ServiceProviderInterface{ public function register(Application $app) { $app['interactor.auction_view'] = $app->share(function () { return new AuctionView( ... ); });

// ... }

public function boot(Application $app) { ... }}

clase de mi aplicación PHP

1. AuctionViewclass AuctionView{ public function __construct(AuctionRepository $repo) { // ... }

public function __invoke(AuctionViewRequest $request) { // ... }}

1. AuctionViewclass AuctionView{ public function __construct(AuctionRepository $repo) { // ... }

public function __invoke(AuctionViewRequest $request) { // ... }}

método mágico:objeto()

1. AuctionViewclass AuctionView{ // ...

public function __invoke(AuctionViewRequest $request) { $auction = $this->repo->find($request->id);

$view = AuctionViewDto::fromEntity($auction);

return new AuctionViewResponse($view); }}

1. AuctionViewclass AuctionView{ // ...

public function __invoke(AuctionViewRequest $request) { $auction = $this->repo->find($request->id);

$view = AuctionViewDto::fromEntity($auction);

return new AuctionViewResponse($view); }}

Request()

1. AuctionViewclass AuctionView{ // ...

public function __invoke(AuctionViewRequest $request) { $auction = $this->repo->find($request->id);

$view = AuctionViewDto::fromEntity($auction);

return new AuctionViewResponse($view); }}

Request()

Doctrine

1. AuctionViewclass AuctionView{ // ...

public function __invoke(AuctionViewRequest $request) { $auction = $this->repo->find($request->id);

$view = AuctionViewDto::fromEntity($auction);

return new AuctionViewResponse($view); }}

Request()

Doctrine

Twig

Desacoplando aplicaciones

TU APLICACIÓN PHP

AuctionViewResponse

AuctionViewRequestAuctionViewRequest

Controlador desacoplado$app->get('/auction/{id}', 'interactor.auction_view') ->value('controller', 'auction_view') ->convert('request', function ($_, Request $request) { $id = $request->attributes->get('id') return new AuctionViewRequest($id); });

1

2

3

2. Usando mis propios objetos$app->...->convert('request', function ($_, Request $request) { return new AuctionViewRequest( $request->attributes->get('id') );});

2. Usando mis propios objetos$app->...->convert('request', function ($_, Request $request) { return new AuctionViewRequest( $request->attributes->get('id') );});

Silex

2. Usando mis propios objetos$app->...->convert('request', function ($_, Request $request) { return new AuctionViewRequest( $request->attributes->get('id') );});

Silex

Mi aplicación PHP

2. AuctionViewRequestclass AuctionViewRequest { public $id;

public function __construct($id) { $this->id = $id; } }

Controlador desacoplado$app->get('/auction/{id}', 'interactor.auction_view') ->value('controller', 'auction_view') ->convert('request', function ($_, Request $request) { $id = $request->attributes->get('id') return new AuctionViewRequest($id); });

1

2

3

3. Generando la respuesta$app->... ->value('controller', 'auction_view')

3. Generando la respuesta$app->... ->value('controller', 'auction_view')

¿?¿?¿?

3. Generando la respuesta$app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { // ...

$view = $event->getControllerResult(); $controller = $request->attributes->get('controller'); $template = "$controller.html";

$view = (object) $view;

$body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); });

return $dispatcher;}));

3. Generando la respuesta$app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { // ...

$view = $event->getControllerResult(); $controller = $request->attributes->get('controller'); $template = "$controller.html";

$view = (object) $view;

$body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); });

return $dispatcher;}));

3. Generando la respuesta$app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { // ...

$view = $event->getControllerResult(); $controller = $request->attributes->get('controller'); $template = "$controller.html";

$view = (object) $view;

$body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); });

return $dispatcher;}));

3. Generando la respuesta$app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { // ...

$view = $event->getControllerResult(); $controller = $request->attributes->get('controller'); $template = "$controller.html";

$view = (object) $view;

$body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); });

return $dispatcher;}));

3. Generando la respuesta$app['dispatcher'] = $app->share($app->extend('dispatcher', function ($dispatcher, $app) { $dispatcher->addListener(KernelEvents::VIEW, function ($event) use ($app) { // ...

$view = $event->getControllerResult(); $controller = $request->attributes->get('controller'); $template = "$controller.html";

$view = (object) $view;

$body = $app['mustache']->render($template, $view); $response = new Response($body); $event->setResponse($response); });

return $dispatcher;}));

Una visión general

srcDouche

DoucheWeb

Una visión general

srcDouche

DoucheWebSilex

PHP

Una visión general

DoucheWebapp.php

views

ControllerResolver.php

ServiceProvider.php

Una visión general

DoucheEntity ExceptionInteractor Repository

Service Storage

Value View

SINGLE-FILE APPLICATIONS

Inline templates (Ruby / Sinatra)require 'sinatra'

get '/' do haml :indexend

__END__

@@ layout%html = yield

@@ index%div.title Hello world.

Inline templates (Ruby / Sinatra)require 'sinatra'

get '/' do haml :indexend

__END__

@@ layout%html = yield

@@ index%div.title Hello world.

controladores, rutas y plantillas en 1 solo archivo

Inline templates (Ruby / Sinatra)require 'sinatra'

get '/' do haml :indexend

__END__

@@ layout%html = yield

@@ index%div.title Hello world.

controladores, rutas y plantillas en 1 solo archivo

Justin Hileman

github.com/bobthecow/mustache-silex-provider

mustache / silex-providerPermite utilizar plantillas Mustache en Silex, incluyendo soporte para las inline templates.

__halt_compiler()

__halt_compiler() en PHAR<?php/* This file is part of the Silex framework. (c) Fabien Potencier <[email protected]> */

Phar::mapPhar('silex.phar');require_once 'phar://silex.phar/vendor/autoload.php';

// ...

__halt_compiler(); ?>'j

silex.phar&src/Silex/ExceptionListenerWrapper.php.콩Q.܎Ŷsrc/Silex/HttpCache.php¡ì½©Q¡•¯2—³¶!src/Silex/Route/SecurityTrait.php:콩Q:ŽsµÌ¶ src/Silex/ControllerResolver.php

콩QÄ–±2¶src/Silex/WebTestCase.php»ì½©Q»ÝW\¹¶src/Silex/Controller.php8

콩Q8

código PHP que se ejecuta

datos de la aplicación

Inline templates (PHP / Silex)$app->get('...', function() use ($app) { // ...});

__halt_compiler();

@@ portada<ul>{{#secciones}}<li> <a href="{{ enlace }}">{{ titulo }}</a></li>{{/secciones}}</ul>

@@ categoria<h1>{{ titulo }}</h1>

controladores

plantillaportada

plantillacategoria

mustache / silex-provider

$ composer require mustache/silex-provider 1.0

$app->register(

new \Mustache\Silex\Provider\MustacheServiceProvider, array(

'mustache.loader' => new Mustache_Loader_InlineLoader(

__FILE__, __COMPILER_HALT_OFFSET__ )));

mustache / silex-provider

$ composer require mustache/silex-provider 1.0

$app->register(

new \Mustache\Silex\Provider\MustacheServiceProvider, array(

'mustache.loader' => new Mustache_Loader_InlineLoader(

__FILE__, __COMPILER_HALT_OFFSET__ )));

este mismo archivo

mustache / silex-provider

$ composer require mustache/silex-provider 1.0

$app->register(

new \Mustache\Silex\Provider\MustacheServiceProvider, array(

'mustache.loader' => new Mustache_Loader_InlineLoader(

__FILE__, __COMPILER_HALT_OFFSET__ )));

este mismo archivo

el lugar en el que se encuentre la llamada a __halt_compiler()

Single-File Application$app->get('/', function() use ($app) { return $app['mustache']->render('portada', array(‘secciones’ => array(...)));});$app->get('/{slug}', function($slug) use ($app) { return $app['mustache']->render('categoria', array(‘titulo’ => $slug));});

__halt_compiler();

@@ portada<ul>{{#secciones}} <li><a href="{{ enlace }}">{{ titulo }}</a></li>{{/secciones}}</ul>

@@ categoria<h1>{{ titulo }}</h1>

Single-File Application$app->get('/', function() use ($app) { return $app['mustache']->render('portada', array(‘secciones’ => array(...)));});$app->get('/{slug}', function($slug) use ($app) { return $app['mustache']->render('categoria', array(‘titulo’ => $slug));});

__halt_compiler();

@@ portada<ul>{{#secciones}} <li><a href="{{ enlace }}">{{ titulo }}</a></li>{{/secciones}}</ul>

@@ categoria<h1>{{ titulo }}</h1>

Mustache_Loader_InlineLoaderclass Mustache_Loader_InlineLoader implements Mustache_Loader

{ // ...

protected function loadTemplates() { $this->templates = array(); $data = file_get_contents($this->fileName, false, null, $this->offset); foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) { if (trim($chunk)) { list($name, $content) = explode("\n", $chunk, 2); $this->templates[trim($name)] = trim($content); } } }}

Referencias

GRACIAS.

PREGUNTASpuedes valorar esta charla en

joind.in/talk/view/8851

Contacto

Javier Eguiluz

javiereguiluz.com

twitter.com/javiereguiluz

github.com/javiereguiluz

linkedin.com/in/javiereguiluz