The document discusses modernizing legacy PHP applications using Symfony2. It outlines the challenges of a total rewrite versus a progressive rewrite. A progressive rewrite involves refactoring the application incrementally over time to decouple modules and introduce new Symfony2 features while maintaining the existing codebase. The document describes technical solutions for preventing regressions, upgrading systems, routing, sharing layouts and sessions, decoupling code, and migrating models and data as part of a progressive rewrite approach.
2. The need for progressive rewrite
The technical challenges and our solutions
The future
19/03/2013 2 THEODO
3. Legacy PHP applications are everywhere
History of PHP applications
• 79% : websites written in PHP among the top 1 million, according
to W3Techs.com
• 1994: PHP was created
• 2004: PHP5 was released. Start of OOP
• 2007: ZF 1.0.0 and SF 1.0 were released. Start of the MVC age
13 years of (mostly) spagetthi-coded web-apps
19/03/2013 3 THEODO
4. What is the problem of legacy applications?
The need for rewrite
19/03/2013 4 THEODO
5. Starting a new version from scratch is a danger to the business
The dangers of total rewrite
• New developments are invisible until the new version is finished
• You need twice more developers: one team to maintain the old
application, while the second team is writing the new version
• The probability of forgetting features during the rewrite is high
• Transition when the new version is ready becomes costly and
very dangerous: everything is impacted at once
19/03/2013 5 THEODO
6. More dangerous than you think!
The black swan blindness
A study led by two Oxford professors on 1,471 IT projects found out that:
• 1 project out of 6 ended up costing on average 3 times as much!
• large-scale computer spending were 20 times more likely to spiral out
of control than expected.
Large-scale examples:
• the complete revamp of Levi Strauss’ IT, which
cost them 192.5 million$ losses
• FoxMeyer Drugs’ bankruptcy after switching
brutally to SAP
Study: http://users.ox.ac.uk/~mast2876/WP_2011_08_15.pdf
Fox-Meyer bankruptcy: http://fr.slideshare.net/jmramireza/the-foxmeyer-drugs-bankruptcy-was-it-a-failure-
of-erp-2332065
19/03/2013 6 THEODO
7. Progressive rewrite is the solution… but is very challenging
The challenge of progressive rewrite
• You have to work with hard-to-read code
• All the aspects of an application in production are concerned:
source code of course
system
data
cache
remote webservices, etc.
• The major conceptual challenge in all those aspects is decoupling
of modules. Just like in scaling!
19/03/2013 7 THEODO
8. Progressive rewrite is more profitable
Unbeatable time-to-market
Functionalities
Functionalities
Evolution
Time Time
• No new features for months • Only 1 app to maintain
• 2 apps to maintain • Lower regression risks
• Unbeatable time-to-market
19/03/2013 8 THEODO
9. The need for progressive rewrite
The technical challenges and our solutions
The future
19/03/2013 9 THEODO
10. Theodo Evolution
Our solution at Theodo
• R&D project started at Theodo in 2012
• Aims to solve all these issues to make app rewriting agile again
• Our dream: be able to code evolutions on the legacy application
without touching the legacy code.
19/03/2013 10 THEODO
11. The technical experts behind Theodo Evolution
Theodo Evolution team
19/03/2013 11 THEODO
12. The big picture of the different technical aspects to solve
The technical challenges
• Preventing regressions
• Upgrading the system
• Routing
• Sharing the layout
• Sharing the session/authentication
• Decoupling the code
• Migrating the model and data
19/03/2013 12 THEODO
14. Belt and straps are necessary… and won’t be enough…
Preventing regressions
19/03/2013 14 THEODO
15. Functionally test what could harm your business
Preventing regressions
• By definition, spaghetti code is deeply coupled. Touching one part
breaks something at the other end
• Create functional tests on the most critical scenarios
• Mink + ZombieJS: https://github.com/Behat/Mink
19/03/2013 15 THEODO
16. Setup good monitoring
Preventing regressions
The most thorough functional testing is done... by putting in production!
• Monitor production well.
• Errors 500, 404
• response time with Pingdom
• newrelic
• Make everybody aware of the heightened risks
• Monitor business metrics with StatsD, give the marketing team a
Graphite dashboard => get free high-level monitoring!
19/03/2013 16 THEODO
17. And create a fast and easy deployment pipeline
Preventing regressions
• Deploy often and small updates. Aim ten a day, not just one a week!
• Setup a fast rollback system
• Setup a fast deployment system, because you want to make it faster
to correct small problems than to rollback
19/03/2013 17 THEODO
18. Simple automated deployment with Fabric
Automated deployment
@roles('prod')
def deploy():
tag = "%s/%s" % (_getrole(), strftime("%Y/%m-%d-%H-%M-%S"))
local('git tag -a %s -m "%s"' % (tag, _getrole()))
local('git push --tags')
run('git fetch’)
run('git fetch origin --tags')
tag = run('git tag -l %s/* | sort | tail -n1' % _getrole())
run('git checkout ' + tag)
run('php composer.phar install')
19/03/2013 18 THEODO
20. Migrate to a modern environment that supports Symfony2
…and improves performance as a bonus.
Upgrading the system
• Symfony2 requires:
• PHP 5.3.3
• Sqlite3, JSON, ctype
• date.timezone set in php.ini
• php app/check.php
19/03/2013 20 THEODO
21. Check that the legacy code will support PHP 5.3/5.4
Upgrading the system
• Phpcs CodeSniffs for 5.3 and 5.4 compatibility:
https://github.com/wimg/PHPCompatibility
• Setup a pre-production environment in the upgraded
environment and run your functional tests on it
• And this time provision your environment with Puppet or Chef!
Check Blueprint: https://github.com/devstructure/blueprint
19/03/2013 21 THEODO
23. Routing between the old and the new
Routing
• The beauty of PHP: some legacy apps have simply no routing system!
• Host the new app next to the old app, with clear URL differences, and
proxy at the server level
• Subdomain: v2.myapp.com
• Subfolder: www.myapp.com/v2/
• My favourite: create a catchall route with Symfony2
19/03/2013 23 THEODO
24. The CatchAll Route
Routing
class LegacyController extends Controller
{
/**
* @Route("/{filename}.php", name="_proxy")
*/
public function proxyAction($filename)
{
ob_start();
include $filename . '.php';
// Replace exit()s and die()s
// or you might never end up here
return new Response(ob_get_clean());
}
}
19/03/2013 24 THEODO
26. For end-users « progressive rewrite » = « same template »
Sharing the layout
• Copy-pasting the layout is not a good solution
• Our solution : crawl the legacy layout and include it as ESIs
<esi:include src="{{ path('legacylayout_top') }}" />
{% block body %}{% endblock %}
<esi:include src="{{ path('legacylayout_bottom') }}" />
19/03/2013 26 THEODO
27. The « crawling » part of the ESI sub-request
Sharing the layout
$this->client->setHeader('Cookie', $currentCookie);
if ($request->headers->has('authorization')) {
$this->client->setHeader(
'Authorization',
$request->headers->get('authorization')
);
}
if ($this->session->isStarted()) {
$this->session->save();
}
$this->client->request('GET', $url);
$esiContent = $this->client->getResponse()->getContent();
19/03/2013 27 THEODO
29. Make the legacy session accessible from Symfony2
Sharing the session/authentication
• You want to access what your legacy app has put in the session
from within Symfony2…
• BUT Symfony2 expects session information to be neatly stored in
arrays in the « _sf2_attributes » namespace
to make your legacy session accessible, you need to :
• register « Bags » for each of your legacy $_SESSION keys
• create a « ScalarBag » type when these values are not arrays
19/03/2013 29 THEODO
30. Example for a Symfony1 Session
Sharing the session/authentication
// onKernelRequest
foreach ( array('symfony/user/sfUser/credentials',
'symfony/user/sfUser/attributes’) as $namespace) {
$bag = new NamespacedAttributeBag($namespace, '.');
$bag->setName($namespace);
$session->registerBag($bag);
}
19/03/2013 30 THEODO
41. You need to create the dream API and slowly refactor around it
Decoupling the code
A
A P
P I
A
I P
I
A
P
I
A
P
I A
A
P
P
I
I
19/03/2013 41 THEODO
42. You need to create the dream API and slowly refactor around it
Decoupling the code
A
A P
P I
A
I P
I
A
P
I
A
P
I A
A
P
P
I
I
19/03/2013 42 THEODO
43. You need to create the dream API and slowly refactor around it
Decoupling the code
A
A P
P I
A
I P
I
A
P
I
A
P
I A
A
P
P
I
I
19/03/2013 43 THEODO
44. You need to create the dream API and slowly refactor around it
Decoupling the code
A
A P
P I
A
I P
I
A
P
I
A
P
I A
A
P
P
I
I
19/03/2013 44 THEODO
45. This is called « Facade Pattern »
Decoupling the code
Wikipedia
A facade is an object that provides a simplified interface to a larger
body of code, such as a class library.
[…]
A façade reduces dependencies of outside code on the inner
workings of a library, since most code uses the facade, thus allowing
more flexibility in developing the system;
19/03/2013 45 THEODO
46. Symfony2 Service Container
Decoupling the code
• With Symfony2, the new API of the Facade Pattern is a service
• Create a bundle for every « module » you identified.
• Create a service for every bundle you now have, that will serve as
your Facade API
• … and start using these services!
19/03/2013 46 THEODO
47. Example: Model services
Decoupling the code
namespace MyCompanyMyFreshBundleServices;
class PostService
{
public function getById($postId)
{
$post = PostTable::strangeLegacyMethodById($postId);
return $post;
}
}
19/03/2013 47 THEODO
49. A typical MySQL database is highly coupled
Migrating the model and data
19/03/2013 49 THEODO
50. Decoupling a relational DB is like attacking a giant monster
Migrating the model and data
19/03/2013 50 THEODO
51. The classical solution is syncing two versions of the data
Migrating the model and data
• Syncing can be done with ETL tools like Kettle or with custom code in
the new application
• To avoid unbearable headaches, it is highly recommended to write in
only one DB. Which means to rewrite parts of the legacy code to save,
update and delete in the new DB
19/03/2013 51 THEODO
52. A « bearable headache » solution:
Migrating the model and data
Legacy Application
New application
Routing
19/03/2013 52 THEODO
53. A « bearable headache » solution:
Migrating the model and data
Legacy Application
New application
Routing
19/03/2013 53 THEODO
54. MongoDB and DoctrineODM make it (a little) easier
Migrating the model and data
• Remember: progressive rewrite relies on decoupling, just like scaling.
Non-relational DBs make partial evolutions easier in the long-term
• In a document DB like MongoDB you can have in the same collection
data which coexist in different versions.
• To migrate them « just in time » use /** @MongoDBPreLoad */ from
the Doctrine ODM
19/03/2013 54 THEODO
55. The need for progressive rewrite
The technical challenges and our solutions
The future
19/03/2013 55 THEODO
56. The evolution of Theodo Evolution
Theodo Evolution strategy
• The Theodo team is working currently on 6 progressive rewrites of
huge applications to Symfony2
• Every technical solution for progressive rewrite to Symfony2 is
bundled in our project Theodo Evolution
• The goal: make it an out-of-the-box solution to work on legacy apps
without touching legacy code
19/03/2013 56 THEODO
57. How you can profit from Theodo Evolution?
Theodo Evolution strategy
• Some Theodo Evolution bundles will be open-
sourced.
• SessionIntegrationBundle is being polished
right now by Pierre-Henri and Marek
• Contact us @theodo or fabriceb@theodo.fr
19/03/2013 57 THEODO