Slide 1

Slide 1 text

@jakub_zalas @jakzal Jakub Zalas EATING SPAGHETTI WITH SYMFONY PHP UK 2016 https://www.flickr.com/photos/stijnnieuwendijk/8839597194/in/photostream/

Slide 2

Slide 2 text

https://www.flickr.com/photos/ocarchives/3952964087/ THE PLAN ➤ Spaghetti/legacy/mud ➤ Migration strategies ➤ Migration cookbook https://flic.kr/p/72iYve

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

WHY MIGRATE? ➤ adding features takes ages ➤ obsolete technology ➤ scaling https://www.flickr.com/photos/sdobie/8226246821/

Slide 6

Slide 6 text

MIGRATION STRATEGIES Migrating Legacy Systems: Gateways, Interfaces & the Incremental Approach by Michael Stonebraker and Michael L. Brodie Cold Turkey Chicken Little vs

Slide 7

Slide 7 text

COOKBOOK ideas to build on https://flic.kr/p/7EbFkK

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

COMPONENTS taking small steps https://flic.kr/p/7PQ15Q

Slide 10

Slide 10 text

PUTTING TWIG TO WORK $loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
 
 $twig = new Twig_Environment($loader, [
 'cache' => __DIR__.'/../var/cache/legacy/twig',
 'debug' => true,
 ]);

Slide 11

Slide 11 text

PUTTING TWIG TO WORK echo $twig->render('home.html.twig', ['foo' => 'bar']);

Slide 12

Slide 12 text

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'));

Slide 13

Slide 13 text

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');

Slide 14

Slide 14 text

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]

Slide 15

Slide 15 text

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());
 }

Slide 16

Slide 16 text

LEVERAGING THE SERVICE CONTAINER - USAGE $foo = $container->get('foo_repository')->findFoo(42);

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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']
 );

Slide 20

Slide 20 text

LEVERAGING THE EVENT DISPATCHER $event = new UserRegisteredEvent($user);
 $dispatcher->dispatch('registration.success', $event);

Slide 21

Slide 21 text

LEVERAGING MESSAGE QUEUES Consumer Legacy Message broker produce consume Consumer Consumer

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

CUT, SLICE, EXTRACT Clear separation https://flic.kr/p/4vRMY3

Slide 24

Slide 24 text

WRAPPING THE LEGACY APP Symfony FooController BarController Router LegacyController Legacy GET /products

Slide 25

Slide 25 text

WRAPPING THE LEGACY APP # app/config/routing.yml
 
 app:
 resource: "@AppBundle/Controller/"
 type: annotation
 
 legacy:
 path: /{path}
 defaults: { _controller: AppBundle:Legacy:forward }
 requirements:
 path: .*

Slide 26

Slide 26 text

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);
 }
 }

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

PROXYING TO THE NEW APP Symfony Proxy Legacy GET /products

Slide 29

Slide 29 text

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;
 }
 }

Slide 30

Slide 30 text

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;
 }
 
 # ...
 }

Slide 31

Slide 31 text

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";
 }


Slide 32

Slide 32 text

GOING MICRO WITH THE MICRO KERNEL

Slide 33

Slide 33 text

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(),
 ];
 } // ...
 }

Slide 34

Slide 34 text

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'],
 ],
 ]);
 }
 
 // ...
 }

Slide 35

Slide 35 text

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);
 }
 }

Slide 36

Slide 36 text

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);

Slide 37

Slide 37 text

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' );
 }
 }

Slide 38

Slide 38 text

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%"

Slide 39

Slide 39 text

DESIGNING SMALLER APPS Legacy Web Checkout Image Converter Web Product Catalog Inventory API

Slide 40

Slide 40 text

HYBRID APPROACH a bit of both https://flic.kr/p/sqfbS

Slide 41

Slide 41 text

ACCESSING THE SYMFONY KERNEL FROM A LEGACY APP Legacy GET /products Symfony

Slide 42

Slide 42 text

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);

Slide 43

Slide 43 text

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');


Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

SHARING EVENTS - SYMFONY KERNEL EVENTS Request Response Controller Resolver Event Dispatcher kernel.terminate ProfilerListener EmailSenderListener

Slide 50

Slide 50 text

SHARING EVENTS - SYMFONY KERNEL EVENTS $eventDispatcher = $container->get('event_dispatcher');
 
 $event = new GetResponseEvent( $kernel, $request, HttpKernelInterface::MASTER_REQUEST );
 $eventDispatcher->dispatch(KernelEvents::REQUEST, $event);

Slide 51

Slide 51 text

EMBEDDING CONTROLLERS / ESI Legacy page

Slide 52

Slide 52 text

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

 some foo content

has surrogate capability no surrogate capability

Slide 53

Slide 53 text

@jakub_zalas @jakzal Thank you. Rate my talk, please: https://joind.in/talk/85830