Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taming Runaway Silex Apps - SymfonyCon Warsaw 2013

Dave Marshall
December 13, 2013

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
Tweet

More Decks by Dave Marshall

Other Decks in Programming

Transcript

  1. // 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']-> ....
  2. $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']); });
  3. <?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
  4. // 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' );
  5. 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);
  6. 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);
  7. 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);
  8. 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);
  9. Silex defines a range of services which can be used

    or replaced You probably don't want to mess with most of them
  10. web/ # assets + front controllers app/ # app config/bootstrap

    src/ # app code bin/ # cli scripts + binstubs
  11. <?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;
  12. > 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(...
  13. // 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());
  14. 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) {} }
  15. // 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);
  16. function welcome($name, Application $app) { return "Hello " . $app->escape($name);

    }; $app->get('/hello/{name}', 'welcome') ->bind('site.welcome');
  17. // 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');
  18. // class BaseControllerResolver list($class, $method) = explode('::', $controller, 2); $controller

    = new $class(); if ($controller instanceof BaseController) { $controller->setApplication($app); } return array($controller, $method);
  19. // abstract class BaseController public function setApplication(Application $app) { $this->app

    = $app; } public function get($key) { return $this->app[$key]; }
  20. // abstract class BaseController public function render($template, array $context) {

    return $this ->get('twig') ->render($template, $context); }
  21. 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, ] ); } }
  22. 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, ] ); } }
  23. 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, ] ); } }
  24. 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');
  25. 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');
  26. 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');
  27. // 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 ] ); }
  28. // 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, ] ); }
  29. 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)
  30. 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);
  31. 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.
  32. 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") );
  33. $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" );
  34. // 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');
  35. // 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');
  36. // 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');
  37. $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)); });
  38. $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)); });
  39. $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)); });
  40. $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)); });
  41. 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