PGF: The Ease Of Rails In PHP
Posted at December 6, 2005 12:30 PM - Category: Coding
Ruby on Rails is clearly the hottest thing since sliced bread on the web right now. Many people are moving over to the framework as a way to speed up their development and get on this whole Web 2.0 bandwagon. I'll leave the argument that Rails != Web 2.0 for another post, but it should be noted that Rails brings to the table some pretty big coding efficiencies beyond just simply reducing the amount of code. So, what if we applied some of those techniques to PHP instead? I've been using this approach to application coding in my own applications lately, and it's offered some major decreases in code and increases in programming efficiency. I call this framework of coding the PGF: PHP Generic Framework.
Objects are the key to this system. PHP has had them since version 4 in a basic capacity. Rails makes them a ubiquitous part of the system, since everything is an object in Ruby. So, we need to make objects a ubiquitous part of our own PHP framework. To do this, we need to eliminate the use of functions in our code wherever possible. Consider this bit of code:
function show_products() {
$res = db_query( "SELECT * FROM products" );
$products = array();
while( $row = db_fetch_array( $res ) ) {
$products[] = $row;
}
...
As you can see, there's a lot of stuff written in there, and I didn't even get to the juicy part of printing it out. I know it's in bad form to reveal the ending right off the bat, but this is what we want to get to:
class Products extends Module {
function show() {
$this->products = $this->db->get_all( "products" );
}
}
Simple and easy-to-understand. The products come from the contents of the "products" table in the database.
Code this simple normally needs a lot of framework code to back it up. However, there isn't any need to write up 15000 lines of code like Rails. Besides, you don't need all that functionality anyways. What I'm going to show you is a guideline for building your own framework. You can use this as a basis and build up whatever components you need. You can also add to it over time as you build new applications and want to improve your central functionality. Need a database abstraction layer for PostgreSQL? Throw that in. Want an FTP access object? Work that into the mix. What I'll show you here is extensible for whatever your needs are, since it only provides a base way of doing things and minimal code on it's own. It's called a generic framework for a reason! Anyhoo, on to the show...
The first big thing with this framework is making a lot of normally explicit things implicit. The biggest of these is printing output. You noticed in my earlier example code that there was nothing in there to do any printing. That will be handled automatically by our framework based on the name of the module and function that is called. We also didn't have to substitute any variables into the output. We just set a property of the module with our data and let the template figure it out. Lastly, there was the functionality of the framework embedded in the object itself. This can be done with globals, but it saves us a line of code when we don't have to import the global variables into every method. This is also where we get extensible, because you'll be able to add $this->whatever to expose new functionality to the module.
Core Walkthrough
So, to start, we're going to define a simple filesystem layout that consists of the following:
- index.php - Everything runs through this script, although it is only two lines of actual code. The heavy lifting is defined elsewhere.
- Libs/System.php - Our main application object. This is where things begin and everything is glued together
- Libs/Display.php - Our display library that will handle all printing and templating.
- Libs/Module.php - The base class for all modules. Includes some important functionality.
- Modules/ - Our directory of code modules that will get executed in the application.
- Templates/ - Our directory of output templates to correspond with the methods in a module.
index.php is the simplest of the files. It's simply going to create an App object and run it. This App object is in System.php and will "boot" the application when we load a page. Basically, that means it's just setting everything up before passing execution on to the module. It looks like this:
$App = new App(); $App->boot();
Moving on to System.php (which you'll find in the download linked at the bottom), it does 4 important things. 1) It sets up our environment (sysstartup()), it loads up our framework libraries (libstartup()), it figures out what module we want to run, and it executes that module. The separation into 4 functions is entirely optional, but let's me separate out the logical function of each in my head a little bit easier.
While I didn't include it in the copy of the code given here, I am including friendly URLs in my own applications. These are Apache-specific, since they rely on mod_rewrite to work, but they're simple to add. So, rather than index.php?A=Products&M=show, you would have /Products/show as the URL. This is taken from Rails and is an optional component. It just pretty's up the presentation a bit to the end user.
Now, let's get into the fun part: the Module object. This takes in our input and figures out what method we're trying to run. First thing we need to do is exclude any methods from the parent object. The actual run() method from the Module class can't be used (so, ?A=Products&M=run wouldn't work), nor can the constructors of the object. Things would get weird in that case, so we don't allow it. Once that's done, we can try to run the method that was requested or just fall back on a default one (so, we can do just ?A=Products). Lastly, we send the properties we set out to the screen through our template.
The last component is the displaying. To make things as simple as possible, we're going to rely on the Smarty template library. It's the only one bit of code that we don't write ourselves. I honestly wish Smarty was a standard component of PHP, because what good is a web-focused language without a built-in templating engine? (although, you can argue PHP embedded in HTML is it's only templating engine) Our display object is going to extend Smarty to add the little bit of extra functionality we need. This is where you can define global template variables (such as the username of the currently logged in user, as an example) and also a global wrapper template to keep additional markup to a minimum. You can also handle PHP errors gracefully by setting your own error handler and having them put into the template in a reliable and controllable manner.
So, to review, we do the following:
- Load up the application and boot it in index.php
- Set up the system environment, libraries, and run the appropriate module in System.php
- Figure out right method of the module, run it, and display the output in Module.php.
Pretty simple.
Writing Modules
With this framework in place, we're ready to start coding up our applications. Not much functionality is provided, just what's built into PHP at this point. You'll need two files to start an object. First, create the module file Whatever.php with a Whatever object that extends Module. Then create a method within that object to set up the output however you need. The second file is put into the Templates directory and will create the template for output. It should be named modulename.methodname.tpl (lowercase, since UNIX has a case-sensitive filesystem) and uses standard Smarty syntax. For your properties you can use stuff like {$foo} where it's set in the module as $this->foo. You can also use the standard Smarty mojo to do things like this:
{foreach from=$things item="thing"}
{$thing.name}
{$thing.size}
{$thing.price}
{/foreach}
What's great is Smarty is extensible by itself, so you can build up your own template functions and extensions if you find it would save you time. For instance, if you have a standard way of showing lists that involves a really nice javascript system with AJAX and whatnot, you can save yourself from having to type up all the HTML every time by adding a Smarty template extension for that, along the lines of {cooltable data="$things"}.
Handling Input Functions
So, one of the big things that you might be wondering about is how you do input functions, aka those that save stuff to a database, remove an entry, save a file, or something similar. So far, this has all been about creating output functions that just display stuff from a database or something else. But the fun part is getting that data into there.
With these functions, there are two cases: The input was correct and successful, or the input was malformed and failed. In the first case, it's quite simple: We just do the actual method and then redirect back to the area of the application we want to go do. Since the method is the second to last thing we do before the output, we can safely exit to skip the display step and just redirect the user's browser to another page. I have this in there as a jump_ship() method in my own applications, but you can sufficed with just a header() and exit() on your own.
When input is malformed, it gets a little more interesting. In that case, you normally want to redirect back to the method we came from and display an error message. One applicable way of handling this is to just redirect back to the function with an &Error=24 attached on the end of the URL to prompt the correct error code. However, that mangles our logical separate of code in this case. The calling function has to have code in it to handle all the error codes or figure out the right one, when the error is actually the result of input function. So, the error happens in one place, but shows up in another. If this becomes a complex application, it could get hard to track where the errors are coming from. Plus, when you redirect, you lose all the state information for the application, which can make debugging hard.
Instead, we're going to internally execute the calling method. So, this is how it should look in your module:
class BlogEntry extends Module {
function edit() {
$this->method = 'edit';
$this->categories = $this->db->get_all( "categories" );
}
function save() {
if( empty( $this->input['title'] ) ) {
$this->error = "You must enter a title";
$this->edit();
return;
}
// Do the rest of the saving stuff here
}
}
So, if we have our form send to the save method and we submit without a title, it will call back to the edit() method and return afterwards, so that it doesn't attempt to save the erroneous data. You'll notice I also had $this->method = 'edit'; there in the edit() method. Since the way we detect what template to output is by the method that's called, it would attempt to show a save template in this case, where there wouldn't be one. Instead, we want to use the edit template and put the error message into it. We set this value so when we exit back out and the parent Module class continues running, it will do the display command with the new method name, instead of the old one. This sort of thing lets us explicitly define what template to use, in case we want to use one template among a few different methods, for example.
Extending The Framework
The last thing we need to be able to do is add on our own functionality to this framework. The biggest things that should get added are database functionality and input cleaning. Both are key things in running most applications, so adding them shouldn't be a long process. In fact, it's just a few lines of code to do.
I'll leave the creation of the actual library up to you, but adding one in is fairly simple. You can create an object and put in in a PHP file in the Libs directory. So, for the databases, you may create a DB.php file with the DB object in it. In our framework, we only need to add it in two places in System.php. First is in the libstartup() function where we require() or include() in the object and instantiate it. You can do this with globals or a propery of the $App object. Either is equivalent, as we're only trying to get that object over to the doaction() method. In that location, we're going to create a reference to the object in the module, so that we can avoid globals as mentioned before. So, $module->db =& $db; would be needed for the database example.
Adding objects like this, we can build in whatever we need. If you want FTP functionality, create the FTP object, include it, and reference it into the module. It's just a $this->ftp away from you. You can also use just functions, if you'd like, but that sort of goes against the ideals of the framework. On the other hand, I don't really want to force an ideology on you with this system, so do whatever you're most comfortable with.
That's All There Is To It
And that's all there is. Seriously.
The point is to form a framework for frameworks. Different applications require different types of functionality, so there shouldn't be one monolithic system to handle all types of code. That just fattens up your application and increases complexity. Instead, think slim and only add what you need. Writing a simple application doesn't have to mean using a complex and bloated system. Similarly, writing a complex application doesn't have to mean limiting yourself to a simplistic or rigid framework.
Using the PGF, you should be able to reduce your code footprint and reduce your organizational complexity. Only code relating to the functionality of your application is in the module. There's no mixing in of setup code or repeated snippets that become an organization nightmare when you change an interface. In fact, the only leakage of framework code into there is the "extends Module". Other than that, it's just another object in the world.
Give it a try and let me know what you think. I hope it ends up saving you some time in your own development. If you think of any ideas to improve things, feel free to share. I'd love to hear it and write up a follow up post to see if we can make this as solid as possible.
Download: PGF.tgz








