Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Deep dive into Symfony4 internals

359 views

Published on

Talk for phpkonf Istanbul 2018

Published in: Education

Deep dive into Symfony4 internals

  1. 1. Deep dive into Symfony 4 internals Tobias Nyholm @tobiasnyholm @tobiasnyholm
  2. 2. @tobiasnyholm Why?
  3. 3. @tobiasnyholm Tobias Nyholm • Full stack unicorn on Happyr.com • Certified Symfony developer • Symfony core member • PHP-Stockholm • Open source
  4. 4. @tobiasnyholm Open source PHP-cache HTTPlug Mailgun LinkedIn API clientSwap Stampie BazingaGeocoderBundle PHP-Geocoder FriendsOfApi/boilerplate Guzzle Buzz CacheBundlePSR7 SymfonyBundleTest NSA SimpleBus integrations PSR HTTP clients Neo4j KNP Github API PHP-Translation Puli Assert Backup-manager/symfony php-http/httplug-bundle php-http/multipart-stream php-http/discovery happyr/normal-distribution-bundle nyholm/effective-interest-rate MailgunBundle league/geotools
  5. 5. @tobiasnyholm Building your own framework
  6. 6. @tobiasnyholm Just a bunch of files <?php // index.php if (isset($_GET['page']) && $_GET['page'] === 'foo') { echo "Foo page <br>"; } else { echo "Welcome to index! <br>"; } if ($_SERVER['REMOTE_ADDR'] === '127.0.0.1') { echo "(admin stuff)"; }
  7. 7. @tobiasnyholm Just a bunch of files
  8. 8. @tobiasnyholm Just a bunch of files <?php // index.php if (isset($_GET['page']) && $_GET['page'] === 'foo') { echo "Foo page <br>"; } else { echo "Welcome to index! <br>"; } if ($_SERVER['REMOTE_ADDR'] === '127.0.0.1') { echo "(admin stuff)"; }
  9. 9. @tobiasnyholm HTTP objects
  10. 10. @tobiasnyholm HTTP objects <?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $query = $request->getQueryParams(); if (isset($query['page']) && $query['page'] === 'foo') { $response = new Response(200, [], 'Foo page'); } else { $response = new Response(200, [], 'Welcome to index!'); } // Send response echo $response->getBody();
  11. 11. @tobiasnyholm HTTP objects
  12. 12. @tobiasnyholm HTTP objects
  13. 13. @tobiasnyholm HTTP objects <?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $query = $request->getQueryParams(); if (isset($query['page']) && $query['page'] === 'foo') { $response = new Response(200, [], 'Foo page'); } else { $response = new Response(200, [], 'Welcome to index!'); } // Send response echo $response->getBody();
  14. 14. @tobiasnyholm Controllers
  15. 15. @tobiasnyholm Controllers
  16. 16. @tobiasnyholm Controllers
  17. 17. @tobiasnyholm Controllers<?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $uri = $request->getUri()->getPath(); if ($uri === '/') { $response = (new AppControllerStartpageController())->run($request); } elseif ($uri === '/foo') { $response = (new AppControllerFooController())->run($request); } else { $response = new Response(404, [], 'Not found'); } // Send response echo $response->getBody();
  18. 18. @tobiasnyholm Controllers <?php declare(strict_types=1); namespace AppController; use NyholmPsr7Response; use PsrHttpMessageRequestInterface; class StartpageController { public function run(RequestInterface $request) { return new Response(200, [], 'Welcome to index!'); } }
  19. 19. @tobiasnyholm Controllers
  20. 20. @tobiasnyholm Controllers<?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $uri = $request->getUri()->getPath(); if ($uri === '/') { $response = (new AppControllerStartpageController())->run($request); } elseif ($uri === '/foo') { $response = (new AppControllerFooController())->run($request); } else { $response = new Response(404, [], 'Not found'); } // Send response echo $response->getBody();
  21. 21. @tobiasnyholm Event loop
  22. 22. @tobiasnyholm Event loop<?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $response = new Response(); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); $response = $runner($request, $response); // Send response echo $response->getBody();
  23. 23. @tobiasnyholmnamespace AppMiddleware; use PsrHttpMessageResponseInterface as Response; use PsrHttpMessageServerRequestInterface as Request; class Router implements MiddlewareInterface { public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri()->getPath(); switch ($uri) { case '/': $response = (new AppControllerStartpageController())->run($request); break; case '/foo': $response = (new AppControllerFooController())->run($request); break; default: $response = $response->withStatus(404); $response->getBody()->write('Not Found'); break; } return $next($request, $response); } }
  24. 24. @tobiasnyholm Event loop
  25. 25. @tobiasnyholm Event loop<?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $response = new Response(); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); $response = $runner($request, $response); // Send response echo $response->getBody();
  26. 26. @tobiasnyholm Cache
  27. 27. @tobiasnyholm<?php // Cache.php namespace AppMiddleware; use CacheAdapterApcuApcuCachePool; use PsrCacheCacheItemPoolInterface; use PsrHttpMessageResponseInterface as Response; use PsrHttpMessageServerRequestInterface as Request; class Cache implements MiddlewareInterface { /*** @var CacheItemPoolInterface */ private $cache; /** @var int */ private $ttl; public function __construct() { $this->cache = new ApcuCachePool(); $this->ttl = 300; } public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri(); $cacheKey = 'url'.sha1($uri->getPath().'?'.$uri->getQuery()); $cacheItem = $this->cache->getItem($cacheKey);
  28. 28. @tobiasnyholm Cache<?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $response = new Response(); $middlewares[] = new AppMiddlewareCache(); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); $response = $runner($request, $response); // Send response echo $response->getBody();
  29. 29. @tobiasnyholm Cache
  30. 30. @tobiasnyholm Cache<?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $response = new Response(); $middlewares[] = new AppMiddlewareCache(); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); $response = $runner($request, $response); // Send response echo $response->getBody();
  31. 31. @tobiasnyholm Container
  32. 32. @tobiasnyholm Container <?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $response = new Response(); $middlewares[] = new AppMiddlewareCache(); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); $response = $runner($request, $response); // Send response echo $response->getBody();
  33. 33. @tobiasnyholm Container <?php // index.php use NyholmPsr7FactoryServerRequestFactory; use NyholmPsr7Response; require __DIR__.'/../vendor/autoload.php'; $request = (new ServerRequestFactory())->createServerRequestFromGlobals(); $kernel = new AppKernel('dev', true); $response = $kernel->handle($request); // Send response echo $response->getBody();
  34. 34. <?php // Kernel.php namespace App; use NyholmPsr7Response; use PsrHttpMessageRequestInterface; use PsrHttpMessageResponseInterface; use SymfonyComponentConfigExceptionFileLocatorFileNotFoundException; use SymfonyComponentConfigFileLocator; use SymfonyComponentDependencyInjectionContainer; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentDependencyInjectionDumperPhpDumper; use SymfonyComponentDependencyInjectionLoaderYamlFileLoader; class Kernel { private $booted = false; private $debug; private $environment; /** @var Container */ private $container; public function __construct(string $env, bool $debug = false) { $this->debug = $debug; $this->environment = $env; }
  35. 35. @tobiasnyholm Container
  36. 36. @tobiasnyholm Container
  37. 37. @tobiasnyholm Container
  38. 38. @tobiasnyholm Container
  39. 39. @tobiasnyholm Router
  40. 40. @tobiasnyholm <?php namespace AppMiddleware; use PsrHttpMessageResponseInterface as Response; use PsrHttpMessageServerRequestInterface as Request; class Router implements MiddlewareInterface { public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri()->getPath(); switch ($uri) { case '/': $response = (new AppControllerStartpageController())->run($request); break; case '/foo': $response = (new AppControllerFooController())->run($request); break; default: $response = $response->withStatus(404); $response->getBody()->write('Not Found'); break; } return $next($request, $response); } }
  41. 41. @tobiasnyholm public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri()->getPath(); switch ($uri) { case '/': $response = (new AppControllerStartpageController())->run($request); break; case '/foo': $response = (new AppControllerFooController())->run($request); break; default: $response = $response->withStatus(404); $response->getBody()->write('Not Found'); break; } return $next($request, $response); }
  42. 42. @tobiasnyholm Router /admin/account /admin/password /images/upload /images/{id}/show /images/{id}/delete /foo /
  43. 43. public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri()->getPath(); if (0 === strpos($uri, '/admin')) { if (0 === strpos($uri, '/admin/account')) { $response = (new AppControllerManagerController())->accountAction($request); } if (0 === strpos($uri, '/admin/password')) { $response = (new AppControllerManagerController())->passwordAction($request); } } elseif (0 === strpos($uri, '/images')) { if (0 === strpos($uri, '/images/upload')) { $response = (new AppControllerImageController())->uploadAction($request); } if (preg_match('#^/images/(?P<id>[^/]++)/show#sD', $uri, $matches)) { $response = (new AppControllerImageController())->showAction($request, $matches[ } if (preg_match('#^/images/(?P<id>[^/]++)/delete#sD', $uri, $matches)) { $response = (new AppControllerImageController())->deleteAction($request, $matche } } elseif ($uri === '/') { $response = (new AppControllerStartpageController())->run($request); } elseif ($uri === '/foo') { $response = (new AppControllerFooController())->run($request); } else { $response = $response->withStatus(404); $response->getBody()->write('Not Found'); }
  44. 44. public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri()->getPath(); $routes = array( '/foo' => 'AppControllerFooController::FooAction', '/images/upload' => 'AppControllerImageController::uploadAction', '/admin/account' => 'AppControllerManagerController::accountAction', '/admin/password' => 'AppControllerManagerController::passwordAction', // More static routes '/' => 'AppControllerStartpageController::startpageAction', ); if (isset($routes[$uri])) { $response = call_user_func($routes[$uri], $request); return $next($request, $response); } $regex = '{^(?' .'|/images/([^/]++)/(?' .'|show(*:31)' .'|delete(*:44)' // my dynamic and complex regex .')' .')$}sD';
  45. 45. @tobiasnyholm Router
  46. 46. @tobiasnyholm Security
  47. 47. @tobiasnyholm Security <?php namespace AppMiddleware; use NyholmPsr7Response; use PsrHttpMessageResponseInterface; use PsrHttpMessageServerRequestInterface; class Security implements MiddlewareInterface { public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callabl { $uri = $request->getUri()->getPath(); $ip = $request->getServerParams()['REMOTE_ADDR']; if ($ip !== '127.0.0.1' && $uri === '/admin') { return new Response(403, [], 'Forbidden'); } if ($uri === '/images/4711/delete') { if (true /* user is not "bob" */) { return new Response(403, [], 'Forbidden'); } } return $next($request, $response); } }
  48. 48. @tobiasnyholm <?php namespace AppMiddleware; use AppSecurityTokenStorage; use NyholmPsr7Response; use PsrHttpMessageResponseInterface ; use PsrHttpMessageServerRequestInterface; class Authentication implements MiddlewareInterface { private $tokenStorage; /** * * @param $tokenStorage */ public function __construct(TokenStorage $tokenStorage) { $this->tokenStorage = $tokenStorage; } public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable { $uri = $request->getUri()->getPath(); $auth = $request->getServerParams()['PHP_AUTH_USER']??''; $pass = $request->getServerParams()['PHP_AUTH_PW']??'';
  49. 49. @tobiasnyholm Securityclass Kernel { // … /** * Handle a Request and turn it in to a response. */ public function handle(RequestInterface $request): ResponseInterface { $this->boot(); $middlewares[] = $this->container->get(‘middleware.auth'); $middlewares[] = $this->container->get('middleware.security'); $middlewares[] = $this->container->get('middleware.cache'); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); return $runner($request, new Response()); } // … }
  50. 50. @tobiasnyholm Security
  51. 51. @tobiasnyholm Security
  52. 52. @tobiasnyholm Security
  53. 53. @tobiasnyholm Security <?php declare(strict_types=1); namespace AppController; use AppSecurityTokenStorage; use NyholmPsr7Response; use PsrHttpMessageRequestInterface; class AdminController { private $tokenStorage; public function __construct(TokenStorage $tokenStorage) { $this->tokenStorage = $tokenStorage; } public function run(RequestInterface $request) { return new Response(200, [], sprintf('Hello %s (admin)', $this->tokenStorage->getLastToken()['username'])); } }
  54. 54. @tobiasnyholm Security Authentication vs Authorisation
  55. 55. @tobiasnyholm class SecurityVoters implements MiddlewareInterface { /** @var VoterInterface[] */ private $voters; public function __construct(array $voters) { $this->voters = $voters; } public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable { $deny = 0; foreach ($this->voters as $voter) { $result = $voter->vote($request); switch ($result) { case VoterInterface::ACCESS_GRANTED: return $next($request, $response); case VoterInterface::ACCESS_DENIED: ++$deny; break; default: break; } } if ($deny > 0) { return new Response(403, [], 'Forbidden'); } return $next($request, $response);
  56. 56. @tobiasnyholm Security declare(strict_types=1); namespace AppSecurityVoter; use AppSecurityTokenStorage; use PsrHttpMessageServerRequestInterface; class AdminVoter implements VoterInterface { private $tokenStorage; public function __construct(TokenStorage$tokenStorage) { $this->tokenStorage = $tokenStorage; } public function vote(ServerRequestInterface $request) { $uri = $request->getUri()->getPath(); $token = $this->tokenStorage->getLastToken(); if ($token['username'] !== 'Tobias' && $uri === '/admin') { return VoterInterface::ACCESS_DENIED; } return VoterInterface::ACCESS_ABSTAIN; } }
  57. 57. @tobiasnyholm Security
  58. 58. @tobiasnyholm Securityclass Kernel { // … /** * Handle a Request and turn it in to a response. */ public function handle(RequestInterface $request): ResponseInterface { $this->boot(); $middlewares[] = $this->container->get(‘middleware.auth'); $middlewares[] = $this->container->get('middleware.security'); $middlewares[] = $this->container->get('middleware.cache'); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); return $runner($request, new Response()); } // … }
  59. 59. @tobiasnyholm Security
  60. 60. @tobiasnyholm Autowiring
  61. 61. @tobiasnyholm Autowiring
  62. 62. @tobiasnyholm Autowiring P P P
  63. 63. @tobiasnyholm Autowiringclass Kernel { // … /** * Handle a Request and turn it in to a response. */ public function handle(RequestInterface $request): ResponseInterface { $this->boot(); $middlewares[] = $this->container->get(Authentication::class); $middlewares[] = $this->container->get(SecurityVoters::class); $middlewares[] = $this->container->get(Cache::class); $middlewares[] = new AppMiddlewareRouter(); $runner = (new RelayRelayBuilder())->newInstance($middlewares); return $runner($request, new Response()); } // … }
  64. 64. @tobiasnyholm Autowiring
  65. 65. @tobiasnyholm Toolbar
  66. 66. @tobiasnyholmclass Toolbar implements MiddlewareInterface { private $cacheDataCollector; public function __construct(CacheDataCollector $cacheDataCollector) { $this->cacheDataCollector = $cacheDataCollector; } public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable { $calls = $this->cacheDataCollector->getCalls(); $getItemCalls = count($calls['getItem']); $content = $response->getBody()->__toString(); $toolbar = <<<HTML <br><br><br><hr> URL: {$request->getUri()->getPath()}<br> IP: {$request->getServerParams()['REMOTE_ADDR']}<br> Cache calls: {$getItemCalls}<br> HTML; $stream = (new StreamFactory())->createStream($content.$toolbar); $response = $response->withBody($stream); return $next($request, $response); } }
  67. 67. @tobiasnyholm<?php namespace AppDataCollector; use PsrCacheCacheItemInterface; use PsrCacheCacheItemPoolInterface; class CacheDataCollector implements CacheItemPoolInterface { /** @var CacheItemPoolInterface */ private $real; private $calls; public function __construct(CacheItemPoolInterface $cache) { $this->real = $cache; } public function getCalls() { return $this->calls; } public function getItem($key) { $this->calls['getItem'][] = ['key'=>$key]; return $this->real->getItem($key); }
  68. 68. @tobiasnyholm Toolbar
  69. 69. @tobiasnyholmclass Toolbar implements MiddlewareInterface { private $cacheDataCollector; public function __construct(CacheDataCollector $cacheDataCollector) { $this->cacheDataCollector = $cacheDataCollector; } public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable { $calls = $this->cacheDataCollector->getCalls(); $getItemCalls = count($calls['getItem']); $content = $response->getBody()->__toString(); $toolbar = <<<HTML <br><br><br><hr> URL: {$request->getUri()->getPath()}<br> IP: {$request->getServerParams()['REMOTE_ADDR']}<br> Cache calls: {$getItemCalls}<br> HTML; $stream = (new StreamFactory())->createStream($content.$toolbar); $response = $response->withBody($stream); return $next($request, $response); } }
  70. 70. @tobiasnyholm Exception
  71. 71. @tobiasnyholm Exception <?php declare(strict_types=1); namespace AppController; use NyholmPsr7Response; use PsrHttpMessageRequestInterface; class ExceptionController { public function run(RequestInterface $request) { throw new RuntimeException('This is an exception'); } }
  72. 72. @tobiasnyholm Exception <?php namespace AppMiddleware; use CacheAdapterApcuApcuCachePool; use NyholmPsr7Response; use PsrCacheCacheItemPoolInterface; use PsrHttpMessageResponseInterface; use PsrHttpMessageServerRequestInterface; class ExceptionHandler implements MiddlewareInterface { public function __invoke(ServerRequestInterface $request, ResponseInterface $response, calla { try { $response = $next($request, $response); } catch (Throwable $exception) { $response = new Response(500, [], $exception->getMessage()); } return $response; } }
  73. 73. @tobiasnyholm Exceptionclass Kernel { /** * Handle a Request and turn it in to a response. */ public function handle(RequestInterface $request): ResponseInterface { $this->boot(); $middlewares[] = $this->container->get(ExceptionHandler::class); $middlewares[] = $this->container->get(Authentication::class); $middlewares[] = $this->container->get(SecurityVoters::class); $middlewares[] = $this->container->get(Cache::class); $middlewares[] = new AppMiddlewareRouter(); $middlewares[] = $this->container->get(Toolbar::class); $runner = (new RelayRelayBuilder())->newInstance($middlewares); return $runner($request, new Response()); } }
  74. 74. @tobiasnyholm Exception
  75. 75. @tobiasnyholm Exception <?php namespace AppMiddleware; use CacheAdapterApcuApcuCachePool; use NyholmPsr7Response; use PsrCacheCacheItemPoolInterface; use PsrHttpMessageResponseInterface; use PsrHttpMessageServerRequestInterface; class ExceptionHandler implements MiddlewareInterface { public function __invoke(ServerRequestInterface $request, ResponseInterface $response, calla { try { $response = $next($request, $response); } catch (Throwable $exception) { $response = new Response(500, [], $exception->getMessage()); } return $response; } }
  76. 76. @tobiasnyholm
  77. 77. @tobiasnyholm
  78. 78. @tobiasnyholm Where is Symfony?
  79. 79. @tobiasnyholm HTTP objects
  80. 80. @tobiasnyholm Controllers
  81. 81. @tobiasnyholm Event loop kernel.request kernel.controller kernel.view kernel.response kernel.finish_request kernel.terminate
  82. 82. @tobiasnyholm Cache PSR-6 PSR-16
  83. 83. @tobiasnyholm Container
  84. 84. @tobiasnyholm Router
  85. 85. @tobiasnyholm Security
  86. 86. @tobiasnyholm Autowiring
  87. 87. @tobiasnyholm Toolbar
  88. 88. @tobiasnyholm Exception
  89. 89. @tobiasnyholm This was Symfony sim ple
  90. 90. @tobiasnyholm What is Symfony? nyholm/psr7 symfony/http-foundation relay/relay symfony/event-dispatcher php-cache/* symfony/cache Custom kernel symfony/http-kernel Custom security symfony/security
  91. 91. @tobiasnyholm Questions? https://joind.in/talk/8b9dd

×