This document discusses strategies for dealing with legacy PHP code, including separating controllers and views, removing dependencies on global variables, refactoring procedural code to be object-oriented, and untangling nested require statements. Specific problems in legacy PHP code are said to include mixing of PHP and HTML, overuse of requires instead of method calls, and excessive use of global variables. The document provides examples of refactoring code to address these issues.
Nelamangala Call Girls: 🍓 7737669865 🍓 High Profile Model Escorts | Bangalore...
Dealing With Legacy PHP Applications
1. Dealing with Legacy PHP
Applications
Prepared by Clinton R. Nixon, Viget Labs
2007 July
2. 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
3. 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
4. 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
5. 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
6. 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
7. 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
8. 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
9. 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
10. 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
11. 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
12. 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
18. 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
19. 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
21. 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
22. 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
23. 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
25. 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
26. 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
27. 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
28. 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
29. 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
30. 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
31. 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
32. 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
33. 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
34. 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
35. 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
36. 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
37. 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
38. 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
39. 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
40. $_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
41. The database global object
Very common in PHP code
Again, not horrible
Prevents testing
Prevents multiple databases
Dealing with Legacy PHP
Applications
2007 Jul
42. 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
43. 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
44. 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
45. 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
46. Further reading
Working Effectively With Legacy Code,
Michael Feathers
Refactoring, Martin Fowler
Dealing with Legacy PHP
Applications
2007 Jul
47. Questions?
clinton@viget.com
Slides available at http://www.slideshare.net/viget
and http://clintonrnixon.net.
Dealing with Legacy PHP
Applications
2007 Jul