dealing with legacy php applications
TRANSCRIPT
![Page 2: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/2.jpg)
What is a legacyapplication?
Code you didn't write
Code you wouldn't write
Untested code
Code with competing visions
![Page 3: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/3.jpg)
What do we do withlegacy code?
We refactor!
Refactoring is safely changing theimplementation of code withoutchanging the behavior of code.
![Page 4: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/4.jpg)
Bad code smellsWhat are some specific problems in legacy PHP code?
No separation between PHP and HTML
Lots of requires, few method calls
Global variables
![Page 5: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/5.jpg)
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>';}?>
![Page 6: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/6.jpg)
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
![Page 7: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/7.jpg)
Why do I need to do this? Your code complexity will increase
echo isn't as fun as it looks
Youwill find hidden bugs and mistakes
![Page 8: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/8.jpg)
The simplest view classclass 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();
}}
}
![Page 9: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/9.jpg)
Obvious improvements to make Error handling
Assignment by reference
Changing view path
Display convenience method
Use-specific subclasses with helper methods
![Page 10: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/10.jpg)
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
![Page 11: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/11.jpg)
The rules of view codeAllowed:
Control structures
echo, or <?= $var ?>
Display-specific functions, never nested
Not allowed:
Assignment
Other function calls
![Page 12: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/12.jpg)
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
![Page 13: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/13.jpg)
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>';}?>
![Page 14: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/14.jpg)
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 } ?>
![Page 15: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/15.jpg)
Alternative control structures
<?php if ($foo): ?>...<?php endif; ?>
<?php foreach ($this as $that): ?>...<?php endforeach; ?>
![Page 16: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/16.jpg)
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; ?>
![Page 17: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/17.jpg)
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; ?>
![Page 18: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/18.jpg)
Dealing with this problemThere are two approaches.
You can create a new array of variables for your view.
Or, you can encapsulate this logic in an object.
![Page 19: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/19.jpg)
Our new order object<?phpclass 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'));
}}?>
![Page 20: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/20.jpg)
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; ?>
![Page 21: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/21.jpg)
Change all variables toview object variables Assign variables to the view object.
$view->assign('foo', $foo);
One-by-one, change variables in view code.
Test to convince yourself.
Youwill probably iterate back to the previous step.
Document inputs to the view.
![Page 22: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/22.jpg)
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; ?>
![Page 23: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/23.jpg)
Separate the files Create a new file for the view code.
Important! Search and replace $view with $this.
Test one more time.
![Page 24: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/24.jpg)
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 ($this->orders as $order): ?><h2><?= $order->getId() ?></h2><p>Status: <?= $order->getStatus() ?><br />Total: <?= $order->getTotal() ?></p>
<?php endforeach; ?>
![Page 25: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/25.jpg)
Find duplicated views As you do this to multiple controllers, you will seerepetition.
There will probably be subtle differences.
Take the time to re-work these so you can re-use viewfiles.
Note! You can include views in other views with
$this->render('included_file.tpl');
![Page 26: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/26.jpg)
Using nested requiresinstead of function calls
<?phprequire_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');
...
![Page 27: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/27.jpg)
Untangling a require web Require statements which call other require statements.
Can be very complex.
Dependent on application structure.
![Page 28: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/28.jpg)
Important reasons tountangle this web Remove unneeded complexity.
Create less procedural code.
Prior to PHP 5.2, require_once and include_onceare more expensive than you would think.
If you are requiring class definitions, and you have astandard file naming method, use __autoload().
![Page 29: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/29.jpg)
The untangling process Identify inputs
Identify outputs
Wrap the file in a method
Refactor method
Move method to correct location
![Page 30: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/30.jpg)
Identify inputs and outputs Find all variables expected to be set before this file isincluded.
One possible way: execute this file by itself.
Find all variables expected to be set or mutated by thisfile.
Set variables are easy: comment out the require andwatch the errors.
Mutated is the set of inputs changed. Learn to search forthese!
![Page 31: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/31.jpg)
account_auth_inc.php<?php$auth_token = $_COOKIE['token'];
if ($auth_token) {$acct_id = $db->GetOne('SELECT acct_id FROMlogins WHERE auth_token = ?', array($auth_token));
}if ($acct_id) {$acct = new Account($acct_id);
} else {$acct = null;
}$_COOKIE['token'] = gen_new_token($auth_token);
![Page 32: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/32.jpg)
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 requiredfile!
This is a mess!
![Page 33: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/33.jpg)
Function-wrapped<?phpfunction account_auth($db, $auth_token) {if ($auth_token) {$acct_id = $db->GetOne('SELECT acct_id FROMlogins 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']);
![Page 34: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/34.jpg)
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 anobject.
Leave your temporary big function in place, so that youroutside code doesn't break. Keep updating it to deal withyour refactoring.
![Page 35: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/35.jpg)
Moved token handling to Account<?phpfunction 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']);
![Page 36: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/36.jpg)
Move to correct location Finally!
Figure out where these functions or objects should live inyour application.
Move them there.
Find where the require is called throughout yourapplication, and replace that with your new function callor object method.
![Page 37: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/37.jpg)
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;...
}
![Page 38: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/38.jpg)
Removing globals one by oneCommon globals:
$_POST and $_GET
Session or cookie data
Database handles
User account
Language
![Page 39: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/39.jpg)
Do you still haveregister_globals on? Youmay have heard: this is a bad idea.
Youmay 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.
![Page 40: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/40.jpg)
$_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() {...}...
}
![Page 41: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/41.jpg)
The database global object Very common in PHP code
Again, not horrible
Prevents testing
Prevents multiple databases
![Page 42: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/42.jpg)
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 inthat variable to the current scope.
Repeat.
![Page 43: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/43.jpg)
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;...
}
![Page 44: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/44.jpg)
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.
![Page 45: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/45.jpg)
A quick recapWhat are some specific problems in legacy PHP code?
Mixed PHP and HTML – confusion between controllerand view
Use of require statements instead of function calls
Unnecessary global variables causing dependencies
![Page 46: Dealing with Legacy PHP Applications](https://reader035.vdocument.in/reader035/viewer/2022081403/55516f38b4c905723b8b4b41/html5/thumbnails/46.jpg)
Further reading Working Effectively With Legacy Code, Michael Feathers
Refactoring, Martin Fowler