John Squibb dot com

Part Two: Building Libraries and Drivers.

If you haven't already completed Part One of this tutorial, I highly recommend doing so before continuing with this section. Click here to check it out.

Contents

Creating Libraries

Let's kick part two of the MVC tutorial off by creating exploring a new concept, Libraries. When writing code we often repeat basic tasks without even thinking about it. Whether our script requires us to connect to a database, or perform a file upload, or connect to a resource on a remote server, or another of many potential tasks, we often write the same bits of code over and over. This is counterproductive and contrary to the purpose of our framework, right? Our first inclination may be to simply write a model to handle database connections of file uploads, which would be a completely valid solution and would certainly work. But how might we organize our framework so that models that act as utilities can be distinguished from models that perform the workload of our various controllers? Perhaps a separate naming convention, or subfolders within the models directory? Better yet, we could move the utility classes of our framework out of models entirely, and into a new folder, libraries.

Let's go ahead and create a new folder, libraries in our application root. As developers of dynamic web applications, one of our common tasks includes connecting to and querying a database. This may be a MySQL, Postgresql, or another database engine. You may be using any of several different APIs to interact with the database of your choice, and I am always one to encourage trying different flavors until you find the one that suits your application best. One thing we know for certain, however, is that there is a bare minimum of common functionality that we must implement regardless of which engine we choose:

	connect to the database.
	prepare a query.
	execute our query.
	fetch the results of our query.
	disconnect from the database.

There can be a lot more going on in a typical script, such as caching of results and queries, locking of tables, transactions for engines that support them, and more, but this gives us a starting point. Now, let's pick an engine. For the purposes of this tutorial, I will be using the MySQL Improved functionality built into PHP, and enabled through the proper compilation flags. I will assume that you know it, or choose to use the engine of your liking.

Go into the libraries folder you created and create a new file called database.php. In database.php we will create an abstract class with the following abstract methods (more on that here if you are unfamiliar with class abstraction):

Toggle Plain Text
  1. <?php
  2. /**
  3.  * The Database Library handles database interaction for the application
  4.  */
  5. abstract class Database_Library
  6. {
  7. abstract protected function connect();
  8. abstract protected function disconnect();
  9. abstract protected function prepare();
  10. abstract protected function query();
  11. abstract protected function fetch();
  12. }

Now we've got a very generic database library 'Database'. In the next paragraph, we will discuss a new concept, known as drivers, which we will utilize to extend our database functionality.

Extending Libraries with Drivers

Rather than define our methods directly in this class, we can use this as a base template for more specific classes. Just like your printer needs a driver or your car needs a driver to make it go, your database library will require a MySQL or some other driver to make it do what it needs to.

Let's go ahead and create a drivers folder inside of the libraries folder. Inside the drivers folder, create a new file called mysqlimproved.php. Since our base database class is abstract, it will act as a requirements list for our individual drivers, though it may include more base functionality down the road as well. Inside mysqlimproved.php, add the following:

Toggle Plain Text
  1. <?php
  2. /**
  3.  * The MySQL Improved driver extends the Database_Library to provide
  4.  * interaction with a MySQL database
  5.  */
  6. class MysqlImproved_Driver extends Database_Library
  7. {
  8. /**
  9. * Connection holds MySQLi resource
  10. */
  11. private $connection;
  12.  
  13. /**
  14. * Query to perform
  15. */
  16. private $query;
  17.  
  18. /**
  19. * Result holds data retrieved from server
  20. */
  21. private $result;
  22.  
  23. /**
  24. * Create new connection to database
  25. */
  26. public function connect()
  27. {
  28. //connection parameters
  29. $host = 'localhost';
  30. $user = 'username';
  31. $password = 'password';
  32. $database = 'my_test';
  33.  
  34. //your implementation may require these...
  35. $port = NULL;
  36. $socket = NULL;
  37.  
  38. //create new mysqli connection
  39. $this->connection = new mysqli
  40. (
  41. $host , $user , $password , $database , $port , $socket
  42. );
  43. }
  44.  
  45. public function disconnect(){}
  46.  
  47. public function prepare(){}
  48.  
  49. public function query(){}
  50.  
  51. public function fetch(){}
  52. }

This creates the basic shell for our MySQL Improved driver. Before continuing on, let's go back to our base library to more narrowly define our parameters for our methods. In order to prepare a query, we would have to provide the query to prepare. Also, when fetching a result we may wish to have data returned in the form of an object or an array. Modify database.php to look like the this:

Toggle Plain Text
  1. <?php
  2. /**
  3.  * The Database Library handles database interaction for the application
  4.  */
  5. abstract class Database_Library
  6. {
  7. abstract protected function connect();
  8. abstract protected function disconnect();
  9. abstract protected function prepare($query);
  10. abstract protected function query();
  11. abstract protected function fetch($type = 'object');
  12. }

in our driver, we can now fill in the gaps a bit more:

Toggle Plain Text
  1. <?php
  2. /**
  3.  * The MySQL Improved driver extends the Database_Library to provide
  4.  * interaction with a MySQL database
  5.  */
  6. class MysqlImproved_Driver extends Database_Library
  7. {
  8. /**
  9. * Connection holds MySQLi resource
  10. */
  11. private $connection;
  12.  
  13. /**
  14. * Query to perform
  15. */
  16. private $query;
  17.  
  18. /**
  19. * Result holds data retrieved from server
  20. */
  21. private $result;
  22.  
  23. /**
  24. * Create new connection to database
  25. */
  26. public function connect()
  27. {
  28. //connection parameters
  29. $host = 'localhost';
  30. $user = 'username';
  31. $password = 'password';
  32. $database = 'my_test';
  33.  
  34. //your implementation may require these...
  35. $port = NULL;
  36. $socket = NULL;
  37.  
  38. //create new mysqli connection
  39. $this->connection = new mysqli
  40. (
  41. $host , $user , $password , $database , $port , $socket
  42. );
  43.  
  44. return TRUE;
  45. }
  46.  
  47. /**
  48. * Break connection to database
  49. */
  50. public function disconnect()
  51. {
  52. //clean up connection!
  53. $this->connection->close();
  54.  
  55. return TRUE;
  56. }
  57.  
  58. /**
  59. * Prepare query to execute
  60. *
  61. * @param $query
  62. */
  63. public function prepare($query)
  64. {
  65. //store query in query variable
  66. $this->query = $query;
  67.  
  68. return TRUE;
  69. }
  70.  
  71. /**
  72. * Execute a prepared query
  73. */
  74. public function query()
  75. {
  76. if (isset($this->query))
  77. {
  78. //execute prepared query and store in result variable
  79. $this->result = $this->connection->query($this->query);
  80.  
  81. return TRUE;
  82. }
  83.  
  84. return FALSE;
  85. }
  86.  
  87. /**
  88. * Fetch a row from the query result
  89. *
  90. * @param $type
  91. */
  92. public function fetch($type = 'object')
  93. {
  94. if (isset($this->result))
  95. {
  96. switch ($type)
  97. {
  98. case 'array':
  99.  
  100. //fetch a row as array
  101. $row = $this->result->fetch_array();
  102.  
  103. break;
  104.  
  105. case 'object':
  106.  
  107. //fall through...
  108.  
  109. default:
  110.  
  111. //fetch a row as object
  112. $row = $this->result->fetch_object();
  113.  
  114. break;
  115. }
  116.  
  117. return $row;
  118. }
  119.  
  120. return FALSE;
  121. }
  122. }

Modifying the Router

Now in order to try out our new library and driver setup, we have to first make some changes to the way files are served in our framework. Open up the router.php file located in the controllers folder that we created in the first part of this tutorial. If we look at our __autoload function we'll see the code we wrote to handle the 'lazy loading' of our models. Since we used the same naming convention for our libraries and drivers, a quick modification to this code will allow us to load those as easily.

Modify the router __autoload method to look like this:

Toggle Plain Text
  1. function __autoload($className)
  2. {
  3. //parse out filename where class should be located
  4. list($filename , $suffix) = split('_' , $className);
  5.  
  6. //select the folder where class should be located based on suffix
  7. switch (strtolower($suffix))
  8. {
  9. case 'model':
  10.  
  11. $folder = '/models/';
  12.  
  13. break;
  14.  
  15. case 'library':
  16.  
  17. $folder = '/libraries/';
  18.  
  19. break;
  20.  
  21. case 'driver':
  22.  
  23. $folder = '/libraries/drivers/';
  24.  
  25. break;
  26. }
  27.  
  28. //compose file name
  29. $file = SERVER_ROOT . $folder . strtolower($filename) . '.php';
  30.  
  31. //fetch file
  32. if (file_exists($file))
  33. {
  34. //get file
  35. include_once($file);
  36. }
  37. else
  38. {
  39. //file does not exist!
  40. die("File '$filename' containing class '$className' not found in '$folder'.");
  41. }
  42. }

Notice how we added the switch statement to check the suffix of our class names for the appropriate folder to search for classes in. This allows the router to serve files of all different types so long as they are organized according to our evolving naming convention.

Adding a Database

We are almost ready to use our database library, however doing so would be rather difficult since we don't actually have any data to query against! If you already have a schema set up with data you would like to practice with, please feel free to skip this seciton and do so. If you lack data, use the following mysqldump file to create some data in your local database. Please ensure that you do not have a conflicting schema in your database before running this script:

Modifying the News Model

Now we should have some data for testing our library out! In the News_Model class we created in the first part of this tutorial, let's remove the hard-coded articles and instead use our database library to pull the dynamic article content from the database by author name. Modify /models/news.php to read:

Toggle Plain Text
  1. <?php
  2. /**
  3.  * The News Model does the back-end heavy lifting for the News Controller
  4.  */
  5. class News_Model
  6. {
  7. /**
  8. * Holds instance of database connection
  9. */
  10. private $db;
  11.  
  12. public function __construct()
  13. {
  14. $this->db = new MysqlImproved_Driver;
  15. }
  16.  
  17. /**
  18. * Fetches article based on supplied name
  19. *
  20. * @param string $author
  21. *
  22. * @return array $article
  23. */
  24. public function get_article($author)
  25. {
  26. //connect to database
  27. $this->db->connect();
  28.  
  29. //can you detect the gaping security flaw here?
  30.  
  31. //prepare query
  32. $this->db->prepare
  33. (
  34. "
  35. SELECT
  36. `date`,
  37. `title`,
  38. `content`,
  39. `author`
  40. FROM
  41. `articles`
  42. WHERE
  43. `author` = '$author'
  44. LIMIT
  45. 1
  46. ;
  47. "
  48. );
  49.  
  50. //execute query
  51. $this->db->query();
  52.  
  53. $article = $this->db->fetch('array');
  54.  
  55. return $article;
  56. }
  57.  
  58. }

In order to pull articles by author name, we are going to have to adjust our router.php file again to allow URL-encoded characters such as space. In a nutshell, if we want to pull the article written by 'John Squibb', when we provide this string in the URL, it will be encoded as 'John%20Squibb'. Modify the router.php file by changing:

	
	 $getVars[$variable] = $value;
	 
	 to:
	 
	 $getVars[$variable] = urldecode($value);

Filtering Input

While we are on the topic of filtering input, it is important to note that since we are accepting data from the user to search our database with, it is essential that we clean it. Any data that is provided to your application must be filtered, cleaned, groomed, validated, or what have you. Never trust data from your users! A simple function is available to clean data before passing it to MySQL. We will create a method to add to the mysqlimproved.php file:

Toggle Plain Text
  1. /**
  2.  * Sanitize data to be used in a query
  3.  *
  4.  * @param $data
  5.  */
  6. public function escape($data)
  7. {
  8. return $this->connection->real_escape_string($data);
  9. }

Let's modify our get_article function in /models/news.php to make our code a bit safer:

Toggle Plain Text
  1. /**
  2.  * Fetches article based on supplied name
  3.  *
  4.  * @param string $author
  5.  *
  6.  * @return array $article
  7.  */
  8. public function get_article($author)
  9. {
  10. //connect to database
  11. $this->db->connect();
  12.  
  13. //sanitize data
  14. $author = $this->db->escape($author);
  15.  
  16. //prepare query
  17. $this->db->prepare
  18. (
  19. "
  20. SELECT
  21. `date`,
  22. `title`,
  23. `content`,
  24. `author`
  25. FROM
  26. `articles`
  27. WHERE
  28. `author` = '$author'
  29. LIMIT
  30. 1
  31. ;
  32. "
  33. );
  34.  
  35. //execute query
  36. $this->db->query();
  37.  
  38. $article = $this->db->fetch('array');
  39.  
  40. return $article;
  41. }

Note, the quick addition of the escape() method in our mysql class should not be the final say in cleansing data. You should strive to validate all incoming data rather than just make it query-safe before throwing it at mysql. We'll touch more on this later when we cover helpers.

Viewing Changes

Save your changes, and call up http://yoursite.com/index.php?news&author=John Squibb
Voila! You now have a working database library.

Download Source

You can download the source files from the following links. They contain the full framework from part 1 of this tutorial, with the modifications made in this section of the tutorial.

What's Next

In the next segment of this series of MVC tutorials, we'll cover the following:

  • Helpers - static classes that provide various functionality such as validation, page redirects, etc.
  • Globals and security. As our framework grows, we are faced with the enormous responsibility of securing our application.
  • Configuration files. We'll get rid of all those 'magic' numbers and random bits of code strewn throughout our files, and sort them into logical config files.

Thanks for reading!