Wednesday, June 10, 2009

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.)

No comments: