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.

Symfony without the FrameworkBundle

202 views

Published on

Keynote for Symfony Live Berlin 2018

Published in: Internet
  • Be the first to comment

Symfony without the FrameworkBundle

  1. 1. @tobiasnyholm Symfony without FrameworkBundle Tobias Nyholm
  2. 2. @tobiasnyholm Why?
  3. 3. @tobiasnyholm Tobias Nyholm • Full stack unicorn on Happyr.com • Certified Symfony developer • Symfony core member • Symfony CARE • 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 NewRelicBundle
  5. 5. @tobiasnyholm “Frameworks are slow”
  6. 6. @tobiasnyholm areFrameworks slow ??? = Poor performance
  7. 7. @tobiasnyholm What is a framework?
  8. 8. @tobiasnyholm What is a framework? Symfony
  9. 9. @tobiasnyholm svn checkout http://svn.symfony-project.com/tags/RELEASE_1_4_8 symfony “Symfony folder”
  10. 10. @tobiasnyholm composer create-project symfony/framework-standard-edition acme "2.8.*" “vendor/symfony/symfony”
  11. 11. @tobiasnyholm composer create-project symfony/framework-standard-edition acme “3.4.*” “vendor/symfony/symfony”
  12. 12. @tobiasnyholm composer create-project symfony/skeleton acme “vendor/symfony/*”
  13. 13. @tobiasnyholm { "name": "symfony/skeleton", "require": { "php": "^7.1.3", "ext-ctype": "*", "ext-iconv": "*", "symfony/console": "*", "symfony/flex": "^1.1", "symfony/framework-bundle": "*", "symfony/yaml": "*" }, "require-dev": { "symfony/dotenv": "*" } }
  14. 14. @tobiasnyholm What is a framework? Symfony
  15. 15. @tobiasnyholm { "name": "symfony/framework-bundle", "type": "symfony-bundle", "description": "Symfony FrameworkBundle", "require": { "php": "^7.1.3", "ext-xml": "*", "symfony/cache": "~3.4|~4.0", "symfony/dependency-injection": "^4.2", "symfony/config": "~4.2", "symfony/event-dispatcher": "^4.1", "symfony/http-foundation": "^4.1.2", "symfony/http-kernel": "^4.2", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", "symfony/routing": "^4.1" } }
  16. 16. @tobiasnyholm Level of Magic 0 % 10 % 20 % 30 % 40 % 50 % 60 % 70 % 80 % 90 % 100 % Symfony version 1 2 3 4 5 Awesome User responsibillity Magic
  17. 17. @tobiasnyholm an ecosystemwhatever we makeSymfony is a community
  18. 18. (pause for tweeting) #Symfony_Live
  19. 19. @tobiasnyholm areFrameworks slow Our code = Poor performance
  20. 20. @tobiasnyholm Let’s talk about performance
  21. 21. @tobiasnyholm Rule 1: Buy a better server
  22. 22. @tobiasnyholm Rule 2: Use Varnish
  23. 23. @tobiasnyholm Requestspertimeunit 0 1000 2000 3000 4000 No Varnish With Varnish
  24. 24. @tobiasnyholm Rule 3: Run less code
  25. 25. @tobiasnyholm section .text global _start ; must be declared for linker (ld) _start: ; tell linker entry point mov edx,len ; message length mov ecx,msg ; message to write mov ebx,1 ; file descriptor (stdout) mov eax,4 ; system call number (sys_write) int 0x80 ; call kernel mov eax,1 ; system call number (sys_exit) int 0x80 ; call kernel section .data msg db 'Hello, world!',0xa ; our dear string len equ $ - msg ; length of our dear string
  26. 26. @tobiasnyholm More code = More “ticks”
  27. 27. @tobiasnyholm Version Lines of code Lines executed Function calls Memory Time Symfony 1.4 172 000 2 401 10 588 1 200 Kb 198 ms Symfony 2.8 415 700 1 928 5 051 1 600 Kb 100 ms Symfony 3.4 433 600 2 649 4 004 1 600 Kb 82 ms Symfony 4.1 136 000 1 029 1 421 500 Kb 31 ms Hello world
  28. 28. @tobiasnyholm Fastest application <?php // index.php if (isset($_GET['page']) && $_GET['page'] === 'foo') { echo "Foo page <br>"; } else { echo "Welcome to index! <br>"; }
  29. 29. @tobiasnyholm What is your application doing?
  30. 30. @tobiasnyholm <?php $uri = $_SERVER['REQUEST_URI']; $base = 'https://happyr.com'; if (substr($uri, 0, 1) !== 'j') { redirect($base); } redirect(sprintf('%s/x/job/%d-x_x', $base, substr($uri, 1))); function redirect($url) { header('Location: '.$url); exit(0); }
  31. 31. @tobiasnyholm Complexity Redirect symfony/skeleton symfony/website-skeleton Your application?
  32. 32. @tobiasnyholm Do you need this?
  33. 33. @tobiasnyholm Do you need this? Forms Security Validation Router Dependency injection Event dispatcher Serializer Yaml Translation HTTP Kernel HTTP Foundation Finder Debug
  34. 34. @tobiasnyholm Do we need HTTP?
  35. 35. @tobiasnyholm HTTP Foundation $_SERVER[‘REQUEST_URI’] vs
  36. 36. @tobiasnyholm Messenger component?
  37. 37. @tobiasnyholm Doctrine or just PDO?
  38. 38. @tobiasnyholm Building a small application
  39. 39. @tobiasnyholm Workflow component
  40. 40. <?php use SymfonyComponentWorkflowDefinitionBuilder; use SymfonyComponentWorkflowTransition; use SymfonyComponentWorkflowWorkflow; use SymfonyComponentWorkflowMarkingStoreSingleStateMarkingStore; $definitionBuilder = new DefinitionBuilder(); $definition = $definitionBuilder->addPlaces(['draft', 'review', 'rejected', 'published']) ->addTransition(new Transition('to_review', 'draft', 'review')) ->addTransition(new Transition('publish', 'review', 'published')) ->addTransition(new Transition('reject', 'review', 'rejected')) ->build() ; $marking = new SingleStateMarkingStore('currentState'); $workflow = new Workflow($definition, $marking); $article = /* fetch from database */ if ($workflow->can($article, 'publish')) { $workflow->apply($article, 'publish'); // TODO redirect or any other action ... }
  41. 41. @tobiasnyholm Cache component
  42. 42. namespace SymfonyContractsCache; /** * Gets/Stores items from/to a cache. * * On cache misses, a callback is called that should return the missing value. * This callback is given an ItemInterface object corresponding to the needed key, * that could be used e.g. for expiration control. */ interface CacheInterface { /** * @param callable(ItemInterface) $callback Should return the computed value for the given * key/item * @param float|null $beta A float that, as it grows, controls the likeliness * of triggering early expiration. 0 disables it, * INF forces immediate expiration. * * @return mixed The value corresponding to the provided key * * @throws InvalidArgumentException When $key is not valid or when $beta is negative */ public function get(string $key, callable $callback, float $beta = null); }
  43. 43. $result = $cache->get('foobar', function (ItemInterface $item) { $pi = null; /* Calculation of pi */ $item->expiresAfter(3600); return $pi; });
  44. 44. $result = $cache->get('foobar', function (ItemInterface $item) { $pi = null; /* Calculation of pi */ $item->expiresAfter(3600); return $pi; }); Time T: 0 T: 3600
  45. 45. @tobiasnyholm Middleware pattern
  46. 46. use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; use AppRunner; require __DIR__.'/../vendor/autoload.php'; $request = Request::createFromGlobals(); $response = new Response(); $middleware[] = function(Request $request, Response $response, callable $next) { if ($request->getMethod() !== 'GET') { return new Response('Method not allowed', 405); } return $next($request, $response); }; $middleware[] = function(Request $request, Response $response, callable $next) { $response->setContent('foobar'); return $next($response, $response); }; $runner = new Runner($middleware); $response = $runner($request, $response); // Send response echo $response->getBody();
  47. 47. namespace App; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; class Runner { /** @var callable[] */ private $queue; public function __construct(array $queue) { $this->queue = $queue; } public function __invoke(Request $request, Response $response) { $middleware = array_shift($this->queue); if (null === $middleware) { // Default return function (Request $request, Response $response, callable $next) { return $response; }; } return $middleware($request, $response, $this); } }
  48. 48. <?php namespace AppMiddleware; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; interface MiddlewareInterface { public function __invoke(Request $request, Response $response, callable $next); }
  49. 49. namespace AppMiddleware; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; class Router implements MiddlewareInterface { public function __invoke(Request $request, Response $response, callable $next) { $uri = $request->getUri(); switch ($uri) { case '/': $response = (new AppControllerStartpageController())->run($request); break; case '/foo': $response = (new AppControllerFooController())->run($request); break; default: $response->setStatusCode(404); $response->setContent('Not Found'); break; } return $next($request, $response); } }
  50. 50. <?php use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; use AppRunner; require __DIR__.'/../vendor/autoload.php'; $request = Request::createFromGlobals(); $response = new Response(); $middleware[] = new Cache; $middleware[] = new NewRelic; $middleware[] = new Security; $middleware[] = new Router; $runner = new Runner($middleware); $response = $runner($request, $response); // Send response echo $response->getBody();
  51. 51. @tobiasnyholm Custom kernel
  52. 52. use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentConfigExceptionFileLocatorFileNotFoundException; use SymfonyComponentConfigFileLocator; use SymfonyComponentDependencyInjectionContainer; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentDependencyInjectionDumperPhpDumper; use SymfonyComponentDependencyInjectionLoaderYamlFileLoader; class Kernel { private $booted = false; private $debug; private $env; /** @var Container */ private $container; public function __construct(string $env, bool $debug = false) { $this->debug = $debug; $this->env = $env; } /** * Handle a Request and turn it in to a response. */ public function handle(Request $request): Response {
  53. 53. $container = new CachedContainer(); } else { $container = new ContainerBuilder(); $container->setParameter('kernel.project_dir', $this->getProjectDir()); $container->setParameter('kernel.environment', $this->env); $container->registerForAutoconfiguration(EventSubscriberInterface::class) ->addTag('kernel.event_subscriber'); $container->registerForAutoconfiguration(Command::class) ->addTag('console.command'); $container->addCompilerPass(new AddConsoleCommandPass()); $container->addCompilerPass( new RegisterListenersPass(EventDispatcherInterface::class), PassConfig::TYPE_BEFORE_REMOVING ); $fileLocator = new FileLocator($this->getProjectDir().'/config'); $loader = new YamlFileLoader($container, $fileLocator); try { $loader->load('services.yaml'); $loader->load('services_'.$this->env.'.yaml'); } catch (FileLocatorFileNotFoundException $e) { } $container->compile(); //dump the container
  54. 54. @tobiasnyholm Too much boilerplate? Use the FrameworkBundle
  55. 55. use SymfonyBundleFrameworkBundleFrameworkBundle; use SymfonyBundleFrameworkBundleKernelMicroKernelTrait; use SymfonyComponentConfigLoaderLoaderInterface; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentHttpKernelKernel as BaseKernel; use SymfonyComponentRoutingRouteCollectionBuilder; class Kernel extends BaseKernel { use MicroKernelTrait; public function registerBundles() { return [ new FrameworkBundle(), ]; } protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) { $confDir = $this->getProjectDir().'/config'; $loader->load($confDir.'/services.yaml'); $loader->load($confDir.'/services_'.$this->environment.'.yaml'); } protected function configureRoutes(RouteCollectionBuilder $routes) { $confDir = $this->getProjectDir().'/config';
  56. 56. use SymfonyBundleFrameworkBundleFrameworkBundle; use SymfonyBundleFrameworkBundleKernelMicroKernelTrait; use SymfonyComponentConfigLoaderLoaderInterface; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentHttpKernelKernel as BaseKernel; use SymfonyComponentRoutingRouteCollectionBuilder; class Kernel extends BaseKernel { use MicroKernelTrait; public function registerBundles() { return [ new FrameworkBundle(), ]; } protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) { $confDir = $this->getProjectDir().'/config'; $loader->load($confDir.'/services.yaml'); $loader->load($confDir.'/services_'.$this->environment.'.yaml'); } protected function configureRoutes(RouteCollectionBuilder $routes) { $confDir = $this->getProjectDir().'/config';
  57. 57. /** * A Kernel that provides configuration hooks. * * @author Ryan Weaver <ryan@knpuniversity.com> * @author Fabien Potencier <fabien@symfony.com> */ trait MicroKernelTrait { abstract protected function configureRoutes(RouteCollectionBuilder $routes); abstract protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader); public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function (ContainerBuilder $container) use ($loader) { $container->loadFromExtension('framework', array( 'router' => array( 'resource' => 'kernel::loadRoutes', 'type' => 'service', ), )); if ($this instanceof EventSubscriberInterface) { $container->register('kernel', static::class) ->setSynthetic(true) ->setPublic(true) ->addTag('kernel.event_subscriber') ; }
  58. 58. @tobiasnyholm Improve performance
  59. 59. @tobiasnyholm Improve performance #1: Buy a better server
 #2: Use Varnish
 #3: Run less code
  60. 60. @tobiasnyholm Improve performance #1: Buy a better computer
 #2: Use cache
 #3: Run less code
  61. 61. @tobiasnyholm Profile your application
  62. 62. @tobiasnyholm “Normal” PHP-FPM App App App App HTTP FastCGI
  63. 63. @tobiasnyholm PHPFastCGI App App App App HTTP FastCG I https://github.com/PHPFastCGI/FastCGIDaemon
  64. 64. <?php require_once dirname(__FILE__) . '/../vendor/autoload.php'; use PHPFastCGIFastCGIDaemonApplicationFactory; use PHPFastCGIFastCGIDaemonHttpRequestInterface; use SymfonyComponentHttpFoundationResponse; $kernel = function (RequestInterface $request) { $sfRequest = $request->getHttpFoundationRequest(); return new Response('<h1>Hello, World!</h1>' . $sfRequest->getUri()); }; $application = (new ApplicationFactory)->createApplication($kernel); $application->run(); https://github.com/PHPFastCGI/FastCGIDaemon
  65. 65. <?php require_once dirname(__FILE__) . '/../vendor/autoload.php'; use PHPFastCGIFastCGIDaemonApplicationFactory; use PHPFastCGIFastCGIDaemonHttpRequestInterface; use SymfonyComponentHttpFoundationResponse; $kernel = function (RequestInterface $request) { $sfRequest = $request->getHttpFoundationRequest(); $response = new Response(); $middleware[] = new Cache; $middleware[] = new NewRelic; $middleware[] = new Security; $middleware[] = new Router; $runner = new Runner($middleware); $response = $runner($sfRequest, $response); return $response; }; $application = (new ApplicationFactory)->createApplication($kernel); $application->run();
  66. 66. use PHPFastCGIFastCGIDaemonHttpRequestInterface; use PHPFastCGIFastCGIDaemonKernelInterface as PHPFastCGIKernel; use SymfonyComponentHttpKernelKernelInterface; use SymfonyComponentHttpKernelTerminableInterface; class FastCGIKernel implements PHPFastCGIKernel { private $kernel; public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; } public function handleRequest(RequestInterface $request) { $this->kernel->boot(); $symfonyRequest = $request->getHttpFoundationRequest(); $symfonyResponse = $this->kernel->handle($symfonyRequest); if ($this->kernel instanceof TerminableInterface) { $this->kernel->terminate($symfonyRequest, $symfonyResponse); } return $symfonyResponse; } }
  67. 67. @tobiasnyholm < 10ms
  68. 68. @tobiasnyholm < 5ms
  69. 69. @tobiasnyholm
  70. 70. @tobiasnyholm Remember
  71. 71. @tobiasnyholm – Tobias Nyholm “We are Symfony” https://joind.in/talk/4d898

×