Friday, June 26, 2009

PHP rounding errors

What's 2.3 * 100 - 230?

You'd expect 0, right?

Well, not so much in PHP.

PHP seems to have troubles with floating point math, even in the simplest of circumstances.

Take the following snippet of code

$r = 2.3;
echo $r.PHP_EOL;
$r = $r * 100.0;
echo $r.PHP_EOL;
echo ($r - 230).PHP_EOL;

Instead of 0 for the last echo, you get -2.8421709430404E-14.

And setting precision via ini_set() isnt going to help either. By default, precision is set to 14. Setting it to 2 will result in -2.8E-14.

To avoid rounding errors in PHP, especially when doing financial calculations that should be limited to 2 decimal places, you should round() every floating point operation with a precision of 2. Most of the time, the calculations will appear normal, but on the off chance that the value to the left of the decimal point is 0, what appears on the right might not be what you expect.

Just for clarity, I'm running PHP 5.2.8, though will be doing an upgrade to 5.2.10 in the near future.

Friday, June 19, 2009

What MS Bullshit is this?

http://www.microsoft.com/windows/internet-explorer/get-the-facts/

A work colleague sent this as a Friday giggle, but as I read through the comparison and the "myth busting", I felt the nerd rage rising.

On a technicality, some of their claims are true. For example, you may get better security in Firefox by installing the NoScript addon, or having the WebShield addon that comes with an AVG installation. But in the main, I'd still take FF3 over IE8, because if a bit of functionality wasn't available, at least FF3 has a framework to allow developers to write that needed functionality as an addon.

I'd like to call "Crap" on their Privacy claim. Of course IE8 is going to win out if "InPrivate Browsing" and "InPrivate Filtering" is used as the comparison basis. However, you can get addons like NoScript for Firefox to achieve the same thing.

I'd like to call "Crap" on the Web Standards comparison of being a tie, but I'm not sure that I can. Given that MS have released no less that 3 versions of browser that have all been an incremental improvement on the previous one, but still a disappointment on web standards compliance, I'm a bit reluctant to delve into IE8 and its specific niggles. I'd be quite happy if IE8 does live up to its CSS 2.1 claims, but there's still IE7 and IE6 I have to support.

I'd like to call "Crap" on their Developer Tools comparison. Even though the Firebug and Web Developer addons are not natively installed with Firefox, they're still head and shoulders above what MS has taken so long to bring to the table in IE8. Though I am glad IE8 has provided the tools that it has, because debugging CSS in IE7 and IE6 is a shit.

And further more, I actually like the separately downloaded tools of Firefox and Web Developer, since there seems to be a bit more active development on them. Should there be bugs in IE8, whether it be in the dev tools, or the CSS compliance, unless they're actually causing the program to crash, you'll probably have to wait until IE9 for them to get fixed.

I'm calling crap on the Compatibility claim. They only get to do that because of all the hacking you had to do to get your site to display the way you wanted it to in IE7.

I will give them the claim of Manageability, with respect to setting group policy in Active Directory. But that's about it. Considering that FF runs on more platforms than IE8, its a slim win.

The Myths.. well, they're mostly a crock too.

The speed claim is moot. Given the grunt you need to actually run a MS OS that will run IE8, and do all your other stuff, speed better not be a problem. For myself, I use FF for my development work, Chrome for my email, blogging, iGoogle and searching, and IE8 for debugging some website that doesn't display nice even though its working perfectly fine under FF, Chrome and Safari.

I'm going to throw security in the same boat. It should be secure. However, I'd stick with Chrome and FF over IE. Especially when that IE7 bug was there, that was allowing hackers to steal WoW usernames and passwords (because they sold more on the black market than credit card details).

Firefox is definitely the more adaptable browser. Their claim is that there are 1700 addons in the MS Add-on Gallery. But when I do a display all on their website, I'm getting 44 pages of results, with 12 addons to a page, and 2 addons for the last page. Thats only 518 addons. Quite a short fall from the claimed 1700.

And besides that, I dont need 1700 addons to get the job done. I only have 19 Firefox addons, and not all of those are necessary. In fact, here's a list:

Adblock Plus
English (Australian) Dictionary
Firebug
GMarks (not really used any more)
Google Gears
Google Toolbar for Firefox (not really used any more)
Java Quick Starter
Jiffy (Firebug plugin for Javascript timer measurements)
Live HTTP Headers
Microsoft .NET Framework Assistant
Net Usage Item (so I can see my home DSL usage)
NoScript
Personas for Firefox
Total Validator
Unit States English Dictionary
URL Flipper (for web based came called Imperial Galaxy)
Web Developer
Xdebug Helper (PHP debug and profile tool)
YSlow (web page performance tool)

And my favourite one is IE not playing well with Web standards. Time will tell, but I think they should have qualified that statement with IE8 not playing well with Web standards as the myth. I'm not sure if its a problem yet, but IE6 and IE7 definitely dont play well.

I've always been of the mind that if software is broke, then fix it and preferably, in that major release. MS really should have fixed their box model rubbish in IE6. Failing that, they should have fixed it in IE7. But they didnt. They should have fixed their CSS 2.0 compliance in IE7 as well, but they didnt.

Here's a little test. When looking a comparison of FF3 release dates and IE8 release dates, I found this link that highlights a few issues with CSS 2.1 compliance in IE8. Perhaps come back in 3 months, then 6 months, then 12 months and see if your latest installation of IE8 has these issues resolved. I bet they're not. And if they are, let me know :)

The nerd rage is fading now. IE8 is an improvement on IE7, but its still not a patch on FF3. I'd like MS to put their money where their collective mouths are... if IE8 is so great, then put an end of life on IE7 and IE6 so I dont have to waste time supporting the bloody things.


Wednesday, June 10, 2009

Blog Template Change

I've changed template for the blog.

Something with a bit more sideways stretch, so any code blocks can be viewed nicely.

Integrating CodeIgniter with Legacy PHP applications (Part III)

This is Part III of a blog covering how I've integrated CI into a legacy PHP application.  Part I is an introduction and Part II covers the initial replacing session creating while logging in to the application.

At the end of the last post, I was able to log into the application, and get to the initial screen, and it was making use of CIs Session class.  And I thought I was home free.

So the next thing I tried to do was use the legacy applications Search functionality to find a record.  And things did not go to plan.

As mentioned before, the application was not coded nicely, and made use of register_globals = on.  CI takes specific steps to prevent globals being used in this fashion.  Oh noes, but the application, as it stands, needs them.  It's way too much work to go through the entire application to detect all the globals and fix them.  So what was I to do?

My first step was to create a subclass of the Input class, since that is where the filtering gets done.  So I created a copy of trunk/src/legacy/libraries/Input.php and put it in trunk/src/legacy/application/libraries/MY_Input.php.  I changed it to make the class and the constructor be called My_Input (which extends CI_Input).  I kept  the contents of the constructor, and commented out the foreach() in _santize_globals() to prevent the register_globals = off processing.  All the other functions were removed, since they could be used from CI_Input.

Now, that kind of did the trick, but it wasnt what I wanted in the long run.  With newly created controllers for use in the application, I still wanted globals to be removed.  And in the My_Controller, I still wanted the Router to be initialised.

Since I only wanted My_Controller and My_Input to only ever be called for calls from the legacy application code, I decided to copy the trunk/src/legacy/application directory and call it trunk/src/legacy/legacy.  In the trunk/src/legacy/config.php, I changed the subclass_prefix variable to be 'Legacy_'.  I altered the $application_folder variable in ci_index.php to be "legacy".  I also changed the subclassed Controller and Input, now in trunk/src/legacy/legacy/libraries to be Legacy_Controller and Legacy_Input.  I could then delete the subclassed versions from trunk/src/legacy/application/libraries.  I then reverted the application/config.php to not allow querystrings.  This would mean that any new controllers would behave the way CI controllers were meant to behave, by not having querystrings, and legacy application code could still include CI sessions and use registered globals.

And that's basically it!

There's only one more item left to solve, and thats the base_url config item in config.php.

Where I work, we all run our own development environments of the same project on our local machines.  We all point to the same database for an environment, but we have our own code base and web servers.  When we're done with changing some code, it gets checked into SVN and anyone else can do an update to get those changes for that branch.  

Because we all run our own webservers, the base_url is not the same.  We all have DNS redirects to our local machines and use vhosts in Apache to have multiple environments and projects running on the same machine.  For example, the normal development URL I'd use for this product would be http://legacy-trunk.reubenh/ for the trunk release.  The current bugfix release would be found at http://legacy-bugfix.reubenh/, and the branch that I took to do this CI testing would be found at http://legacy-ci.reubenh/.  

If one of my colleagues, Corey, were to also be doing bugfixes, the URL for his bugfix release would be http://legacy-bugfix.corey/.  So hardcoding the base_url into the code base doesnt work too well for us.

I'd really like it if CI could support multiple configs per environment, but what I'll probably end up doing to putting some code in the config to try and recognise which machine it has been deployed to, and either hardcode the base_url to the production one, or just use whatever is available in $_SERVER.

I'll probably try and do a similar thing with database.php, as well, so we can deliver the code base, and not have to be concerned development database parameters being used instead of production ones.

One more thing to note.  Because we use register_globals in our legacy application, we get alot of notices about variables being used without initialisation.  This can actually stop CI in its tracks if the error_reporting is set too high.  By default, its E_ALL.  You may want to update ci_index.php to be E_ALL && ~E_NOTICE, to suppress the error handler from going nuts.  Even when you have this enabled, you may still get alot of notices in the error log, they just wont be prevent processing.  I havent bothered to figure that one our yet.  I thought the log_threshold should maybe have taken care of it, but that seems to be more about calls to log_message(), than actual PHP error reporting.

Anyway, I hope this has been instructional.  Good luck with integrating CI into your legacy application, warts and all.

(Update:  In a *nix environment, file names are case sensitive, so make sure the subclasses for Controller and Input are called Legacy_Controller.php and Legacy_Input.php, not legacy_controller.php or legacy_input.php.)

Code Blogs in Blogger

What a bloody trial it is to get code blocks to display nice in the Blogger editor.

Maybe I should just setup the email thing, and email my posts in, rather than trying to use the Blogger Editor.

Integrating CodeIgniter with Legacy PHP applications (Part II)

This is Part II of a blog covering how I've integrated CI into a legacy PHP application. Have a read of Part I for an introduction.

Firstly, a bit of background about the legacy application. To protect the client, and myself, I'm going to call it Legacy.

In SVN, the code has the following general structure:

trunk/src/legacy. This directory is where all the source code lives. There are about 30 directories under it, and a bucket load more of programs, either in those directories, or at the root level. Each sub directory has tried to split out a logical part of the system, but it hasnt worked so well. There are two items of note. One is config.inc.php, which contains the global configuration information. Its mostly about database connection information, and a few other tidbits of information relevant to the application. The other is that when deployed, the application will be accessed as https://legacy.host.com/legacy.
trunk/config. This directory contains different versions of the global configuration file. There's one for production, and there's one for development.
trunk/db. This directory contains database information, like what the latest schema looks like.

Some other things worthy of note.

The PHP ini setting register_globals is forced on. The code base was already quite large when I got it, and it was going to be too risky to go around and find all those globals and change them to $_GET or $_POST accesses. One might argue its just as risky to leave them there, but given that the application is intranet based, I figured the risk was low.

The application also runs under PHP5. Probably one of the few things that works in our favour, with regard to support for OOP.

So how did I integrate CI into the mess?

First thing was to download CI and unzip it. For reference, I used CodeIgniter 1.7.1. I put the contents of the system directory under the trunk/src/legacy directory. Luckily, I didnt have any directory name clashes.

I then set up trunk/src/index.php to rename the $system_folder to "legacy", then edited trunk/src/legacy/application/config/config.php and database.php for the base_url and database connection information. I also configured sessions to be stored in the database, as per CI Session instructions, and added a trunk/src/.htaccess file so I could skip having index.php in the URL.

Having

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]

in the .htaccess file would allow me to run the legacy application, and any new CI controllers side by side. Make sure you change the index_page in config.php to "".

Just to make sure everything was ok, I made sure I could log into the application and perform a few basic tasks. Yippee! All working.

The next step was to follow some basic guide lines from the article Calling CI models from outside script to create some stubs to allow integration with the application.

First file was a trunk/src/ci_index.php, to match index.php in CI, and config_constants.php from the article.


<?php/*
* Copy of index.php for use with legacy code
*/
/*
|---------------------------------------------------------------
| PHP ERROR REPORTING LEVEL
|---------------------------------------------------------------
|
| By default CI runs with error reporting set to ALL.  For security
| reasons you are encouraged to change this when your site goes live.
| For more info visit:  http://www.php.net/error_reporting
|
*/
error_reporting(E_ALL && ~E_NOTICE);

/*
|---------------------------------------------------------------
| SYSTEM FOLDER NAME
|---------------------------------------------------------------
|
| This variable must contain the name of your "system" folder.
| Include the path if the folder is not in the same  directory
| as this file.
|
| NO TRAILING SLASH!
|
*/
  $system_folder = "legacy";

/*
|---------------------------------------------------------------
| APPLICATION FOLDER NAME
|---------------------------------------------------------------
|
| If you want this front controller to use a different "application"
| folder then the default one you can set its name here. The folder
| can also be renamed or relocated anywhere on your server.
| For more info please see the user guide:
| http://codeigniter.com/user_guide/general/managing_apps.html
|
|
| NO TRAILING SLASH!
|
*/ 
$application_folder = "application";

/*
|===============================================================
| END OF USER CONFIGURABLE SETTINGS
|===============================================================
*/


/*
|---------------------------------------------------------------
| SET THE SERVER PATH
|---------------------------------------------------------------
|
| Let's attempt to determine the full-server path to the "system"
| folder in order to reduce the possibility of path problems.
| Note: We only attempt this if the user hasn't specified a
| full server path.
|
*/
if (strpos($system_folder, '/') === FALSE)
{
if (function_exists('realpath') AND @realpath(dirname(__FILE__)) !== FALSE)
{
$system_folder = realpath(dirname(__FILE__)).'/'.$system_folder;
}
}
else
{
// Swap directory separators to Unix style for consistency
$system_folder = str_replace("\\", "/", $system_folder);
}

/*
|---------------------------------------------------------------
| DEFINE APPLICATION CONSTANTS
|---------------------------------------------------------------
|
| EXT  - The file extension.  Typically ".php"
| FCPATH - The full server path to THIS file
| SELF  - The name of THIS file (typically "index.php")
| BASEPATH - The full server path to the "system" folder
| APPPATH - The full server path to the "application" folder
|
*/
define('EXT', '.'.pathinfo(__FILE__, PATHINFO_EXTENSION));
define('FCPATH', __FILE__);
define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME));
define('BASEPATH', $system_folder.'/');

if (is_dir($application_folder))
{
define('APPPATH', $application_folder.'/');
}
else
{
if ($application_folder == '')
{
$application_folder = 'application';
}

define('APPPATH', BASEPATH.$application_folder.'/');
}


/* End of file index.php */
/* Location: ./index.php */
?>


The second file was a trunk/src/ci_open.php to match a part of codeigniter/CodeIgniter.php in CI, and ci_model_remote_open.php from the article.

<?php
/*
* Copy of system/codeigniter/CodeIgniter.php
* Beginning processing and load the base controller
*/

require_once('ci_index.php');

/*
* ------------------------------------------------------
*  Load the global functions
* ------------------------------------------------------
*/
require(BASEPATH.'codeigniter/Common'.EXT);

/*
* ------------------------------------------------------
*  Load the compatibility override functions
* ------------------------------------------------------
*/
require(BASEPATH.'codeigniter/Compat'.EXT);

/*
* ------------------------------------------------------
*  Load the framework constants
* ------------------------------------------------------
*/
require(APPPATH.'config/constants'.EXT);

/*
* ------------------------------------------------------
*  Define a custom error handler so we can log PHP errors
* ------------------------------------------------------
*/
set_error_handler('_exception_handler');
set_magic_quotes_runtime(0); // Kill magic quotes


/* $CFG =& load_class('Config'); */

if (floor(phpversion()) < 5)
{
load_class('Loader', FALSE);
require(BASEPATH.'codeigniter/Base4'.EXT);
}
else
{
require(BASEPATH.'codeigniter/Base5'.EXT);
}

// Load the base controller class
$CI = load_class('Controller');
?>


The third file was a trunk/src/ci_close.php to match the last part of codeigniter/CodeIgniter.php in CI, and ci_model_remote_close.php from the article.

<?php
/*
* Copy of system/codeigniter/CodeIgniter.php
* End processing
*
*/
/*
* ------------------------------------------------------
*  Close the DB connection if one exists
* ------------------------------------------------------
*/
if (class_exists('CI_DB') AND isset($CI->db))
{
$CI->db->close();
}

?>



My first task was to convert sessions to CI Sessions. The first program to change would be the login screen. Currently functionality was to clear any sessions that existed when visiting this screen. I replaced my ugly session clearing code in trunk/src/legacy/login/login.php with
require_once('../../ci_open.php');
$CI->load->library('session');
$CI->session->sess_destroy();
require_once('../../ci_close.php');

The next step was to change the page that handled the login post to use CI sessions once the user had been authenticated.

As I'd mentioned previously, the legacy application made use of register_globals, which is nasty. The login page used them as well, so I made a small change to load this from the post, but left the rest as close to the same as I could. I ended up with this...

<?php
ini_set('register_globals', 'on');
include_once('../config.inc.php');
require_once('../../ci_open.php');
$CI->load->library('session');

$userxname = $CI->input->post('userxname');
$passxword = $CI->input->post('passxword');
/* do user authentication with username and password
* if authentication is successful:
*/
$userdata = array('logged' => 'yes', 'User' => $row['userid'], ... );
$CI->session->set_userdata($userdata);
/* near the bottom */
require_once('../../ci_close.php');
?>


Now, luckily, we do all our session checking in one place (trunk/src/legacy/login/logincheck.php). And that modification went something like:

<?php
require_once('../ci_open.php');
$CI->load->library('session');
$found = $CI->session->userdata('logged');
if ($found != "yes") { /* redirect to login page */}
?>


Now, for this one, I've left the ci_close.php out, since it closed the database connection and prevented the legacy application from any more database accesses.

That all nearly worked.

After a successful login, the page would redirect to /legacy/legacy_main_screen.php?l=y. legacy_main_screen.php would use the quest string to work out what page to run, and where to position the menu structure. However, CI was getting in the way, and tried to remove the query string. So I had to enable the query string in config.php.

The next problem was that the Router would get initialised during the initialisation of the Controller, and would try to intepret the URL to work out what controller to forward the user to. I didnt want to forward control to a controller, I just wanted the controller to start and let processing continue.

So I subclassed the Controller with my own one that specifically did not initialise the Router.

To do this, I first made a note of the subclass_prefix in config.php. The value is "MY_".

Next, I made a copy of trunk/src/legacy/libraries/Controller.php and put it into trunk/src/legacy/application/libraries/my_controller.php, and changed a few things. The first was the class name and constructor name (MY_Controller). I made it extend the Controller, rather than CI_Base. The contents of the constructor remained the same, calling parent::CI_Base() and $this->_ci_initialize() rather than parent::Controller. And most important, I took a copy of _ci_initialize() and commented out the 'router' entry from the $classes array.

Awesome! I could now login into the application and see the first screen of the application. I thought I was home free. Not quite.

Stay tuned for Part III.

Integrating CodeIgniter with Legacy PHP applications (Part I)

One of the products that I do maintenance on, and occasionally do new development work on, at work, is a monster PHP thing.  It's web based, its procedural, if you're lucky, and its a bit of a mess.  It uses register_globals=on, and quite alot of the business logic, view and presentation is present all in the one program for any given particular screen.  Someone else wrote it originally, and we've never really recovered enough to turn it around and make it better.

We've not done any active development work on it for about 9 months now, but there are some changes coming.  My past few projects have been done using CakePHP.  I really like MVC, and I'd like to drag this monolithic monster kicking and screaming into the realm of modern programming practices.  At the very least, it will keep the project work fresh, and make it something interesting to work on, as well as being maintainable.

As I said, it's a monster, and a full conversion to something easier to maintain is not something that the client is going to dish out for.  So converting to CakePHP is off the table.  What I need is a light weight MVC framework for PHP that is flexible and be worked into the legacy application one bit at a time.  The framework also needs to be in active development, and supported.

So I went for a wander on Google and found CodeIgniter (CI).  It would appear to be a light weight MVC framework for PHP, where you can choose to drink as much or as little of the kool aid as you want.  It seems to have an active development team, and an active community of folks using it.

I didnt go for CakePHP in selection because its just too heavy.  Its an all or nothing framework, and would take too much hacking around to make it work in the environment.

So I took a cut of the project in SVN so I could integrate CI.

The next thing I did was have a look around to see what I could do to integrate legacy PHP applications with CI.  By legacy, I mean old-ish code, nasty stuff thats all procedural, uses register_globals and is a general mess.  I eventually came across this article called Calling CI models from outside script.  It's pretty good article with some good pointers to other articles discussing how to make use of CI models without making your whole program CI.  However, wasnt quite what I wanted.

What I wanted was an introduction on how to use CI libraries in regular code.  My specific goal, and test case, was to replace the session control in the legacy application with the session control available in CI.  I figured, once I have that sorted, the rest should fall into place.

Part II will cover a little about the layout of the legacy application, and how I integrated CI session control into it.