Consuming the previous cache and data access layers: building a simple user model
As it was stated at the beginning, the user model that I plan to build in the following lines will be aware of the underlying storage mechanism. While this approach can be pretty useful in relative simple use cases, unfortunately it fails when it comes to separating business logic from infrastructure (a clear example of this is Active Record, but I’ll leave my rant for some other occasion). Anyway, if you feel adventurous and want to write some extra code, it’s fairly easy to construct a domain model and transfer the CRUD operations to an intermediate layer such as a data mapper.
But it’s time to stop talking, and show the definition of my sample model. Here it is:
(UserModel.php)
<?php
class UserModel { protected $_adapter; protected $_cache;
/** * Constructor */ public function __construct(DatabaseAdapterInterface $adapter, CacheableInterface $cache) { $this->_adapter = $adapter; $this->_cache = $cache; }
/** * Fetch a user by their ID */ public function fetchById($id) { // try to fetch user data from the cache system if ($user = $this->_cache->get($id)) { return $user; } // otherwise fecth user data from the database $this->_adapter->select('users', "id = $id"); if ($user = $this->_adapter->fetch()) { $this->_cache->set($id, $user); return $user; } return null; }
/** * Fetch all users */ public function fetchAll() { // try to fetch users data from the cache system if ($users = $this->_cache->get('all')) { return $users; } // otherwise fecth users data from the database $this->_adapter->select('users'); $users = array(); while ($user = $this->_adapter->fetch()) { $users[] = $user; } if (count($users) !== 0) { $this->_cache->set('all', $users); } return $users; }
/** * Insert new user data (the cache is cleared also) */ public function insert(array $data) { if ($insertId = $this->_adapter->insert('users', $data)) { $this->_cache->clear(); return $insertId; } return false; }
/** * Delete the table row corresponding to the specified user (the cache is cleared also) */ public function delete($id) { if ($affectedRow = $this->_adapter->delete('users', "id = $id")) { $this->_cache->clear(); return $affectedRow; } return false; } }
From the above code fragment, it’s clear to see the strong dependency that exists between this user model and the storage layer. However, not all are bad things, as the model’s dependencies are injected in its constructor, something that makes it easier to test it in isolation. What’s more, since this method type hints the couple of segregated interfaces defined before, instead of the concrete classes, it’s really simple to pass to it different database adapters and even several cache back-ends.
Finally, it’s worth to make a brief analysis of the model’s CRUD methods, even though they’re fairly easy to grasp: firstly, there’s a couple of generic finders called “findById()” and “findAll()”, which retrieve a single user (or all of them) from the corresponding MySQL table and save this data to the current cache. And lastly, the “insert()” and “delete()” methods add a new user to the specified MySQL table and remove an existing one respectively.
At this moment, it’s perfectly possible to give the model a try and see how it works. But, considering that to do so you’ll need to create a sample MySQL table, plus a couple of scripts that use the model with different cache back-ends, I suggest you to wait until the next tutorial, where I’ll be doing all this hard work for you.
Final thoughts
In this penultimate chapter of the series, I showed how to create a functional model, whose collaborators relied on the contracts defined by a couple of segregated interfaces to do their businesses. But hang on a second! To demonstrate that the model is as functional as it seems at first glance, it should be properly tested, right? Don’t you feel concerned, however, as this will be done in the final installment of the series.