Share
Embed
Taming Runaway Silex Apps - SymfonyCon Warsaw 2013
These may not stand up too well on their own, audio recording will be available from SensioLabs sometime soon.
Dave Marshall
December 13, 2013
Transcript
132 slides
-
$app['kernel'] = function ($app) { return new HttpKernel( $app['dispatcher'], $app['resolver'],
$app['request_stack'] ); }; -
class Pimple implements ArrayAccess { function share($callable); function protect($callable); function
raw($id); function extend($id, $callable); function keys(); } -
class Pimple implements ArrayAccess { function share($callable); function protect($callable); function
raw($id); function extend($id, $callable); function keys(); } -
// user.service depends on user.repository $app['user.service'] = $app->share(function($app) { return
new UserService($app['user.repository']); }); -
// user.repository is redefined/extended $app['user.repository'] = $app->share($app->extend( 'user.repository', function ($repo)
{ return new CachingRepository($repo); } )); // but user.service already has the uncached version $app['user.service']-> .... -
Do not access any services until all services have been
extended as required -
Not everything has to be defined as a service Extract
when necessary -
$app['sw.service'] = $app->share(function($app) { return new Stopwords\Service( $app['sw.repo'] ); });
$app['sw.repo'] = $app->share(function($app) { return new Stopwords\Repository($app['db']); }); -
<?php namespace Acme\Common; trait PimpleAutoWiring { public function bindType($type, $service)
{ $this->boundTypes[$type] = $service; } public function resolve($type, array $fixedArgs = array()) { $boundTypes = &$this->boundTypes; return function ($app) use ($type, &$boundTypes, $fixedArgs) { $rfc = new \ReflectionClass($type); $ctor = $rfc->getConstructor(); $args = array(); foreach ($ctor->getParameters() as $param) { $classHint = $param->getClass()->getName(); if ($classHint) { if (isset($boundTypes[$classHint])) { $args[] = $app[$boundTypes[$classHint]]; } else if (isset($app[$classHint])) { $args[] = $app[$classHint]; } else if (isset($app[$param->getName()])) { $args[] = $app[$param->getName()]; } else { throw new \RuntimeException("Could not resolve service for $classHint"); } } else { if (isset($fixedArgs[$param->getName()])) { $args[] = $fixedArgs[$param->getName]; } else { throw new \RuntimeException("Could not resolve parameter for {$param->getName} for $type"); } } } return $rfc->newInstanceArgs($args); }; } } ~50 slocs Could do with some caching -
// Acme\User\Service\Authentication public function __construct(UserRepository $userRepo) { $this->repo = $userRepo;
} // usage $app->bindType( 'Acme\User\Repository\UserRepository', 'user.repository' ); $app['user.service'] = $app->resolve( 'Acme\User\Service\Authentication' ); -
$app = new Silex\Application; $app->get("/hello/{name}", function ($name, $app) { return
"Hello" . $app->escape($name); }); $app->run(); -
function get($pattern, $to = null); function post($pattern, $to = null);
function put($pattern, $to = null); function delete($pattern, $to = null); function match($pattern, $to = null); -
function get($pattern, $to = null); function post($pattern, $to = null);
function put($pattern, $to = null); function delete($pattern, $to = null); function match($pattern, $to = null); -
function before($callback, $priority = 0); function after($callback, $priority = 0);
function finish($callback, $priority = 0); function error($callback, $priority = -8); function on($eventName, $callback, $priority = 0); -
function before($callback, $priority = 0); function after($callback, $priority = 0);
function finish($callback, $priority = 0); function error($callback, $priority = -8); function on($eventName, $callback, $priority = 0); -
Silex defines a range of services which can be used
or replaced You probably don't want to mess with most of them -
web/ # assets + front controllers app/ # app config/bootstrap
src/ # app code bin/ # cli scripts + binstubs -
<?php // app/app.php require __DIR__.'/../vendor/autoload.php'; $app = new Silex\Application(); //
config... // services $app['user.service'] = function() {}; // routes $app->get('/', function() { return 'Hello'; } return $app; -
> export APPLICATION_ENV=production > psysh app/app.phpT Psy Shell v0.1.0-dev (PHP
5.4.9 — cli) by Justin Hileman >>> $app[‘user.service’]->deleteAll(... -
namespace Silex; interface ServiceProviderInterface { public function register(Application $app); public
function boot(Application $app); } -
boot() is for doing things before handling a request Usually
adding event listeners -
// Acme/Silex/ControllerProvider public function connect(Application $app) { $controllers = $app['controllers_factory'];
$controllers->get( '/hello/{name}', function ($name, Application $app) { return 'Hello'.$app->escape($name); } )->bind('site.hello_world'); return $controllers; } // app/app.php $app->mount('/', new SiteControllerProvider()); -
namespace Acme\User; use Silex\Application; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; class Provider
implements ControllerProviderInterface, ServiceProviderInterface { public function register(Application $app) {} public function boot(Application $app) {} public function connect(Application $app) {} } -
// Acme\User\Module public static function register(Application $app) { $app->register(new ServiceProvider());
$app->mount('/', new ControllerProvider()); $app->mount('/', new AdminControllerProvider()); } // app/app.php Acme\User\Module::register($app); -
$app->get( '/hello/{name}', function ($name, Application $app) { return "Hello" .
$app->escape($name); } )->bind('site.welcome'); -
function welcome($name, Application $app) { return "Hello " . $app->escape($name);
}; $app->get('/hello/{name}', 'welcome') ->bind('site.welcome'); -
// Acme\Controller\SiteController public function welcome($name, Application $app) { return 'Hello
' . $app->escape($name); } // Acme\ControllerProvider.php $app->get( '/hello/{name}', 'Acme\Controller\SiteController::welcome' )->bind('site.welcome'); -
// class BaseControllerResolver list($class, $method) = explode('::', $controller, 2); $controller
= new $class(); if ($controller instanceof BaseController) { $controller->setApplication($app); } return array($controller, $method); -
$app['resolver'] = $this->share($app->extend( 'resolver', function ($resolver, $app) { return new
BaseControllerResolver( $resolver, $app ); } )); -
// abstract class BaseController public function setApplication(Application $app) { $this->app
= $app; } public function get($key) { return $this->app[$key]; } -
// abstract class BaseController public function render($template, array $context) {
return $this ->get('twig') ->render($template, $context); } -
class SiteController extends BaseController { public function welcome($name) { $user
= $this ->get('user.repository') ->findByName($name); return $this->render( 'user.html.twig', [ 'user' => $user, 'name' => $name, ] ); } } -
class SiteController extends BaseController { public function welcome($name) { $user
= $this ->get('user.repository') ->findByName($name); return $this->render( 'user.html.twig', [ 'user' => $user, 'name' => $name, ] ); } } -
class SiteController extends BaseController { public function welcome($name) { $user
= $this ->get('user.repository') ->findByName($name); return $this->render( 'user.html.twig', [ 'user' => $user, 'name' => $name, ] ); } } -
use Silex\Provider\ServiceControllerServiceProvider; $app->register( new ServiceControllerServiceProvider(), ); $app['site.controller'] = $app->share(function($app) {
return new Acme\Controller\SiteController( $app['user.repository'], $app['twig'] ); }); $app->get('/hello/{name}', 'site.controller:welcome') ->bind('site.welcome'); -
use Silex\Provider\ServiceControllerServiceProvider; $app->register( new ServiceControllerServiceProvider(), ); $app['site.controller'] = $app->share(function($app) {
return new Acme\Controller\SiteController( $app['user.repository'], $app['twig'] ); }); $app->get('/hello/{name}', 'site.controller:welcome') ->bind('site.welcome'); -
use Silex\Provider\ServiceControllerServiceProvider; $app->register( new ServiceControllerServiceProvider(), ); $app['site.controller'] = $app->share(function($app) {
return new Acme\Controller\SiteController( $app['user.repository'], $app['twig'] ); }); $app->get('/hello/{name}', 'site.controller:welcome') ->bind('site.welcome'); -
// Acme\Controller\SiteController public function __construct($repo, $twig) { $this->repo = $repo;
$this->twig = $twig; } public function welcome($name) { $user = $this->repo->find($name); return $this->twig->render( 'user.html.twig', [ 'user' => $user, 'name' => $name ] ); } -
// Acme\Controller\SiteController public function __construct($repo, $twig) { $this->repo = $repo;
$this->twig = $twig; } public function welcome($name) { $user = $this->repo->find($name); return $this->twig->render( 'user.html.twig', [ 'user' => $user, 'name' => $name, ] ); } -
Name Method Pattern Controller Method users.index GET /users indexAction users.new
GET /users/new newAction users.create POST /users newAction users.show GET /users/{id} showAction($id) users.edit GET /users/{id}/edit editAction($id) users.update PUT /users/{id} editAction($id) users.delete DELETE /users/{id} deleteAction($id) -
function resource($path, $ns, $service, $app) { $app->get($path, "$service:indexAction") ->bind("$ns.index"); $app->get($path."/{id}",
"$service:showAction") ->bind("$ns.show"); $app->put($path."/{id}", "$service:updateAction") ->bind("$ns.update"); /* ... */ } resource("/users", "users", "users.controller", $app); resource("/posts", "posts", "posts.controller", $app); -
Silex doesn’t use the full Router, only the UrlMatcher Routes
are “recompiled” for every request -
Laravel 4.1 features a totally re-written routing layer. The API
is the same; however, registering routes is a full 100% faster compared to 4.0. The entire engine has been greatly simplified, and the dependency on Symfony Routing has been removed. -
Replaces the UrlMatcher with the full Router At the cost
of some flexibility -
function lazy($app, $service, $method) { return function() use ($app, $service,
$method) { return call_user_func_array( [$app[$service], $method], func_get_args() ); }; } $app->on( AppEvents::USER_UPGRADE, lazy($app, "user.event_logger", "onUserEvent") ); -
$app['dispatcher'] = $app->share($app->extend( 'dispatcher', function($dispatcher) use ($app) { return new
PimpleAwareEventDispatcher( $dispatcher, $app ); } )); $app['dispatcher']->addSubscriberService( “user.event_logger", "Acme\User\EventLogger" ); -
// Acme\Controller\UserController public function showAction($id) { $user = $this->repo->find($id); return
['user' => $user]; } $app->get('/user/{id}', 'user.controller:showAction') ->value('template', 'user.html.twig'); -
// Acme\Controller\UserController public function showAction($id) { $user = $this->repo->find($id); return
['user' => $user]; } $app->get('/user/{id}', 'user.controller:showAction') ->value('template', 'user.html.twig'); -
// Acme\Controller\UserController public function showAction($id) { $user = $this->repo->find($id); return
['user' => $user]; } $app->get('/user/{id}', 'user.controller:showAction') ->value('template', 'user.html.twig'); -
$app->on(KernelEvents::VIEW, function ($e) use ($app) { $view = $e->getControllerResult(); if
(!is_array($view)) { return; } $request = $event->getRequest(); $template = $request->attributes->get('template'); $body = $app['twig']->render($template, $view); $e->setResponse(new Response($body)); }); -
$app->on(KernelEvents::VIEW, function ($e) use ($app) { $view = $e->getControllerResult(); if
(!is_array($view)) { return; } $request = $event->getRequest(); $template = $request->attributes->get('template'); $body = $app['twig']->render($template, $view); $e->setResponse(new Response($body)); }); -
$app->on(KernelEvents::VIEW, function ($e) use ($app) { $view = $e->getControllerResult(); if
(!is_array($view)) { return; } $request = $event->getRequest(); $template = $request->attributes->get('template'); $body = $app['twig']->render($template, $view); $e->setResponse(new Response($body)); }); -
$app->on(KernelEvents::VIEW, function ($e) use ($app) { $view = $event->getControllerResult(); if
(!is_array($view)) { return; } $request = $event->getRequest(); // user.show.html.twig $template = $request->attributes->get('_route'); $template.= ".html.twig"; $body = $app['twig']->render($template, $view); $event->setResponse(new Response($body)); }); -
Summary •Treat a Silex app like any other app •Pimple
requires care when you have a lot of services •Namespace and name everything •Lazyness for as much as possible •Silex’ API is narrow, scope widens when you dig around inside •Silex has to do a lot of work for every request, grows linearly with your app