Dealing With Legacy PHP Applications

100 %
0 %
Information about Dealing With Legacy PHP Applications

Published on August 8, 2007

Author: viget

Source: slideshare.net

Description

Clinton Nixon describes some common problems found when inheriting legacy PHP applications and how to deal with them. He presents a solution that will encapsulate business logic and clean up the view layer without requiring a full-fledged MVC framework.

Dealing with Legacy PHP Applications Prepared by Clinton R. Nixon, Viget Labs 2007 July

What is a legacy application? Code you didn't write Code you wouldn't write Untested code Code with competing visions Dealing with Legacy PHP Applications 2007 Jul

What do we do with legacy code? We refactor! Refactoring is safely changing the implementation of code without changing the behavior of code. Dealing with Legacy PHP Applications 2007 Jul

Bad code smells What are some specific problems in legacy PHP code? ‣ No separation between PHP and HTML ‣ Lots of requires, few method calls ‣ Global variables Dealing with Legacy PHP Applications 2007 Jul

No separation between PHP and HTML <h1>Orders</h1> <?php $account = new Account($account_id); $account->loadOrders(); foreach ($account->getOrders() as $order) { echo '<h2>' . $order['id'] . '</h2>'; echo '<p>Status: ' . lookup_status($order['status_id']) . '<br />; echo 'Total: '; $total = array_reduce($order['purchases'], create_function('$a, $b', '$a += $b; return $a')); echo $total . '</p>'; } ?> Dealing with Legacy PHP Applications 2007 Jul

Separating controllers and views Even without a solid MVC architecture, this helps You can do this in several safe and easy steps You absolutely will find pain points Dealing with Legacy PHP Applications 2007 Jul

Why do I need to do this? Your code complexity will decrease. echo isn't as fun as it looks. You will find hidden bugs and mistakes. Dealing with Legacy PHP Applications 2007 Jul

The simplest view class class View { protected static $VIEW_PATH = '/wherever/views/'; public function assign($name, $value) { return $this->$name = $value; } public function render($filename) { $filename = self::$VIEW_PATH . $filename; if (is_file($filename)) { ob_start(); include($filename); return ob_get_clean(); } } } Dealing with Legacy PHP Applications 2007 Jul

Obvious improvements to make Error handling Assignment by reference Changing view path Display convenience method Use-specific subclasses with helper methods Dealing with Legacy PHP Applications 2007 Jul

The separation process Gather all your code Sift and separate controller from view code Assign variables to the view object Change all variable references in the view code Split the files Find duplicated views Dealing with Legacy PHP Applications 2007 Jul

The rules of view code Allowed: ‣ Control structures ‣ echo, or <?= $var ?> ‣ Display-specific functions, never nested Not allowed: ‣ Assignment ‣ Other function calls Dealing with Legacy PHP Applications 2007 Jul

Gather and sift code The step you won't like: gather all code for this controller Wipe brow Draw a line at the top of the code Move controller code above this line, fixing as necessary ‣ At this point, everything is view code Dealing with Legacy PHP Applications 2007 Jul

Code gathered <?php // View code goes below here ?> <h1>Orders</h1> <?php $account = new Account($account_id); $account->loadOrders(); foreach ($account->getOrders() as $order) { echo '<h2>' . $order['id'] . '</h2>'; echo '<p>Status: ' . lookup_status($order['status_id']) . '<br />'; echo 'Total: '; $total = array_reduce($order['purchases'], create_function('$a, $b', '$a += $b; return $a')); echo $total . '</p>'; } Dealing with Legacy PHP Applications 2007 Jul

Some controller code moved <?php $account = new Account($account_id); $account->loadOrders(); ?> <?php // View code goes below here ?> <h1>Orders</h1> <?php foreach ($account->getOrders() as $order) { ?> <h2><?= $order['id'] ?></h2> <p>Status: <?= lookup_status($order['status_id']) ?> <br /> Total: <?= array_reduce($order['purchases'], create_function( '$a, $b', '$a += $b; return $a')) ?> </p> <?php } ?> Dealing with Legacy PHP Applications 2007 Jul

Alternative control structures <?php if ($foo): ?> ... <?php endif; ?> <?php foreach ($this as $that): ?> ... <?php endforeach; ?> Dealing with Legacy PHP Applications 2007 Jul

Using alternative control structures <?php $account = new Account($account_id); $account->loadOrders(); ?> <?php // View code goes below here ?> <h1>Orders</h1> <?php foreach ($account->getOrders() as $order): ?> <h2><?= $order['id'] ?></h2> <p>Status: <?= lookup_status($order['status_id']) ?> <br /> Total: <?= array_reduce($order['purchases'], create_function( '$a, $b', '$a += $b; return $a')) ?> </p> <?php endforeach; ?> Dealing with Legacy PHP Applications 2007 Jul

A frustrating problem <?php foreach ($account->getOrders() as $order): ?> <h2><?= $order['id'] ?></h2> <p>Status: <?= lookup_status( $order['status_id']) ?> <br /> Total: <?= array_reduce($order['purchases'], create_function('$a, $b', '$a += $b; return $a')) ?> </p> <?php endforeach; ?> Dealing with Legacy PHP Applications 2007 Jul

Dealing with this problem There are two approaches. ‣ You can create a new array of variables for your view. ‣ Or, you can encapsulate this logic in an object. Dealing with Legacy PHP Applications 2007 Jul

Our new order object <?php class Order { ... public function getStatus() { return lookup_status($this->getStatusId()); } public function getTotal() { return array_reduce($this->getPurchases(), create_function('$a, $b', '$a += $b; return $a')); } } ?> Dealing with Legacy PHP Applications 2007 Jul

Logic removed from view code <?php $account = new Account($account_id); $account->loadOrders(); $orders = $account->getOrders(); ?> <?php // View code goes below here ?> <h1>Orders</h1> <?php foreach ($orders as $order): ?> <h2><?= $order->getId() ?></h2> <p>Status: <?= $order->getStatus() ?> <br /> Total: <?= $order->getTotal() ?> </p> <?php endforeach; ?> Dealing with Legacy PHP Applications 2007 Jul

Change all variables to view object variables Assign variables to the view object: $view->assign('foo', $foo); One-by-one, change variables in view code. Test to convince yourself. You will probably iterate back to the previous step. Document inputs to the view. Dealing with Legacy PHP Applications 2007 Jul

View object created <?php $account = new Account($account_id); $account->loadOrders(); $orders = $account->getOrders(); $view = new View(); $view->assign('orders', $orders); ?> <?php // View code goes below here ?> <h1>Orders</h1> <?php foreach ($view->orders as $order): ?> <h2><?= $order->getId() ?></h2> <p>Status: <?= $order->getStatus() ?> <br /> Total: <?= $order->getTotal() ?> </p> <?php endforeach; ?> Dealing with Legacy PHP Applications 2007 Jul

Separate the files Create a new file for the view code. Important! Search and replace $view with $this. Test one more time. Dealing with Legacy PHP Applications 2007 Jul

Our two files <?php $account = new Account($account_id); $account->loadOrders(); $orders = $account->getOrders(); $view = new View(); $view->assign('orders', $orders); $view->display('orders.tpl'); ?> <h1>Orders</h1> <?php foreach ($view->orders as $order): ?> <h2><?= $order->getId() ?></h2> <p>Status: <?= $order->getStatus() ?> <br /> Total: <?= $order->getTotal() ?> </p> <?php endforeach; ?> Dealing with Legacy PHP Applications 2007 Jul

Find duplicated views As you do this to multiple controllers, you will see repetition. There will probably be subtle differences. Take the time to re-work these so you can re-use view files. Note! You can include views in other views with: $this->render('included_file.tpl'); Dealing with Legacy PHP Applications 2007 Jul

Using nested requires instead of function calls <?php require_once('db_setup_inc.php'); require_once('account_auth_inc.php'); require_once('i18n_inc.php'); echo '<h1>Orders for account #' . $account_id . '</h1>'; require('get_all_orders_inc.php'); ... Dealing with Legacy PHP Applications 2007 Jul

Untangling a require web Require statements which call other require statements. Can be very complex. Dependent on application structure. Dealing with Legacy PHP Applications 2007 Jul

Reasons to untangle this web Remove unneeded complexity. Create less procedural code. Prior to PHP 5.2, require_once and include_once are more expensive than you would think. If you are requiring class definitions, and you have a standard file naming method, use __autoload(). Dealing with Legacy PHP Applications 2007 Jul

The untangling process Identify inputs Identify outputs Wrap the file in a method Refactor method Move method to correct location Dealing with Legacy PHP Applications 2007 Jul

Identify inputs and outputs Find all variables expected to be set before this file is included. One possible way: execute this file by itself. Find all variables expected to be set or mutated by this file. Set variables are easy: comment out the require and watch the errors. Mutated is the set of inputs changed. Learn to search for these! Dealing with Legacy PHP Applications 2007 Jul

account_auth_inc.php <?php $auth_token = $_COOKIE['token']; if ($auth_token) { $acct_id = $db->GetOne('SELECT acct_id FROM logins WHERE auth_token = ?', array($auth_token)); } if ($acct_id) { $acct = new Account($acct_id); } else { $acct = null; } $_COOKIE['token'] = gen_new_token($auth_token); Dealing with Legacy PHP Applications 2007 Jul

Wrap the file in a function Wrap the entire include in a function. Pass all input variables. Return all output variables as an array. And then, call that function at the bottom of the required file! This is a mess! Dealing with Legacy PHP Applications 2007 Jul

Function-wrapped <?php function account_auth($db, $auth_token) { if ($auth_token) { $acct_id = $db->GetOne('SELECT acct_id FROM logins WHERE auth_token = ?', array($auth_token)); } if ($acct_id) { $acct = new Account($acct_id); } else { $acct = null; } return array($acct, gen_new_token($auth_token)); } list($acct, $_COOKIE['token']) = account_auth($db, $_COOKIE['token']); Dealing with Legacy PHP Applications 2007 Jul

Refactor until complete Tease out the functions, or objects, inside this function. If you are returning a lot of data, see if it can be an object. Leave your temporary big function in place, so that your outside code doesn't break. Keep updating it to deal with your refactoring. Dealing with Legacy PHP Applications 2007 Jul

Moved token handling to Account <?php function account_auth($db, $auth_token) { // Instead of null, we now return an unloaded Account. $acct = new Account(); if ($auth_token) { // SQL code from before $acct->loadFromToken($auth_token); // Token generation and cookie setting $acct->genNewToken($auth_token); } return $acct; } $acct = account_auth($db, $_COOKIE['token']); Dealing with Legacy PHP Applications 2007 Jul

Move to correct location Finally! Figure out where these functions or objects should live in your application. Move them there. Find where the require is called throughout your application, and replace that with your new function call or object method. Dealing with Legacy PHP Applications 2007 Jul

Global variables everywhere <?php $account_id = $_POST['acct_id']; $account = new Account($account_id); function getPurchases() { global $account; global $database; ... } function getLanguage() { global $account; global $database; global $i18n; ... } Dealing with Legacy PHP Applications 2007 Jul

Removing globals one by one Common globals: ‣ $_POST and $_GET ‣ Session or cookie data ‣ Database handles ‣ User account ‣ Language Dealing with Legacy PHP Applications 2007 Jul

Do you still have register_globals on? You may have heard: this is a bad idea. You may think that it will be impossible to fix. It's not. Turn on E_ALL. Spider your site and grep for uninitialized variables. It's some work, but not as hard as you think. It's worth it. Dealing with Legacy PHP Applications 2007 Jul

$_POST and $_GET These aren't horrible. But not horrible isn't a very high standard. class InputVariable { public function __construct($name) {...} public function isSet() {...} public function isGet() {...} public function isPost() {...} public function getAsString() {...} public function getAsInt() {...} ... } Dealing with Legacy PHP Applications 2007 Jul

The database global object Very common in PHP code Again, not horrible Prevents testing Prevents multiple databases Dealing with Legacy PHP Applications 2007 Jul

Parameterizing the DB handle Does it need to be everywhere? Can you pass it in to a function or to a constructor? The process is simple. ‣ Add database parameter. ‣ Pass in that global variable. ‣ If the call is not in global scope, find out how to pass in that variable to the current scope. ‣ Repeat. Dealing with Legacy PHP Applications 2007 Jul

Parameterizing globals <?php $account_id = $_POST['acct_id']; $account = new Account($database, $account_id); function getPurchases($account) { global $account; global $database; ... } function getLanguage($account, $i18n) { global $account; global $database; global $i18n; ... } Dealing with Legacy PHP Applications 2007 Jul

Maybe it does have to be everywhere Use a singleton. But not really. Make a way to change the singleton instance. ‣ Global define or environment variable. ‣ Static mutator. Dealing with Legacy PHP Applications 2007 Jul

A quick recap What are some specific problems in legacy PHP code? ‣ Mixed PHP and HTML – confusion between controller and view ‣ Use of require statements instead of function calls ‣ Unnecessary global variables causing dependencies Dealing with Legacy PHP Applications 2007 Jul

Further reading Working Effectively With Legacy Code, Michael Feathers Refactoring, Martin Fowler Dealing with Legacy PHP Applications 2007 Jul

Questions? clinton@viget.com Slides available at http://www.slideshare.net/viget and http://clintonrnixon.net. Dealing with Legacy PHP Applications 2007 Jul

Add a comment

Related pages

Modernizing Legacy… by Paul M. Jones [PDF/iPad/Kindle]

If you feel overwhelmed by a legacy application, "Modernizing Legacy Applications in PHP" is the book for ... read Modernizing Legacy Applications in PHP.
Read more

Virtualization: Dealing with Legacy Apps | TechNet Magazine

Virtualization: Dealing with Legacy Apps. The ability to seamlessly continue to manage legacy applications is one of the many tangible benefits of ...
Read more

Modernizing Legacy Applications in PHP: Review

That's a really great book. How does Modernizing Legacy Applications in PHP compare to it? I've just read the free sample available on Leanpub, ...
Read more

Test your Legacy PHP Application with Function Mocks ...

Test your Legacy PHP Application with ... for PHP developers (me included) is dealing ... all those legacy applications that use procedural PHP. ...
Read more

Modernizing Legacy Applications In PHP - murze.be

Modernizing Legacy Applications In PHP. ... An excellent read for if you’re dealing with a large legacy code base that mainly consists of spaghetti.
Read more

CIO Outlook: Dealing With Legacy Applications | WIRED

CIO Outlook: Dealing With Legacy Applications SUBSCRIBE. Open Search Field. Search. Business; culture; Design; Gear; Science; Security; transportation ...
Read more

Application Reengineering: Building Web-Based Applications ...

Application Reengineering: Building Web-Based Applications and Dealing with Legacies ... Dealing with Legacy Applications: An Overview. 9: ...
Read more

Rewrite your legacy PHP apps in... PHP! - symfony.fi

Rewrite your legacy PHP apps in ... Once you've upgraded your PHP and sure that the legacy application runs on it, you can start work on improvements.
Read more

GitHub - sebastianbergmann/de-legacy-fy: Tool for dealing ...

de-legacy-fy - Tool for dealing with ... and ideas that proved to be effective at dealing with legacy PHP applications into code and make ...
Read more