Creating a PHP MVC framework - Part 3
Welcome to the third part of the PHP MVC tutorial! In the following series we’ll dig deeper onto MVC, in this part we’ll work on the “M” part of MVC, that’s the model!
As BackBone fundamentals describes “The Model represent domain-specific knowledge and data in an application (bussiness data). Think of this as being a type of data you can model — like a User, Photo, or Todo note.”, it is in fact describing the classic MVC, but it applies to web MVC.
It’s important to note that the original implementation of MVC is not the one you see on the web today, it has changed so much in fact it’s actually called Model 2, nevertheless if you ask developers what type of framework Rails (or any other popular Model 2 framework) is, they will most likely answer “an MVC framework”.
It changed because the HTTP protocol is stateles, every request creates a new communication channel between the application and the user, there isn’t a constant connection, as in the desktop world, so the classic MVC had to be modified to work in this context.
Back to web MVC, most of MVC frameworks come with a nice ORM or API we can use to abstract the database from us, which enables us to easily represent the bussiness data in our application, taking us one level higher by using abstraction, remember how C is a low level programming language and dynamic languages such as PHP are higher level? Frameworks provide an even higher level by using several abstractions.
On abstraction
Feel free to skip this section up until we actually start working on our Model layer, nevertheless if you are new to frameworks it might be a nice read.
Abstractions may seem nice but have in mind that they add complexity to the final product, making it harder to pick up, some frameworks, such as (CodeIgniter)[http://ellislab.com/codeigniter] have a pretty low learning curve, just because they don’t abstract as much from you as other frameworks, the opposite could be said for (Symfony2)[http://symfony.com/], it’s harder to learn, but eventually you can take advantage from the higher level of abstraction by writing less code, and get results faster.
In these tutorials I’ll aim for a pretty thin framework, meaning the abstraction will be rather low, feel free to work on the code and add as much as you want, or even remove some you dont like!
This very reason is why there’s no definitive framework, CodeIgniter is nice applications which need to run in a very limited hosting environment, or when the development team is new to MVC, as it’s pretty easy to learn and provide a solid base, on the other hand, Symfony might be better for an experienced team, writing big maintainable systems, as they will be able to take maximum benefits from the high level of abstraction Symfony provides.
I could spend a whole post talking about frameworks, when and why to use them, nevertheless we should skip right onto the meat shouldn’t we?
The M in MVC… The model!
To give our little framework a bit of abstraction let’s opt for using an ORM, remember when I said we shouldn’t reinvent the wheel? Well, I won’t even attempt to write an ORM for this, and neither should you! This is a very common problem and as so, it has several good solutions, if you do a quick google search for “PHP ORM” you’ll find the biggest options we have to choose from, some of them beeing Doctrine, Propel, ActiveRecord and RedBean, even some alternative solutions come out, such as NotORM.
If you don’t know what an ORM is, this is a good day for you :) ORMs provide a simple and object oriented API to work with databases, all you end up doing is working with objects (that’s the big idea, map objects to relational data), the ORM handles all the SQL stuff. Normally, ORMs map a class to a table in the database, so they map the columns in a table to the properties of a class.
ORMs provide an API to create, read, update and delete entries from a database without the need to worry about SQL and in a database-agnostic way so your code will work for several databases, most ORMs support at least MySQL, SQLite and Posgres.
As for the ORM in this framework, personally I choose PHP ActiveRecord. I want an ORM so NotORM is out of the question, RedBean is nice, but it doesn’t really use models, it just wasn’t designed that way, Propel is also pretty nice but it uses PHP 5.4 and I’d rather stick to 5.3 compatibility for now, finally Doctrine and is pretty awesome but it’s HUGE, and I think it’s an overkill for this framework.
PHP ActiveRecord is as simple as an ORM can get in my opinion, let’s include it
by using Composer, first of all, let’s install composer,
it’s as simple as running the following command curl -sS https://getcomposer.org/installer | php
inside the framework folder.
It will take a few minutes to install, once it finishes we can create a composer.json
file, it will hold all our dependencies, for now, it’s only PHP-AR, so just
put this code inside
{
"require": {
"php-activerecord/php-activerecord": "dev-master"
}
}
Now by running php composer.phar install
we tell composer to install
all our dependencies, in this case it will download PHP ActiveRecord for us.
If you have problems with this step, have in mind, I’m working on unix (ubuntu 12.04)
by using Vagrant, and I have the php-cli
package installed, thus I can use
the php
command from the command line, I also have curl
installed, so
if you want to run the command above you’ll need curl and php in your terminal
path.
Once you run the install command, and wait a few minutes, composer will install
PHP ActiveRecord onto vendor/
, all we have to do now is loading composer’s
autoloader to be able to use any class inside any dependency without bothering
to include any files, how neat is that?
As we’ll need to load all our dependencies before we actually start the controller’s actions, let’s just do it in the index.php file.
// require composer autoloader require(__DIR__ . '/vendor/autoload.php'); // If the controller does exist, include it and call the action/function require($ctrl_file); $ctrl = new $controller; $ctrl->$action();
Eventually we could create our own autoloader which, for now, let’s just use the autoloader for the dependencies.
We just modified a line, adding composer’s autoloader. Let’s now take a look at the documentation of PHP ActiveRecord, the setup is rather simple, but where do we initialize ActiveRecord?
As all models will be called from the controller, I think it’s a good place to do so, another option would be the index.php file, but let’s try to leave the index.php as small as possible for now, we can later on move code around, once the framework has a more mature form.
As we’re gonna toy around with a database, let’s create a new one, I’ll use MySQL but you can use SQLite or any PDO compatible database, if you do use MySQL you can dump this SQL to create a posts table
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(25) NOT NULL,
`content` text NOT NULL,
`created` int(11) NOT NULL,
`updated` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
If you don’t use MySQL then you’ll have to create the table on your own, it’s
just a basic example table, it has an auto_increment
id, a title, a
content and we’ll store time as an integer to avoid hassle, using created and
updated columns.
We’ll now use the Controller’s constructor to configure our models
public function __construct() { ActiveRecord\Config::initialize(function($cfg) { // activerecord will autoload all classes inside this folder $cfg->set_model_directory(__DIR__ . '/../models'); // let's use a mysql datbase, in this case the username and // password are both root, and the database is php-mvc $cfg->set_connections(array( 'development' => 'mysql://root:root@localhost/php-mvc' )); }); }
Here we hard-code the configuration parameters of ActiveRecord, we should load them from a configuration file global to our application, nevertheless, we’ll work on that in the later parts of the tutorial, just have in mind that’s temporary ;)
Our Controller has now three methods, nevertheless, two of the three are methods
all controllers must implement, let’s use Object Oriented Programming to
encapsulate the common behaviour of our controller, create a new file named
Controller.php
, all other controllers will inherit from this guy, and
just move the two common methods to this file, note that i’ve changed the
privacy of the view
function to protected.
class Controller { public function __construct() { ActiveRecord\Config::initialize(function($cfg) { // activerecord will autoload all classes inside this folder $cfg->set_model_directory(__DIR__ . '/../models'); // let's use a mysql datbase, in this case the username and // password are both root, and the database is php-mvc $cfg->set_connections(array( 'development' => 'mysql://root:root@localhost/php-mvc' )); }); } protected function view($file, $params = array()) { extract($params); require(__DIR__ . '/../views/' .$file); } }
We can now simplify our MainController
to just the routed methods (remember
how the URL matched the class’ methods?). MainController now looks like this:
require(__DIR__ . '/Controller.php'); class MainController extends Controller { public function index() { // What time is it? Save it as integer $now = time(); // Create a new post with given data, because activerecord autoloaded // all our models we can just call this class $post = new Post(array( 'title' => 'A blog post', 'content' => 'Lorem ipsum dolor sit amet', 'created' => $now, 'updated' => $now, )); // save it to the database! $post->save(); // let's now retrieve all posts $posts = Post::find('all'); $this->view('Main/index.html.php', array( 'posts' => $posts, )); } }
Notice I’ve already changed what the index method actually does, we now
create a new instance of a class named Post
, this is a file which now
lives in app/models/Post.php
`, and it’s just an empty class, as
PHP ActiveRecord makes it pretty easy to create models.
We instantiate the class with an array, this is what we’ll save onto the
database, just by calling the save
method the instance is persisted
onto the database, easy huh?
To retrieve all posts it’s also easy, see PHP-AR documentation for more on how to use all features.
In PHP-AR models are just classes which inherit from a base PHP-AR model, and as so they inherit a bunch of useful methods, data validation, and such.
class Post extends ActiveRecord\Model { }
As you can see, it’s just empty for now, we are going to add validation logic in a few moments,
something important to note though, is that the class will map to a table named
posts
, this is a convention over configuration
and can be changed if needed.
Another change I’ve made is in the view, this time, to display all the posts added, each time the index function is called, it generates a new post, and then displays a list of all the posts, so if you refresh the page several times you can see how it grows.
<!DOCTYPE html>
<html>
<head>
<title>Example View</title>
</head>
<body>
<h1>All posts</h1>
<?php foreach($posts as $post): ?>
<h1><?php echo $post->title; ?> - ID: <?php echo $post->id; ?></h1>
<h2>Created on <?php echo date('d M Y', $post->created); ?></h2>
<?php echo $post->content; ?>
<hr />
<?php endforeach; ?>
</body>
</html>
Let’s now add some validation logic to the model
class Post extends ActiveRecord\Model { // Validate the size of... static $validates_size_of = array( // the title, within 5 and 25 characters array('title', 'within' => array(5, 25)), // the content, at least 15 characters array('content', 'minimum' => 15), ); static $validates_presence_of = array( array('created'), array('updated'), ); }
We now have an easy way to check whether a model is valid, let’s update our controller
require(__DIR__ . '/Controller.php'); class MainController extends Controller { public function index() { // What time is it? Save it as integer $now = time(); // Create a new post with given data, because activerecord autoloaded // all our models we can just call this class $post = new Post(array( 'title' => 'A blog post', 'content' => 'Lorem ipsum dolor sit amet', 'created' => $now, 'updated' => $now, )); if($post->is_valid()) { // save it to the database! $post->save(); } else { // assuming we got the data from the user, we can access all // errors using errors->full_messages() // for now just dump the data var_dump($post->errors->full_messages()); return; } // let's now retrieve all posts $posts = Post::find('all'); $this->view('Main/index.html.php', array( 'posts' => $posts, )); } }
Here we can see yet another advantage of separation of concern, say the user just submitted a POST form, and we want to know if the submitted data is valid, in ye olde PHP we would have to check each field, each with it’s own custom validation, and then if it’s valid, create the new entry, if not, display the errors.
One of the problems with that is that we are mixing the bussiness logic and our data model, if the bussiness logic gets complex or even if the validations gets complex, code can get pretty big, hard to understand and mantain.
With MVC all the validation logic lives inside the model, so validation is much simpler to see, as we are separating the problem into smaller chunks, also as the model it’s actually reflecting a table in the database it makes sense to put validation logic there, it’s the model’s problem whether the data it holds is valid or not, from within the controller we can just say “We have this POST data, is it valid? If so add it to the database, else show errors”, we don’t have to know whether the title must be between 5 and 15 characters or at least 20, or even if it can be empty.
Another advantage is that as the validation logic lives inside the model, we just have to write it once, and it’s much easier to modify later on if it changes, as all validations live in a common place, the models, you can just say “Oh, the schema changed, now the title cannot be empty, oh well I’ll just add a validation in the model” and all your application will reflect those changes, easy enough!
Conclusion
That’s it! PHP-AR does all the heavy lifting from us, and we end up with a nice level of abstraction. We still have some work to do before the Model layer is oficially finished, mostly because of the ugly way we do configuration, nevertheless is starting to look like a framework!
If you’ve followed me along, thanks a lot, and good job! In the next tutorials we’ll work on the C and V of MVC.
Remember you can see the source code at the GitHub repo.
Cheers! And please leave your feedback below <3