Wrap up your legacy application with Symfony

January 06, 2015Kinga Sziliagyi3 min read

At Theodo we have had several projects consisting of adding new features to an existing PHP code using Symfony1, CodeIgniter or homemade frameworks.
Rewriting these applications from scratch would be very costly and dangerous for the businesses. Therefore, we prefer
embedding the legacy code in a full-stack Symfony2 project.
By doing so we can write the new features in a the Symfony application and, if needed, migrate legacy code gradually.

Theodo Evolution

The principle is quite simple:

  • let Symfony return a response to the incoming request
  • if there is no matching route, delegate the request to the legacy application
  • and, if still no matching, return a 404 response

Theodo Evolution is a set of tools combining the practices we have been using for several years to extend legacy applications.
In the rest of this article, I will provide a brief introduction to this bundle.

Overriding the RouterListener

Theodo Evolution interferes by overriding Symfony's RouterListener:

namespace Theodo\Evolution\Bundle\LegacyWrapperBundle\DependencyInjection\Compiler;

class ReplaceRouterPass implements CompilerPassInterface
{

    public function process(ContainerBuilder $container)
    {
        if ($container->hasDefinition('theodo_evolution_legacy_wrapper.router_listener')) {
            $routerListener = $container->getDefinition('router_listener');

            $definition = $container->getDefinition('theodo_evolution_legacy_wrapper.router_listener');
            $definition->replaceArgument(1, $routerListener);

            $container->setAlias('router_listener', 'theodo_evolution_legacy_wrapper.router_listener');
        }
    }
}

This custom RouterListener starts by delegating the request handling to Symfony's RouterListener.
If the request does not match any controller, the legacy listener catches the NotFoundHttpException and asks the LegacyKernel to handle it:

namespace Theodo\Evolution\Bundle\LegacyWrapperBundle\EventListener;

class RouterListener implements EventSubscriberInterface
{
    public function onKernelRequest(GetResponseEvent $event)
    {
        try {
            $this->routerListener->onKernelRequest($event);
        } catch (NotFoundHttpException $e) {
            if (null !== $this->logger) {
                $this->logger->info('Request handled by the '.$this->legacyKernel->getName().' kernel.');
            }

            $response = $this->legacyKernel->handle($event->getRequest(), $event->getRequestType(), true);
            if ($response->getStatusCode() !== 404) {
                $event->setResponse($response);

                return $event;
            }
        }
    }
}

LegacyKernel

Symfony's Kernel is an implementation of the HttpKernelInterface and therefore has to be able to tell how to transform an incoming Request to a Response object.
The same is true for the LegacyKernel. In addition, the LegacyKernel should also know how to autoload the legacy application.
(For detailed information on autoloading take a look at the autoloading guide)

If your are about to wrap a Symfony 1.4 or a CodeIgniter application you are lucky, Theodo Evolution provides you an extension of the abstract LegacyKernel class.
Otherwise it's your job to implement the boot and handle functions. Before you start, have a look at the Symfony14Kernel
and CodeIgniterKernel classes.

As I said, the basic idea is quite simple: if Symfony cannot handle the incoming request let your legacy application take care of it.
However, in practice there is lot to do:
Autoloading, sharing the authentication and the database, managing legacy assets, harmonizing the layout, routing between the two applications, etc.

More about these topics soon!

Kinga Sziliagyi

Kinga Sziliagyi

Web Developer at Theodo