PHP Frameworks? Just roll your own! – PART 1

There are quite a few php frameworks out there. Some huge, some small; Some useful, some not. I often hear developers, even myself, complain about frameworks in php not having or not doing something the way they/we want it too. So my solution? Roll your own framework! This series of articles is going to give you a brief and quick intro on how to do just that. We are going to cover url routing, basic database connection, basic templating and basic rewrite rules for Apache. What we will not cover is ORM development, advanced routing and installation of LAMP (Linux, Apache, MySQL, PHP). Also error checking will be next to none as this is just an example.

Mini-series Overview

  • Week 1: URL Routing and Directory Setup
  • Week 2: Templating
  • Week 3: Database Interaction

To get started let’s setup our directory structure. We are going to call our framework “Peanut” (first thing that popped in my head).
So for our structure let’s make it really basic:

MyWebApp/
  '---actions/
    '---helloworld/
      '--- actions.php
      '--- models.php
      '--- templates/
        '--- yourTemplateHere.php
'---system/
  '--- template.php
  '--- database.php
  '--- dispatch.php
'---www/
  '--- js/
  '--- css/
  '--- images/
  '--- index.php

Let’s break this structure down, it’s simple.

MyWebApp: the root of your web app that will be using our “Peanut” framework

  • actions: this will contain all the pages our web app will use.
    • helloworld: is the name of our page action or ’section’ of our site
      • actions.php: is where we will code our page actions
      • models.php: where we will write all our database interaction
      • templates: this is the folder we will store all our views/templates
  • system: this is where we will store all our framework specific classes/source/libraries
    • template.php: This will handle all of our template rendering.
    • database.php: This is for our database connection and access
    • dispatch.php: This will parse our url and call the correct page action.
  • www: this is our root web directory, it is also where we will pull everything together.
    • js: the directory where you store your javascript
    • css: put your style sheets in here
    • images: guess!
    • index.php: This is the file that will initiate and pull together the pieces of Peanut.

Now that we have a directory structure, let’s setup a virtual host in Apache to accommodate for Peanut. Here is an example of a virtual host you can use (here is what I do on my Mac):

NameVirtualHost *:80

<VirtualHost *:80>
        ServerName      mywebapp.local
        DocumentRoot    /Users/wcope/Sites/mywebapp/www

        AcceptPathInfo On

        RewriteEngine On
        RewriteRule     /*\.(css|js|gif|png|jpe?g)$ - [NC,L]
        RewriteRule "^(.*)$"    "/index.php?_url=$1" [QSA,L]

        <Directory "/Users/wcope/Sites/mywebapp/www">
                AllowOverride All
                Order allow,deny
                Allow from all
        </Directory>

</VirtualHost>

And In my /etc/hosts file I add this line:

127.0.0.1	mywebapp.local

That will allow ‘mywebapp.local’ to resolve to my localhost where Apache will pick it up and kick it off to the virtual host we created. Inside that virtual host we have some rewrite rules that will redirect all request made (except css, js, gif, png, jpeg/jpg) to index.php?_url=$1. To break that down in the simplest way, basically Apache takes: mywebapp.local/some/page and redirects it to index.php?_url=/some/page (but you never see the latter). Also notice that we made the root directory of our virtual host to our www folder where index.php is.

Ok, so now we have our directory structure, virtual host and rewrite rules all setup. If you have any detailed questions about Apache, mod_rewrite, virtual hosts, host files, etc. Sorry they will not be covered here due to the fact that there is tons on it out there… so hit up your local google.com :).

So now let’s get to some code.

So first lets write our dispatch code since that will be what is responsible for routing all our request to the proper pages.

Open up index.php in your favorite text editor and let’s slap some code in it:

In this file is a good place to put constant variables, include any files and call our dispatch (once we write it). But for right now let’s do a little test, and also write a little helper function I’m going to use to print stuff to the screen that can help us see what’s going on.

<?php
// First let's define our apps root directory
define("PEANUT_ROOT_DIR", realpath(dirname(__FILE__) . '/../') . '/');

// now a pretty little dump function (or if you have xdebug you can just use var_dump)
function dump($item, $die=true)
{
	$printString = '<pre>' . print_r($item, true) . '</pre>';
	if($die)
		die($printString);
	else
		echo $printString;
}

// Now let's just dump our URL to make sure that all paths lead here
dump($_GET);
?>

Now if we bring up our url: http://mywebapp.local/some/url/ we should see something like:

Array
(
    [_url] => /some/url/
)

So now we can see that our rewrite is working and we are ready to start writing our dispatch function, so let’s change our index.php code a little.

<?php
// First let's define our apps root directory
define("PEANUT_ROOT_DIR", realpath(dirname(__FILE__) . '/../') . '/');

// now a pretty little dump function (or if you have xdebug you can just use var_dump)
function dump($item, $die=true)
{
	$printString = '<pre>' . print_r($item, true) . '</pre>';
	if($die)
		die($printString);
	else
		echo $printString;
}

// Let's comment this out
// dump($_GET);
// and add this:

// first we are going to drop our dispatch file in here
require(PEANUT_ROOT_DIR . 'system/dispatch.php');

// then call our dispatch function to handle the url
// Notice Im not passing in $_GET or $_GET['_url'], being that
// $_GET is a global system variable, I don't have to worry about it
// here.

dispatch();
?>

So now let’s save that and crack open our system/dispatch.php file and and add some code to it to handle our new found url pattern. As I said before, this will only be a simple router with no config or predefined url pattern to page action mapping.

So, inside dispatch.php, let’s add:

<?php
// Here we are going to handle our url request

function dispatch()
{
	// First we are going to grab our url path and trim the / of the
	// left and the right
	$url = trim($_GET['_url'], '/');

	// Now we are going to split the url on the / which
	// will give us an array with 2 indexes.
	$url = explode('/', $url);

	// Let's just print out our array to get a visual of
	// what we are working with.
	dump($url);
}

?>

Now if you refresh your browser at our current url (http://mywebapp.local/some/url/) you should see:

Array
(
    [0] => some
    [1] => url
)

Great, now we have 2 clean items to work with. So let’s tell dispatch that the item at index 0 will be our folder and item at 1 will be the page action inside our actions.php file. Since our directory structure is setup to use actions/helloworld/actions.php, let’s setup dispatch to include the file with our action in it in the directory that was requested in the url. So change dispatch.php to look like:

// Let's handle that url
function dispatch()
{
	// First we are going to grab the url path and trim the / off
	// the left and the right.
	$url = trim($_GET['_url'], '/');

	// We changed this from before to make it easier to read and also so we can
	// work with easier variables.  using the list (http://php.net/list) function
	// will map the array indexes to the respected order of the var names inside
	// the list function call (better explanation at php.net/lists).
	list($directory, $action) = explode('/', $url);

	// Now we drop in our respected actions file from the directory in the url
	require(PEANUT_ROOT_DIR . 'actions/' . $directory . '/actions.php');

	// And call the action from inside our actions.php file
	// and since we want to end our request with the return value of our requested method
	// let's just die on the return.
	die($action());
}

Now we need to add our action inside our actions.php file, so open it up and drop the following
in it:

<?php

// We simply define the function (the second item in our request url)
// making sure it is the same name as the one in the url.
function mypage()
{
	// For now we are simply going to return "Hello World" string
	return "Hello World";
}

?>

So let’s give this a shot. Open up your web browser and point it to: http://mywebapp.local/helloworld/mypage.You should see a pretty little “Hello World” in the browser. If you do, congrats! You now have routing in your new Peanut framework.

Next week we will see how to add templating into our framework.

Since the days of the Commodore 128, Wess has become an expert in C, C++, Java, Javascript, Python, and PHP and he also has extensive knowledge of Objective-C/Cocoa, Ruby, and Erlang. His blog can be found at www.wattz.net.

 

If you liked this article, please help spread the news on the following sites:

  • Bump It
  • Blend It
  • Bookmark on Delicious
  • Stumble It
  • Float This
  • Reddit This
  • Share on FriendFeed
  • Clip to Evernote