SuperMike
May 28th, 2009, 11:38 PM
Lately I've been experimenting with my very own MVC framework in PHP. I thought I'd share the technique. One major goal of doing your own custom framework of course is to fend off criticism about building yet again another framework. So, if you build one, the point about what you do inside it should be completely obvious. A newbie dev who inherits your code should be able to jump in and get going fairly quickly.
If you're not familiar with what MVC is, here it is:
http://en.wikipedia.org/wiki/Model-view-controller
Basically you are separating your PHP from your PHP + SQL from your XHTML/CSS, and you're providing pretty URLs, and often are loading some common classes you'll often use on projects. Another key point about MVC is the whole way of doing Gang of Four or EAI refactoring under "models", but that's another topic for another day. You can read up on the topic by googling on refactoring.
And another thing that threw me for a loop was the naming. Just think this:
front controller = the central point of access to the website, no matter which URL is typed, and it routes to the page controllers
page controllers = kind of like a skeleton page that loads your models with data, gets data off the models, and sends it to the page templates (views)
models = this was hard for me to grasp why it's called models, but basically it's where the meat of all your code should go so that you have code reuse instead of cut and paste. Just forget that it's called models. Think of it just simply called "classes", even though it's not named that.
views = just replace the notion of "page templates" with "views".
Usually these things start with what's called a front controller. It's basically the central hub where all URL requests come into your website. You might call it index.php, but it's more than just that. You first make an Apache .htaccess file that has something like this in it:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [QSA]
What this does is basically take all 404s, where someone has typed in any URL at all for files that might not exist on the website, and route them all through index.php before it actually does a 404. We call this the pretty url front controller technique. Note that this technique does not cause Google's spider index system to register the site as one great big 404. If you had had use the Apache2 ErrorDocument 404 directive technique for this, then you most certainly would have -- so I don't recommend that. Use the ErrorDocument directive appropriately -- for handling anything that you send a 404 header on from your PHP code.
Then, in index.php, you'll want to load a bootstrap.php that loads your framework, and then parse the incoming URL, and then route out the request to what's called a page controller. But first, let's discuss the obvious directory structure:
.htaccess
404.html
robots.txt
site.conf
index.php
controllers/
controllers/Home/Home.php
models/
views/
views/Home/vHome.php
system/
system/bootstrap.php
system/static_Controller.php
system/static_View.php
system/static_Model.php
system/static_Settings.php
I actually think this is pretty obvious in how this works if you actually know how to use an MVC.
You see, with this, you get your page controllers under "controllers", your models under "models", your view templates under "views", and the framework classes under "system". This makes it pretty obvious to recognize and use.
As well, you get static classes that let you do controller work with static_Controller.php, model work with static_Model.php, and view work with static_View.php.
And the Home/Home.php page controller would be the controller that handles when someone hits your site like:
http://mysite.com/
And the Home/vHome.php view template would be the view you can call from your page controller Home/Home.php so that you show your XHTML. And the reason I used the vHome instead of Home? Yeah -- it's so when editing files you don't get the text editor (IDE) tabs confused and you know therefore that this is a view template file.
The job of bootstrap.php should be to load your common static framework classes and other common classes (non-static) out of the "system" folder -- thereby making all your pages already have those classes in memory. You can also put some instantiation code at the bottom of these common classes so that you don't need to instantiate them.
Okay, now going back to the index.php, which is your front controller, take a cue from the CodeIgniter. In CI, you can create a folder structure like this:
controllers/EmployerSettings/Update.php
and automatically call it without having to edit your front controller (index.php) like so:
http://mysite.com/employer-settings/update/
And, as a bonus, you can do:
http://mysite.com/employer-settings/update/2323023/ABCDE/
...and the Update.php file can receive the stuff after /update/ by parsing for this and using it as necessary.
Pretty slick, huh? Well, what makes that possible is that you parse the incoming URL, apply some rules on the string, prevent XSS and do other security controls, and then use require_once() to load that file. So, the general format for URLs would be:
http://mysite.com/{Domain Object}/{Task}/{whatever}/{whatever}
So that gives you a front controller routing to page controllers. You then use the page controllers like the glue to feed stuff into and out of the models, and then into the selected view, before telling the view template to display.
Models will be where the meat of your code is, but a lot of people just make fat page controllers and come back around to making the models do the heavy lifting after they get the project into a phase 2. It's your call. Models really do make your life a whole lot easier, though, when you get going on a huge project. They help you avoid the cut and paste and move into the code reuse. So what you typically do is divide the website project up into what are the major features, and these are called Domain Objects. Then, you have minor features, and those are Sub Objects to the Domain Objects. You can represent that either in one folder or two levels of folders under models. Last, you have the task on a given object, and so that's your actual file. For instance:
models/Settings/Employer/Update.php
...and inside Update.php you might have a class named like so:
class Settings_Employer_Update {
}
So, to load that model, you might create the right system/static_Model.php class such that you can do this:
Model::loadModel('Settings/Employer/Update');
Now let's look at the views. You can create XHTML and stick it in your views by creating a folder and file structure under "views" like so:
views/Registration/vRegisterForm.php
views/Registration/vRegisterResult.php
And inside those files just start with this at the top...
<?php ?>
and follow with your XHTML. Okay, fine, but how do you get data into those files? Well, a variety of ways, and this is where your system/static_View.php class comes in. You really only have these choices no matter what template engine you use, or if you roll your own:
{VAR} - search and replace tag strategy (slow, even with regexp's or str_replace)
$VAR - pass via global variable strategy (medium fast)
VAR - pass via global constant strategy (super fast)
$SomeObject->VAR -- pass a public var from an object (faster than a global $VAR, but klunky implementation and more typing)
If you use Smarty instead of your own view class, you basically are doing {VAR} but with lots of perks and goodies, should you want those. And you may have heard or used XSLT, and basically it's pretty close to the {VAR} technique as well, although harder and more complex, yet more powerful.
For me, I like using the constants. I like speed. You just have to know the ground rules in PHP about using constants -- they're not like regular variables.
So, for me, my bootstrap.php file loads my View class, and then I would do something like this:
View::assignVal('PAGE_TITLE','Registration');
View::displayView('Registration/RegisterForm');
Here you can see that assignVal() basically translates into the PHP define() statement to define a constant. And then displayView basically converts the incoming path to...
views/Registration/vRegisterForm.php
...and uses require_once() to load it. And because define() creates a global constant, it's immediately available in the file called by require_once().
Next we have the typical DB class for your project. I stick this under the system folder. All I had to do was make the class extend the PDO class that comes with PHP5. Then I start adding in extra goodies I might want, like a routine that gives me an ActiveRecord (http://en.wikipedia.org/wiki/Active_record_pattern) class for a quick and dirty ORM. I apply the KISS and obvious principles here -- not too many class methods so that things are crazy, but some of the more obvious ones people would actually want to use repeatedly on projects.
And then anything else you want to add as far as goodies to your framework can be inside the "system" folder. So, for instance, a Dates class, a PDF class, a Timer class, a Cart class, an XML class -- the world is your oyster. Just note, however, that site-specific stuff, not framework-specific stuff, should go into anything but the "system" folder. And framework-specific stuff should go into the "system" folder.
So if anyone complains to you on why you did this versus using ZendFramework, CodeIgniter, whatever -- just say that you can do it better than that, and yet have something that's completely obvious for newbies to get started and using. If someone understands MVC, then they'll easily recognize your framework and learn it fast.
As for why would you want to write your own? Well, why do you think the guys who made CodeIgniter, CakePHP, ZendFramework, Kohana, Symfony -- why do you think those and more decided to write their own framework? It's because it was a reaction to either another framework or because they thought they could do something better and cleaner. Often when I read about PHP frameworks, I keep hearing about scary stuff that people say, such as, "Why the heck does CI do x and y? That's scary! That means problems 1, 2, and 3." and the same for another framework. For me, I like to borrow concepts and ideas from multiple frameworks and do things the most efficient way that I can, minus the bugs and gotchas from other frameworks. I also like the freedom.
But even with all this convincing I'm trying to do here, you will always have guys who will hate you for making your own framework, even if you're completely logical and obvious -- and you'll just have to work with them somehow. If that means learning ZF or CI because that's what people are using on a job site, then you just might have to do it. But isn't it more fun to build your own and convince others that you've built something really, really cool? And I tell you -- build your own MVC framework and you will be able to appreciate every other framework out there, and also learn the skills to help you get started with many frameworks fast, and also learn how to criticize these frameworks. So, by all means, make your own MVC framework and learn this skill.
If you're not familiar with what MVC is, here it is:
http://en.wikipedia.org/wiki/Model-view-controller
Basically you are separating your PHP from your PHP + SQL from your XHTML/CSS, and you're providing pretty URLs, and often are loading some common classes you'll often use on projects. Another key point about MVC is the whole way of doing Gang of Four or EAI refactoring under "models", but that's another topic for another day. You can read up on the topic by googling on refactoring.
And another thing that threw me for a loop was the naming. Just think this:
front controller = the central point of access to the website, no matter which URL is typed, and it routes to the page controllers
page controllers = kind of like a skeleton page that loads your models with data, gets data off the models, and sends it to the page templates (views)
models = this was hard for me to grasp why it's called models, but basically it's where the meat of all your code should go so that you have code reuse instead of cut and paste. Just forget that it's called models. Think of it just simply called "classes", even though it's not named that.
views = just replace the notion of "page templates" with "views".
Usually these things start with what's called a front controller. It's basically the central hub where all URL requests come into your website. You might call it index.php, but it's more than just that. You first make an Apache .htaccess file that has something like this in it:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [QSA]
What this does is basically take all 404s, where someone has typed in any URL at all for files that might not exist on the website, and route them all through index.php before it actually does a 404. We call this the pretty url front controller technique. Note that this technique does not cause Google's spider index system to register the site as one great big 404. If you had had use the Apache2 ErrorDocument 404 directive technique for this, then you most certainly would have -- so I don't recommend that. Use the ErrorDocument directive appropriately -- for handling anything that you send a 404 header on from your PHP code.
Then, in index.php, you'll want to load a bootstrap.php that loads your framework, and then parse the incoming URL, and then route out the request to what's called a page controller. But first, let's discuss the obvious directory structure:
.htaccess
404.html
robots.txt
site.conf
index.php
controllers/
controllers/Home/Home.php
models/
views/
views/Home/vHome.php
system/
system/bootstrap.php
system/static_Controller.php
system/static_View.php
system/static_Model.php
system/static_Settings.php
I actually think this is pretty obvious in how this works if you actually know how to use an MVC.
You see, with this, you get your page controllers under "controllers", your models under "models", your view templates under "views", and the framework classes under "system". This makes it pretty obvious to recognize and use.
As well, you get static classes that let you do controller work with static_Controller.php, model work with static_Model.php, and view work with static_View.php.
And the Home/Home.php page controller would be the controller that handles when someone hits your site like:
http://mysite.com/
And the Home/vHome.php view template would be the view you can call from your page controller Home/Home.php so that you show your XHTML. And the reason I used the vHome instead of Home? Yeah -- it's so when editing files you don't get the text editor (IDE) tabs confused and you know therefore that this is a view template file.
The job of bootstrap.php should be to load your common static framework classes and other common classes (non-static) out of the "system" folder -- thereby making all your pages already have those classes in memory. You can also put some instantiation code at the bottom of these common classes so that you don't need to instantiate them.
Okay, now going back to the index.php, which is your front controller, take a cue from the CodeIgniter. In CI, you can create a folder structure like this:
controllers/EmployerSettings/Update.php
and automatically call it without having to edit your front controller (index.php) like so:
http://mysite.com/employer-settings/update/
And, as a bonus, you can do:
http://mysite.com/employer-settings/update/2323023/ABCDE/
...and the Update.php file can receive the stuff after /update/ by parsing for this and using it as necessary.
Pretty slick, huh? Well, what makes that possible is that you parse the incoming URL, apply some rules on the string, prevent XSS and do other security controls, and then use require_once() to load that file. So, the general format for URLs would be:
http://mysite.com/{Domain Object}/{Task}/{whatever}/{whatever}
So that gives you a front controller routing to page controllers. You then use the page controllers like the glue to feed stuff into and out of the models, and then into the selected view, before telling the view template to display.
Models will be where the meat of your code is, but a lot of people just make fat page controllers and come back around to making the models do the heavy lifting after they get the project into a phase 2. It's your call. Models really do make your life a whole lot easier, though, when you get going on a huge project. They help you avoid the cut and paste and move into the code reuse. So what you typically do is divide the website project up into what are the major features, and these are called Domain Objects. Then, you have minor features, and those are Sub Objects to the Domain Objects. You can represent that either in one folder or two levels of folders under models. Last, you have the task on a given object, and so that's your actual file. For instance:
models/Settings/Employer/Update.php
...and inside Update.php you might have a class named like so:
class Settings_Employer_Update {
}
So, to load that model, you might create the right system/static_Model.php class such that you can do this:
Model::loadModel('Settings/Employer/Update');
Now let's look at the views. You can create XHTML and stick it in your views by creating a folder and file structure under "views" like so:
views/Registration/vRegisterForm.php
views/Registration/vRegisterResult.php
And inside those files just start with this at the top...
<?php ?>
and follow with your XHTML. Okay, fine, but how do you get data into those files? Well, a variety of ways, and this is where your system/static_View.php class comes in. You really only have these choices no matter what template engine you use, or if you roll your own:
{VAR} - search and replace tag strategy (slow, even with regexp's or str_replace)
$VAR - pass via global variable strategy (medium fast)
VAR - pass via global constant strategy (super fast)
$SomeObject->VAR -- pass a public var from an object (faster than a global $VAR, but klunky implementation and more typing)
If you use Smarty instead of your own view class, you basically are doing {VAR} but with lots of perks and goodies, should you want those. And you may have heard or used XSLT, and basically it's pretty close to the {VAR} technique as well, although harder and more complex, yet more powerful.
For me, I like using the constants. I like speed. You just have to know the ground rules in PHP about using constants -- they're not like regular variables.
So, for me, my bootstrap.php file loads my View class, and then I would do something like this:
View::assignVal('PAGE_TITLE','Registration');
View::displayView('Registration/RegisterForm');
Here you can see that assignVal() basically translates into the PHP define() statement to define a constant. And then displayView basically converts the incoming path to...
views/Registration/vRegisterForm.php
...and uses require_once() to load it. And because define() creates a global constant, it's immediately available in the file called by require_once().
Next we have the typical DB class for your project. I stick this under the system folder. All I had to do was make the class extend the PDO class that comes with PHP5. Then I start adding in extra goodies I might want, like a routine that gives me an ActiveRecord (http://en.wikipedia.org/wiki/Active_record_pattern) class for a quick and dirty ORM. I apply the KISS and obvious principles here -- not too many class methods so that things are crazy, but some of the more obvious ones people would actually want to use repeatedly on projects.
And then anything else you want to add as far as goodies to your framework can be inside the "system" folder. So, for instance, a Dates class, a PDF class, a Timer class, a Cart class, an XML class -- the world is your oyster. Just note, however, that site-specific stuff, not framework-specific stuff, should go into anything but the "system" folder. And framework-specific stuff should go into the "system" folder.
So if anyone complains to you on why you did this versus using ZendFramework, CodeIgniter, whatever -- just say that you can do it better than that, and yet have something that's completely obvious for newbies to get started and using. If someone understands MVC, then they'll easily recognize your framework and learn it fast.
As for why would you want to write your own? Well, why do you think the guys who made CodeIgniter, CakePHP, ZendFramework, Kohana, Symfony -- why do you think those and more decided to write their own framework? It's because it was a reaction to either another framework or because they thought they could do something better and cleaner. Often when I read about PHP frameworks, I keep hearing about scary stuff that people say, such as, "Why the heck does CI do x and y? That's scary! That means problems 1, 2, and 3." and the same for another framework. For me, I like to borrow concepts and ideas from multiple frameworks and do things the most efficient way that I can, minus the bugs and gotchas from other frameworks. I also like the freedom.
But even with all this convincing I'm trying to do here, you will always have guys who will hate you for making your own framework, even if you're completely logical and obvious -- and you'll just have to work with them somehow. If that means learning ZF or CI because that's what people are using on a job site, then you just might have to do it. But isn't it more fun to build your own and convince others that you've built something really, really cool? And I tell you -- build your own MVC framework and you will be able to appreciate every other framework out there, and also learn the skills to help you get started with many frameworks fast, and also learn how to criticize these frameworks. So, by all means, make your own MVC framework and learn this skill.