Pro Yearly is on sale from $80 to $50! »

Eating spaghetti with Symfony

1a4e1f98f3aeef310273366c8c785207?s=47 Jakub Zalas
February 18, 2016

Eating spaghetti with Symfony

Big-bang migrations hardly ever work and usually take significantly more effort than expected. It's also hard to convince the stakeholders there's any value in the whole operation. It's much more effective to make gradual improvements. It's also more rewarding to celebrate success after every sprint. Learn how to leverage Symfony to move away from an Old School PHP Spaghetti Project™ to a modern Symfony based application. Release after the first iteration and keep improving the code base, delivering value in the same time.

1a4e1f98f3aeef310273366c8c785207?s=128

Jakub Zalas

February 18, 2016
Tweet

Transcript

  1. @jakub_zalas @jakzal Jakub Zalas EATING SPAGHETTI WITH SYMFONY PHP UK

    2016 https://www.flickr.com/photos/stijnnieuwendijk/8839597194/in/photostream/
  2. https://www.flickr.com/photos/ocarchives/3952964087/ THE PLAN ➤ Spaghetti/legacy/mud ➤ Migration strategies ➤ Migration

    cookbook https://flic.kr/p/72iYve
  3. SPAGHETTI, LEGACY, BIG BALL OF MUD Twisted and tangled as

    a bowl of spaghetti. Legacy code relates to a no-longer supported technology. Legacy code is code with no tests. Legacy code is source code inherited from someone else. A big ball of mud is haphazardly structured, sprawling, sloppy, duct-tape and bailing wire, spaghetti code jungle.
  4. THERE’S A BRIGHT SIDE ➤ it works ➤ it brings

    (or saves) money ➤ it’s a source of domain knowledge and… always look on the bright side of life
  5. WHY MIGRATE? ➤ adding features takes ages ➤ obsolete technology

    ➤ scaling https://www.flickr.com/photos/sdobie/8226246821/
  6. MIGRATION STRATEGIES Migrating Legacy Systems: Gateways, Interfaces & the Incremental

    Approach by Michael Stonebraker and Michael L. Brodie Cold Turkey Chicken Little vs
  7. COOKBOOK ideas to build on https://flic.kr/p/7EbFkK

  8. Write tests for any new feature, bug, or any piece

    of code you’re changing. Writing tests-first is the best way to enforce a better design, and eventually crawl out of legacy. https://flic.kr/p/DmE99L
  9. COMPONENTS taking small steps https://flic.kr/p/7PQ15Q

  10. PUTTING TWIG TO WORK $loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
 
 $twig

    = new Twig_Environment($loader, [
 'cache' => __DIR__.'/../var/cache/legacy/twig',
 'debug' => true,
 ]);
  11. PUTTING TWIG TO WORK echo $twig->render('home.html.twig', ['foo' => 'bar']);

  12. LEVERAGING THE SERVICE CONTAINER - CREATING SERVICE DEFINITIONS $container =

    new ContainerBuilder();
 $container->setParameter('database_path', __DIR__.'/../db');
 $container
 ->register('db', 'PDO')
 ->addArgument('sqlite:%database_path%');
 
 $container
 ->register('foo_repository', 'FooRepository')
 ->addArgument(new Reference('db'));
  13. LEVERAGING THE SERVICE CONTAINER - CREATING SERVICE DEFINITIONS use Symfony\Component\DependencyInjection\ContainerBuilder;


    use Symfony\Component\Config\FileLocator;
 use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
 
 $container = new ContainerBuilder();
 $loader = new YamlFileLoader($container, new FileLocator(__DIR__));
 $loader->load('services.yml');
  14. LEVERAGING THE SERVICE CONTAINER - CREATING SERVICE DEFINITIONS # services.yml

    parameters:
 database_path: '%kernel.root_dir%/db'
 
 services:
 pdo:
 class: PDO
 arguments: [sqlite:%database_path%]
 foo_repository:
 class: FooRepository
 arguments: [@pdo]
  15. LEVERAGING THE SERVICE CONTAINER - CACHING $file = __DIR__.'/../cache/container.php';
 


    if (file_exists($file)) {
 require_once $file;
 $container = new ProjectServiceContainer();
 } else {
 $container = new ContainerBuilder();
 // ...
 $container->compile();
 
 $dumper = new PhpDumper($container);
 file_put_contents($file, $dumper->dump());
 }
  16. LEVERAGING THE SERVICE CONTAINER - USAGE $foo = $container->get('foo_repository')->findFoo(42);

  17. LEVERAGING THE EVENT DISPATCHER Subjects Listeners Event Dispatcher Mailer Listener

    Registration Dispatcher call notify registration.success add listener registration.success register
  18. LEVERAGING THE EVENT DISPATCHER Subjects Listeners Event Dispatcher Mailer Listener

    Registration Dispatcher call notify registration.success add listener registration.success SMS Listener add listener registration.success call register
  19. LEVERAGING THE EVENT DISPATCHER use Symfony\Component\EventDispatcher\EventDispatcher;
 
 $dispatcher = new

    EventDispatcher();
 $dispatcher->addListener(
 'registration.success',
 function (Event $event) {}
 );
 $dispatcher->addListener(
 'registration.success',
 [$listener, 'onRegistrationSuccess']
 );
  20. LEVERAGING THE EVENT DISPATCHER $event = new UserRegisteredEvent($user);
 $dispatcher->dispatch('registration.success', $event);

  21. LEVERAGING MESSAGE QUEUES Consumer Legacy Message broker produce consume Consumer

    Consumer
  22. LEVERAGING MESSAGE QUEUES curl -i -u user:pass \ -H "content-type:application/json"

    \ -XPOST http://rabbtmiq:15672/api/exchanges/%2f/image-resize/publish \ -d '{ “properties":{}, “payload":"cat.png", "payload_encoding":"string", “routing_key”:"" }' https://github.com/php-amqplib/php-amqplib https://github.com/php-amqplib/RabbitMqBundle
  23. CUT, SLICE, EXTRACT Clear separation https://flic.kr/p/4vRMY3

  24. WRAPPING THE LEGACY APP Symfony FooController BarController Router LegacyController Legacy

    GET /products
  25. WRAPPING THE LEGACY APP # app/config/routing.yml
 
 app:
 resource: "@AppBundle/Controller/"


    type: annotation
 
 legacy:
 path: /{path}
 defaults: { _controller: AppBundle:Legacy:forward }
 requirements:
 path: .*
  26. WRAPPING THE LEGACY APP // src/AppBundle/Controller/BlogController.php
 
 namespace AppBundle\Controller;
 


    use Symfony\Component\HttpFoundation\Response;
 
 final class LegacyController
 {
 public function forwardAction($path)
 {
 ob_start(); 
 require __DIR__.’/../../../legacy/index.php'; 
 $content = ob_get_clean();
 
 return new Response($content);
 }
 }
  27. SHARING THE SESSION framework:
 session:
 storage_id: session.storage.php_bridge
 save_path: /tmp
 handler_id:

    session.handler.native_file Hint: to access the legacy session from Symfony: https://github.com/theodo/TheodoEvolutionSessionBundle
  28. PROXYING TO THE NEW APP Symfony Proxy Legacy GET /products

  29. PROXYING TO THE NEW APP # my-app.conf server {
 listen

    80;
 server_name my-app.dev;
 root /my-app/legacy;
 
 location / {
 try_files $uri $uri/ /index.php$is_args$args;
 }
 
 location ~ ^/index\.php(/|$) {
 fastcgi_index index.php;
 fastcgi_pass my-app-php:9000;
 fastcgi_split_path_info ^(.+\.php)(/.*)$;
 include fastcgi_params;
 fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
 fastcgi_param DOCUMENT_ROOT $realpath_root;
 }
 }
  30. PROXYING TO THE NEW APP # my-app.conf server {
 listen

    80;
 server_name my-app.dev;
 root /my-app/legacy;
 
 location ~ ^/products/.+ {
 proxy_pass http://v2.my-app.dev;
 }
 
 location / {
 try_files $uri $uri/ /index.php$is_args$args;
 }
 
 # ...
 }
  31. PROXYING TO THE NEW APP # v2.my-app.conf server {
 listen

    80;
 server_name v2.my-app.dev;
 root /my-app/web;
 
 location / {
 try_files $uri $uri/ /app.php$is_args$args;
 }
 
 location ~ ^/app\.php(/|$) {
 fastcgi_index app.php;
 fastcgi_pass localhost:9000;
 fastcgi_split_path_info ^(.+\.php)(/.*)$;
 include fastcgi_params;
 fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
 fastcgi_param DOCUMENT_ROOT $realpath_root;
 fastcgi_param HTTP_HOST "my-app.dev";
 }

  32. GOING MICRO WITH THE MICRO KERNEL

  33. GOING MICRO WITH THE MICRO KERNEL final class AppKernel extends

    Kernel
 {
 use MicroKernelTrait;
 
 public function registerBundles()
 {
 return [
 new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
 new Symfony\Bundle\TwigBundle\TwigBundle(),
 ];
 } // ...
 }
  34. GOING MICRO WITH THE MICRO KERNEL final class AppKernel extends

    Kernel
 {
 // ...
 
 protected function configureContainer( ContainerBuilder $c, LoaderInterface $loader ) {
 $c->loadFromExtension('framework', [
 'secret' => '$ecret',
 'templating' => [
 'engines' => ['twig'],
 ],
 ]);
 }
 
 // ...
 }
  35. GOING MICRO WITH THE MICRO KERNEL final class AppKernel extends

    Kernel
 {
 // ... 
 protected function configureRoutes(RouteCollectionBuilder $routes)
 {
 $routes->add('/products/{slug}', 'kernel:productAction');
 }
 
 public function productAction($slug)
 {
 $twig = $this->container->get('twig');
 $content = $twig->render(‘product.html.twig', ['slug' => $slug]);
 
 return new Response($content);
 }
 }
  36. GOING MICRO WITH THE MICRO KERNEL // web/app.php
 
 //

    use statements omitted
 
 require __DIR__.'/../vendor/autoload.php';
 
 final class AppKernel extends Kernel
 {
 // ...
 } 
 $kernel = new AppKernel('dev', true);
 $request = Request::createFromGlobals();
 $response = $kernel->handle($request);
 $response->send();
 $kernel->terminate($request, $response);
  37. GOING FULL STACK final class AppKernel extends Kernel
 {
 //

    remove the trait
 // ...
 
 public function registerContainerConfiguration(LoaderInterface $loader)
 {
 return $loader->load( $this->getRootDir() .’/config/config_'.$this->getEnvironment().'.yml' );
 }
 }
  38. GOING FULL STACK # take parts relevant to you from

    symfony/symfony-standard configs
 imports:
 - { resource: parameters.yml }
 - { resource: security.yml }
 - { resource: services.yml }
 
 framework:
 secret: "%secret%"
 router:
 resource: "%kernel.root_dir%/config/routing.yml"
 strict_requirements: ~
 # ...
 
 twig:
 debug: "%kernel.debug%"
 strict_variables: "%kernel.debug%"
  39. DESIGNING SMALLER APPS Legacy Web Checkout Image Converter Web Product

    Catalog Inventory API
  40. HYBRID APPROACH a bit of both https://flic.kr/p/sqfbS

  41. ACCESSING THE SYMFONY KERNEL FROM A LEGACY APP Legacy GET

    /products Symfony
  42. ACCESSING THE SYMFONY KERNEL FROM A LEGACY APP use Symfony\Component\HttpFoundation\Request;

    $request = Request::createFromGlobals();
 $request->attributes->set('is_legacy', true);
 $request->server->set('SCRIPT_FILENAME', 'app.php');
 
 $kernel = new \AppKernel($environment, $debug);
 $kernel->boot();
 
 $container = $kernel->getContainer();
 $container->get('request_stack')->push($request);
  43. ACCESSING THE SYMFONY KERNEL FROM A LEGACY APP $container =

    $kernel->getContainer();
 
 
 $container->get('event_dispatcher')
 ->dispatch('registration.success', new Event());
 
 $container->get('twig')->render('template.html.twig');

  44. SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver

    Event Dispatcher kernel.request FragmentListener SessionListener LocaleListener RouterListener Firewall
  45. SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver

    Event Dispatcher ControllerListener kernel.controller RequestDataCollector Listener ParamConverter Listener TemplateListener
  46. SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver

    Event Dispatcher kernel.response CacheListener ResponseListener ProfilerListener WebDebugToolbarListener
  47. SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver

    Event Dispatcher kernel.finish_request LocaleListener RouterListener Firewall
  48. SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver

    Event Dispatcher TemplateListener kernel.view
  49. SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver

    Event Dispatcher kernel.terminate ProfilerListener EmailSenderListener
  50. SHARING EVENTS - SYMFONY KERNEL EVENTS $eventDispatcher = $container->get('event_dispatcher');
 


    $event = new GetResponseEvent( $kernel, $request, HttpKernelInterface::MASTER_REQUEST );
 $eventDispatcher->dispatch(KernelEvents::REQUEST, $event);
  51. EMBEDDING CONTROLLERS / ESI Legacy page <esi:include src="/foo" />

  52. EMBEDDING CONTROLLERS / ESI $inlineRenderer = $container->get('fragment.renderer.esi');
 
 echo $inlineRenderer->render('/foo',

    $request)->getContent(); <esi:include src="/foo" /> <div clas="foo">
 some foo content
 </div> has surrogate capability no surrogate capability
  53. @jakub_zalas @jakzal Thank you. Rate my talk, please: https://joind.in/talk/85830