switch ($page) {
case "user":
$user = new User($user_id);
if ($action == 'edit') {
$body = $user->editUser();
} else {
$body = $user->userInfo();
}
break;
case "page2":
$heading = "Some page";
$body = get_some_page();
break;
// ... ~1500 lines more
}
Classes (A.K.A. God objects)
class User extends Db
{
// inherits from Db (but sometimes overridden)
function save() {}
function delete() {}
function log() {}
function checkAccess() {}
function formTextInput() {}
function validateDate() {}
// ... lots more
function printMenu() {}
function printInfo() {}
function printUpdateForm() {}
// ... lots more
}
Forward non-Symfony routes to legacy
Forward non-Symfony routes to legacy
Forward non-Symfony routes to legacy
class LegacyKernel implements HttpKernelInterface
{
public function handle(Request $request, ...)
{
ob_start();
$legacyDir = dirname($this->legacyAppPath);
chdir($legacyDir);
require_once $this->legacyAppPath;
$response = new Response(ob_get_clean());
return $response;
}
}
class LegacyKernelListener implements EventSubscriberInterface
{
public function onKernelException($event)
{
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$request = $event->getRequest();
$response = $this->legacyKernel->handle($request);
// Override 404 status code with 200
$response->headers->set('X-Status-Code', 200);
$event->setResponse($response);
}
}
}
Alternatives to LegacyKernel & LegacyKernelListener
Try to include all 'filename.php' routes
Whitelist all possible 'filename.php' routes
Watch for certain GET param
etc.
LegacyController + "Catch All" route
class LegacyController extends Controller
{
/**
* @Route("/{filename}.php", name="_legacy")
*/
public function legacyAction($filename)
{
$legacyPath = $this->container
->getParameter('legacy.path');
ob_start();
chdir($legacyAppPath);
require_once $legacyAppPath . $filename . '.php';
$response = new Response(ob_get_clean());
return $response;
}
}
Goal: Use Symfony services and parameters in legacy app
Assign Request & Service Container
to a variable in legacy scope
Get services & parameters from the container in legacy app
Any Symfony component or Symfony framework built-in service
Any custom service written in Symfony bundles
Any configuration parameters from parameters.yml
class LegacyKernel implements HttpKernelInterface
{
public function handle(Request $request, ...)
{
// ...
// Assign Container to a local variable
// so it can be used in legacy app
$container = $this->container;
// Request is already in a local variable
require_once $this->legacyAppPath;
// ...
}
}
Legacy index.php
// Make Symfony Container and Request global
// so they can be used in other functions & classes
/** @var \Symfony\Component\DependencyInjection\Container $container */
$GLOBALS['container'] = $container;
/** @var \Symfony\Component\HttpFoundation\Request $request */
$GLOBALS['request'] = $request;
Exception detected! Global container!! OMG!!1!
500 Internal Server Conflict - RuntimeOmgException
Goal: both legacy and Symfony apps use the same database
Symfony side uses Doctrine ORM
Map database tables as Doctrine Entities
Only when needed
Use meaningful names in the Entity
config.yml
...
doctrine:
dbal:
...
schema_filter: ~^(?!(^some_table$)|(^stuff$) ⏎
|(^super_secret_admin_stuff$) ⏎
... # many, many tables here
(^last_table$))~
Part 1: Symfony integration
Legacy requests go through Symfony
Symfony service container in legacy
Shared configuration
Same layout and UI
Shared authentication
Easy for developers to work with and deploy
Part 2: Refactoring legacy
Gradual migration
Smoothly migrate old code over time
Don't try to do too much at once
Write tests!
Write tests
Unit & functional tests if you can
If your legacy project is immune to unit testing, write characterization tests
"a characterization test is a means to describe (characterize) the actual behavior of an existing piece of software, and therefore protect existing behavior of legacy code..." en.wikipedia.org/wiki/Characterization_test
Database access
Goal: Separate database access from business logic
Move database queries to Repository classes
Can be Doctrine Entity repository but doesn't need to be
Get repository object from container in legacy app
UserRepository.php
class UserRepository extends EntityRepository
{
/**
* @param User $user
*/
public function save(User $user)
{
$this->_em->persist($user);
$this->_em->flush();
}
}
Legacy functions.php etc.
Before
function getUsername($id)
{
$sql = "SELECT username FROM user WHERE id=" . (int)$id;
$result = mysql_fetch_array(mysql_query($sql));
return $result[0];
}
Legacy functions.php etc.
After
function getUsername($id)
{
global $container;
$userRepository = $container->get('acme.demo.repository.user');
$user = $userRepository->find($id);
return $user->getUsername();
}
View templates
Goal: Decouple presentation logic from business logic