PHP MVC Framework: the Cache Class

Welcome to the final installment of a series that shows you how to build a simple MVC-based framework in PHP 5. This series walks you in a step-by-step fashion through the development of a stack of reusable components, which can be easily put to work together under a strict MVC-based layer.

Learning how to implement the Model-View-Controller design pattern within a real-world project can be of great help in sharpening the skills of any web developer who wants to be in touch with modern software programming trends.

So, if you’re a PHP coder who wants to grasp the key concepts that surround the implementation of this architectural pattern by building a basic, extensible framework, you’ve come to the right place.

Of course, if you’ve been a loyal follower of this series and have read all of the tutorials that precede this one, then you now have a thorough background in how to use the aforementioned MVC framework for building a trivial MySQL-driven application. This application is capable of performing CRUD operations on a database table containing user-related data.

What’s more, in the penultimate chapter I finished building the application by adding to it the pair of view files required to display on screen the list of users stored in the database, and to render an HTML form that collects data on new users.

While the application currently performs quite well, it’s possible to improve it further by enabling it to cache result sets via the cache class that was built in a previous chapter (you remember that class, right?). Therefore, in this last tutorial of the series I’m going to demonstrate how to use the framework’s cache class to improve the performance of this sample application.

Now, let’s tackle this final leg of our educational journey. Let’s get going!

{mospagebreak title=Review: the sample application}

To better understand how the performance of the MySQL-driven program developed in the previous chapter of the series can be improved by means of the cache class, it’s necessary to list the program’s full source files as they were initially created.

Below I included the definitions of those files, starting with the user controller class. Here it is:

(UsersController.php)

<?php

class UsersController

{

private $model = NULL;

 

// constructor

public function __construct()

{

// store model object as property

$this->model = new Model(MySQL::getInstance(array(‘host’, ‘user’, ‘password’, ‘database’)));

}

// fetch and display all users

public function index()

{

// create view object

$view = new View(‘users’);

// create view properties

$view->title = ‘Using the MVC design pattern with PHP 5′;

$view->heading = ‘User List’;

$view->users = $this->model->fetchAll();

// display view

echo $view->display();

}

// create new user

public function create()

{

// create view object

$view = new View(‘user_form’);

// create view properties

$view->title = ‘Using the MVC design pattern with PHP 5′;

$view->heading = ‘Create new user’;

// display view

echo $view->display();

}

 

// save user

public function save()

{

// get POST params

if (Input::post(‘send’))

{

$fname = Input::post(‘fname’);

$lname = Input::post(‘lname’);

$email = Input::post(‘email’);

// save user data

$this->model->save(array(‘fname’ => $fname, ‘lname’ => $lname, ‘email’ => $email));

}

}

// update existing user

public function update($id)

{

$this->model->save(array(‘fname’ => ‘My first name’, ‘lname’ => ‘My last name’, ‘email’ => ‘myemail@domain.com’), (int)$id);

}

// delete existing user

public function delete($id)

{

$this->model->delete((int)$id);

}

}// End UsersController class

As shown above, the user controller class implements a few basic methods for retrieving all of the users stored in the corresponding MySQL table, and for inserting, updating and deleting records in that table. In keeping with the schema followed by the MVC design pattern, these operations are executed by the model; the view class is responsible for sending the generated output to the browser.

Speaking of browser output, below is the pair of view files that perform this task. First, look at the view that displays the full list of users:

(users.php)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

<title><?php echo $title;?></title>

</head>

<body>

<h1><?php echo $heading;?></h1>

<?php if (empty($users)):?>

<h2>Oops! There are not users in the database.</h2>

<?php else:?>

<?php foreach($users as $user):?>

<p><strong>First Name : </strong><?php echo $user->fname;?></p>

<p><strong>Last Name : </strong><?php echo $user->lname;?></p>

<p><strong>Email : </strong><?php echo $user->email;?></p>

<hr />

<?php endforeach?>

<?php endif?>

</body>

</html>

And here’s the second view. It renders the HTML form required for inserting new users into the database table:

(user_form.php)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

<title><?php echo $title;?></title>

</head>

<body>

<h1><?php echo $heading;?></h1>

<?php echo Form::open(array(‘action’ => ‘save’, ‘method’ => ‘post’));?>

<p>First Name: <?php echo Form::input(array(‘type’ => ‘text’, ‘name’ => ‘fname’));?></p>

<p>Last Name: <?php echo Form::input(array(‘type’ => ‘text’, ‘name’ => ‘lname’));?></p>

<p>Email: <?php echo Form::input(array(‘type’ => ‘text’, ‘name’ => ‘email’));?></p>

<p><?php echo Form::input(array(‘type’ => ‘submit’, ‘name’ => ‘send’, ‘value’ => ‘Create user’));?></p>

<?php echo Form::close();?>

</body>

</html>

With the inclusion of these two view files, it’s easy to understand how this example web program works. Naturally, its functionality relies on most of the classes that comprise the MVC framework, which you hopefully learned about in previous tutorials.

However, as I pointed out in the introduction, the framework’s cache class has been left abandoned (if the term is really applicable in this case), off in a dark corner. This is about to change. In the following segment I’m going to demonstrate how to exploit its functionality to avoid performing additional SQL queries when fetching users from the database.

This process will be discussed in detail in the section to come. So click on the link that appears below and keep reading.

{mospagebreak title=Caching database result sets}

As you may know, one of the most common approaches used for enhancing the performance of applications, including web- and desktop-based apps, is the implementation of a cache mechanism. This mechanism may use the file system, shared memory, etc. to reduce the number of accesses to a specified resource, such as a relational database.

In the case of the web application being developed here, the simplest way to decrease the number of accesses to its associated MySQL database is by using the framework’s cache class. If you don’t recall how this class was originally defined, below I listed its definition for you:

(Cache.php)

<?php

class Cache

{

private $cachedir = ‘cache/’;

private $expire = 60;

private static $instance = NULL;

 

// get Singleton instance of Cache class

public static function getInstance($cachedir = ”)

{

if (self::$instance === NULL)

{

self::$instance = new self($cachedir);

}

return self::$instance;

}

 

// constructor

public function __construct($cachedir = ”)

{

if ($cachedir !== ”)

{

if (!is_dir($cachedir) or !is_writable($cachedir))

{

throw new Exception(‘Cache directory must be a valid writeable directory.’);

}

$this->cachedir = $cachedir;

}

}

 

// write data to cache file given an ID

public function set($id, $data)

{

$file = $this->cachedir . $id;

if (file_exists($file))

{

unlink($file);

}

// write data to cache

if (!file_put_contents($file, serialize($data)))

{

throw new Exception(‘Error writing data to cache file.’);

}

}

 

// read data from cache file given an ID

public function get($id)

{

$file = glob($this->cachedir . $id);

$file = array_shift($file);

if (!$data = file_get_contents($file))

{

throw new Exception(‘Error reading data from cache file.’);

}

return unserialize($data);

}

 

// check if the cache file is valid or not

public function valid($id)

{

$file = glob($this->cachedir . $id);

$file = array_shift($file);

return (bool)(time() – filemtime($file) <= $this->expire);

}

}// End Cache class

Even though its driving logic is very simple, the cache class permits you to store serialized data in a specified directory during a predefined time period, which can be modified at will. With this caching mechanism available for use, I’m now going to modify the signature of the model class so it can use an instance of the cache class to store in the cache, for at least 60 seconds, the row set returned by its “fetchAll()” method.

The modified version of the model class will look like this:

(Model.php)

<?php

class Model

{

private $db = NULL;

private $cache = NULL;

private $table = ‘users’;

 

// constructor

public function __construct(MySQL $db, $table = ”)

{

// store database class instance

$this->db = $db;

// store Cache class instance

$this->cache = Cache::getInstance();

if ($table !== ”)

{

$this->table = $table;

}

}

// get all rows from specified table

public function fetchAll()

{

// if the cache file is valid fetch records from cache file

if ($this->cache->valid($this->table))

{

return $this->cache->get($this->table);

}

else

{

// otherwise fetch records from database table

$rows = array();

$this->db->query(‘SELECT * FROM ‘ . $this->table);

while ($row = $this->db->fetch())

{

$rows[] = $row;

}

// save data set to cache file

$this->cache->set($this->table, $rows);

return $rows;

}

}

// update/insert row

public function save(array $data, $id = NULL)

{

if (!empty($data))

{

// quote strings

foreach ($data as $field => $value)

{

$value = mysql_escape_string($value);

if (!is_numeric($value))

{

$data[$field] = ”’ . $value . ”’;

}

}

// update row

if ($id !== NULL)

{

$set = ”;

foreach($data as $field => $value)

{

$set .= $field .’=’ . $value . ‘,’;

}

$set = substr($set, 0, -1);

$this->db->query(‘UPDATE ‘ . $this->table . ‘ SET ‘ . $set . ‘ WHERE id=’ . (int)$id);

}

// insert new row

else

{

$fields = implode(‘,’, array_keys($data));

$values = implode(‘,’, array_values($data));

$this->db->query(‘INSERT INTO ‘ . $this->table . ‘ (‘ . $fields . ‘)’ . ‘ VALUES (‘ . $values . ‘)’);

}

}

}

 

// delete row

public function delete($id = NULL)

{

if ($id !== NULL)

{

$this->db->query(‘DELETE FROM ‘ . $this->table . ‘ WHERE id=’ . (int)$id);

}

}

}// End Model class

If you take a close look at the “fetchAll()” method in the model, you’ll quickly grasp how it works. Essentially, this method will first check to see if there’s some data in the specified cache, and if it’s valid. If this happens to be true, then it’ll retrieve this data directly from the cache; otherwise, the data will be fetched from the database and cached for 60 seconds. This simple use of the cache class will notably reduce the overhead for accessing the MySQL table, and the cache expiration is entirely customizable.

You may be wondering why I decided to cache result sets in the model and not in the controller instead. Well, I did it that way because I’m a strong advocate of delegating to the model all the tasks that handle the data layer. In addition, if you’re using a framework that works with a generic model like this one does, then centralizing the caching of data can make your application easier to maintain and scale.

If you find it’s more flexible to implement the cache system within the controller, though, go ahead and do it. That will be fine too.

So far, so good. Now that the model class has been modified to cache data within its “fetchAll()” method, it’s necessary to evaluate the impact that this change has introduced to the controller. Will its definition remain exactly the same,  or will it need to be modified too?

The answer to these question will be revealed in the final section. So go ahead and read the following segment.

{mospagebreak title=The user controller class}

Since data caching is accomplished at the model level, the definition of the controller class will remain exactly the same. However, as I said in the preceding segment, you’re free to use the cache class within the controller if you feel more comfortable doing this.

In summary, the definition of the user controller will be as follows:

(UsersController.php)

<?php

class UsersController

{

private $model = NULL;

 

// constructor

public function __construct()

{

// store model object as property

$this->model = new Model(MySQL::getInstance(array(‘host’, ‘user’, ‘password’, ‘database’)));

}

// fetch and display all users

public function index()

{

// create view object

$view = new View(‘users’);

// create view properties

$view->title = ‘Using the MVC design pattern with PHP 5′;

$view->heading = ‘User List’;

$view->users = $this->model->fetchAll();

// display view

echo $view->display();

}

// create new user

public function create()

{

// create view object

$view = new View(‘user_form’);

// create view properties

$view->title = ‘Using the MVC design pattern with PHP 5′;

$view->heading = ‘Create new user’;

// display view

echo $view->display();

}

 

// save user

public function save()

{

// get POST params

if (Input::post(‘send’))

{

$fname = Input::post(‘fname’);

$lname = Input::post(‘lname’);

$email = Input::post(‘email’);

// save user data

$this->model->save(array(‘fname’ => $fname, ‘lname’ => $lname, ‘email’ => $email));

}

}

// update existing user

public function update($id)

{

$this->model->save(array(‘fname’ => ‘My first name’, ‘lname’ => ‘My last name’, ‘email’ => ‘myemail@domain.com’), (int)$id);

}

// delete existing user

public function delete($id)

{

$this->model->delete((int)$id);

}

}// End UsersController class

At first sight, the logic implemented by each method in the above controller remains the same. Nonetheless, this isn’t completely true, because the “fetchAll()” method will cache the retrieved data for a specified period of time, according to the settings passed to the instance of the cache class.

If at this point, you list all of the users stored in the corresponding database table by typing the following request:

http://localhost/mvc/users/

Then, if all goes well, you should see that list neatly displayed on screen. However, if a new user is added to the database by requesting the “create()” method in the controller, like this:

http://localhost/mvc/users/create

Then, when you refresh the list of users, it won’t reflect any changes, since the result set has been cached by default for 60 seconds. Give a try to all these examples and feel free to introduce into them the tweaks that best suit your personal needs.

Now, you have at your disposal a functional MySQL-driven application that successfully implements the MVC pattern, thanks to the functionality given by its underlying framework. Isn’t that a good thing? You bet it is!

Final thoughts

Sad but true, we’ve come to the end of this series. However, this journey has been instructive, as you learned how to build a basic, yet functional framework in PHP 5 that implements the Model-View-Controller pattern in a pretty strict manner.

As I said at the beginning of the series, I didn’t intend to reinvent the wheel here; there are many production-ready frameworks available that you can use for speeding up the development of your PHP applications. Even so, this group of tutorials should provide you with the right pointers to using the MVC pattern in an object-based context, without having to deal with complex and sometimes overwhelming guidelines.

See you in the next PHP tutorial!

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan