Categories
HTML MVC PHP

Picture Uploads in PHP MVC Applications

Basic picture file uploads are handled on the client-side by a form and on the server-side by a script moving a cached temporary file to a permanent location while recording the appropriate path information to database. In this post, we will elaborate on each of these portions.

The HTML Form

For forms to upload files correctly, 2 requirements must be met: a named file input must be included in the form and the encoding type must be appropriately. We show below an example proper form:

  1. <form action='' method='post' enctype='multipart/form-data'>
  2.   <div class='form-group'>
  3.     <label>Picture:
  4.       <input type='file' name='newPicture' class='form-control' />
  5.     </label>
  6.   </div>
  7.   <div class='form-group'>
  8.     <label>Description:
  9.       <textarea name='description' class='form-control'></textarea>
  10.     </label>
  11.   </div>
  12.   <input type='submit' name='action' value='Add picture' class='btn btn-primary' />
  13.   <a href='/product/index' class='btn btn-secondary'>Cancel</a>
  14. </form>

First, we see on line 1 that the enctype attribute for the form is set to multipart/form-data. This encodes the submitted form data with different parts for different data, much like an email with attachments included. If the form enctype attribute to this value, the file is simply not received by the server application.

Second, we see on line 4 an input with the type attribute set to the value file. This input also has the attribute name set to newPicture so that we may refer to it by this name in the server script.

The submit URL for this form will be the same as its calling URL.

The PHP Script

Now, a server-side script must receive the form data, check the data and save the file as appropriate:

  1. public function addPicture($product_id){
  2.   if(isset($_FILES['newPicture']) 
  3.     && $_FILES['newPicture']['error'] == UPLOAD_ERR_OK){
  4.     $info = getimagesize($_FILES['newPicture']['tmp_name']);
  5.     $allowedTypes = [IMAGETYPE_JPEG=>'.jpg',
  6.                      IMAGETYPE_PNG=>'.png',
  7.                      IMAGETYPE_GIF=>'.gif'];//accept jpg, png, gif
  8.     if($info === false){ // no go
  9.       $this->view('product/addPicture', ['error'=>'Bad file format']);
  10.     }else if(!array_key_exists($info[2], $allowedTypes)){ // no go
  11.       $this->view('product/addPicture', 
  12.         ['error'=>'Not an accepted file type']);
  13.     }else{
  14.       //save the picture in the images folder
  15.       $path = getcwd().DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR;
  16.       $filename = uniqid().$allowedTypes[$info[2]];
  17.       move_uploaded_file($_FILES['newPicture']['tmp_name'], $path.$filename);
  18.  
  19.       $newPicture = $this->model('Picture');
  20.       $newPicture->product_id = $product_id;
  21.       $newPicture->filename = $filename;
  22.       $newPicture->description = $_POST['description'];
  23.       $newPicture->create();
  24.       header('location:/product/index');
  25.     }
  26.   }else{
  27.     $this->view('product/addPicture');
  28.   }
  29. }

This method is meant to accept files associated to products in a product catalog. Therefore, on line 1, we need to pass in the product_id foreign key value for the picture record to refer to the appropriate product record.

On lines 2 and 3, we verify that the picture is indeed sent (line 2) to the receiving script without any error (line 3). If not, the script branches to line 27 and presents the submission form.

On lines 4 and 8, we extract image file information (line 4) that will allow us to validate (on line 8) that the received file is indeed an image. If not, an error message is sent to the user on line 9.

At line 10, the image information (from line 4) is used again against an image type whitelist (produced on lines 5-7). If the file is not of the proper type, an error message is sent to the user, on lines 11-12.

Finally, if all checks are good, the script continues on to lines 15-24. Lines 15, 16, 17 handle saving the file to the filesystem as follows:

  1. Define the saving path in the OS, based on the current working directory. The file will be saved in the images folder under the server document root.
  2. Define the file name to a random string with the extension from the whitelist defined on lines 5-7.
  3. Move (save) the file from its temporary location to the images folder.

Lines 19-24 record the data as follows:

  1. Get an instance of the Picture class.
  2. Populate the product_id foreign key from the method parameter.
  3. Populate the filename, since other computers don’t have access to the full path structure. We will use this filename later to display these pictures.
  4. Populate the description from the form data.
  5. Invoke Picture::create() to save the Picture record to the database.
  6. Redirect the client side to the Product index.

Viewing the Pictures

In such an application, we simply call up all the pictures in a product detail user story with a controller method as follows:

  1. public function detail($product_id){
  2.   $theProduct = $this->model('Product')->find($product_id);
  3.   $thePictures = $this->model('Picture')->getForProduct($product_id);
  4.   $theProduct->pictures = $thePictures;
  5.   $this->view('product/detail', $theProduct);
  6. }

By setting the pictures to be part of the product, we are able to pass all the data as a single object to then output the picture URLs as in the following view code:

  1. <?php
  2. foreach($data->pictures as $picture){
  3.   echo "<img src='/images/$picture->filename' style='max-width:100px;' />";
  4.   echo "<a href='/product/deletePicture/$picture->picture_id' class='btn btn-danger'>Delete picture</a>";
  5. }
  6. ?>

On line 3, we output an image element that will refer to the faved file in the images folder under the Web server document root. On line 4, we also include a delete link.

Deleting the Picture

Mistakes happen and things change, so it makes sense to be able to delete pictures from our Web application. The hyperlink form the above view calls the deletePicture method from our product controller, defined as follows:

  1. public function deletePicture($picture_id){
  2.   $thePicture = $this->model('Picture')->find($picture_id);
  3.   unlink(getcwd().DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.$thePicture->filename);
  4.   $thePicture->delete();
  5.   header('location:/product/detail/'.$thePicture->product_id);
  6. }

The process is simply to find the Picture record using its primary key value, delete the associated file with the unlink instruction, and delete the picture record before redirecting back the appropriate page (here we go back to the product details.

The Picture Model and Database

To support all these operations, we need a Picture model similar to the following:

  1.  
  2. class Picture extends Model{
  3.   var $product_id;
  4.   var $filename;
  5.   var $description;
  6.  
  7.   public function getForProduct($product_id){
  8.     $SQL = 'SELECT * FROM Picture where product_id = :product_id';
  9.     $stmt = self::$_connection->prepare($SQL);
  10.     $stmt->execute(['product_id'=>$product_id]);
  11.     $stmt->setFetchMode(PDO::FETCH_CLASS, 'Picture');
  12.     return $stmt->fetchAll();
  13.   }
  14.  
  15.   public function create(){
  16.     $SQL = 'INSERT INTO Picture(product_id,filename,description) VALUE(:product_id,:filename,:description)';
  17.     $stmt = self::$_connection->prepare($SQL);
  18.     $stmt->execute(['product_id'=>$this->product_id,'filename'=>$this->filename,'description'=>$this->description]);
  19.     return $stmt->rowCount();
  20.   }
  21.  
  22.   public function find($picture_id){
  23.     $SQL = 'SELECT * FROM Picture WHERE picture_id = :picture_id';
  24.     $stmt = self::$_connection->prepare($SQL);
  25.     $stmt->execute(['picture_id'=>$picture_id]);
  26.     $stmt->setFetchMode(PDO::FETCH_CLASS, 'Picture');
  27.     return $stmt->fetch();
  28.   }
  29.  
  30.   public function update(){
  31.     $SQL = 'UPDATE Picture SET filename = :filename,description = :description WHERE picture_id = :picture_id';
  32.     $stmt = self::$_connection->prepare($SQL);
  33.     $stmt->execute(['filename'=>$this->filename,'description'=>$this->description,'picture_id'=>$this->picture_id]);
  34.     return $stmt->rowCount();
  35.   }
  36.  
  37.   public function delete(){
  38.     $SQL = 'DELETE FROM Picture WHERE picture_id = :picture_id';
  39.     $stmt = self::$_connection->prepare($SQL);
  40.     $stmt->execute(['picture_id'=>$this->picture_id]);
  41.     return $stmt->rowCount();
  42.   }
  43. }

This model performs operations on a Picture table with the following fields:

  • picture_id: the automatically incremented integer primary key
  • product_id: the foreign key to the product table
  • filename: a string type to hold the short filename
  • description: a text type field to hold long descriptions.

Conclusion

These general guidelines are also applicable to records that are child records of a master-detail relationship. You could change the upload types by modifying the upload whitelist and replacing getimagesize with other validation mechanisms.

Categories
MVC PHP

Filtering Access to Controller Classes and Methods (before PHP 8)

A feature of good routing is the possibility to enforce policies that get specified for Controller classes and methods. For this purpose, frameworks such as ASP.NET use annotations on classes and methods. We wish to replicate this in PHP, but PHP does not support annotations.

phpDoc Comments

phpDoc comments are the official means to document class and function API in PHP. These comments are delimited by /** and */ character sequences (note the opening tag has 2* characters. The usual contents should include method parameters, return values, summaries, etc. We aim to add our own annotation to this phpDoc which we will in turn use to filter user access.

Consider a simple controller class with phpDoc comments as below:

  1. <?php
  2. /**
  3. HomeController will handle operations related to Item entities.
  4. @accessFilter{LoginFilter}
  5. */
  6. class HomeController extends Controller{
  7. /**
  8. Provide a listing of the Item entities
  9. */
  10.   public function index(){
  11.     //...
  12.   }
  13.  
  14.   public function create(){
  15.     //...
  16.   }
  17. /** 
  18. @accessFilter{itemOwner}
  19. */
  20.   public function detail($item_id){
  21.     //...
  22.   }
  23. /**
  24. @accessFilter{itemOwner}
  25. */
  26.   public function edit($item_id){
  27.     //...
  28.   }
  29. /**
  30. @accessFilter{itemOwner}
  31. */
  32.   public function delete($item_id){
  33.     //...
  34.   }
  35. }
  36. ?>

In the above, we wish to apply the LoginFilter access policy to the entire class and the itemOwner policy only to Detail, Edit, and Delete methods.

ReflectionClass

For our purposes, phpDoc comments would serve no purpose without the means to efficiently extract them programatically. For this purpose, we use the ReflectionClass class, which provides an interface to all information about a selected class and its methods. Below, we extract the phpDoc comments of the class and each one of the methods:

  1. <?php
  2. $reflection = new ReflectionClass('HomeController');
  3. $classDoc = $reflection->getDocComment();
  4. $indexComment = $reflection->getMethod('index')->getDocComment();
  5. $createComment = $reflection->getMethod('create')->getDocComment();
  6. $detailComment = $reflection->getMethod('detail')->getDocComment();
  7. $editComment = $reflection->getMethod('edit')->getDocComment();
  8. $deleteComment = $reflection->getMethod('delete')->getDocComment();
  9. ?>

More specifically for our purposes, we need to extract the phpDoc comments for the specific controller and method resolved in the routing algorithm. (If you have been reading this site, I place this functionality in the App class constructor.)

Below is an example of how, given a class, method, and parameters, we should extract, parse, and resolve the filters.

  1. private static function redirectFilters($class,$method, $params){
  2.   $reflection = new ReflectionClass($class);
  3.  
  4.   $classDocComment = $reflection-&gt;getDocComment();
  5.   $methodDocComment = $reflection-&gt;getMethod($method)-&gt;getDocComment();
  6.  
  7.   //parse and extract the filters
  8.   $classFilters = self::getFiltersFromAnnotations($classDocComment);
  9.   $methodFilters = self::getFiltersFromAnnotations($methodDocComment);
  10.  
  11.   $filters = array_values(array_filter(array_merge($classFilters,$methodFilters)));
  12.  
  13.   $redirect = self::runFilters($filters, $params);
  14.   return $redirect;
  15. }

First, we initialize the ReflectionClass instance for this controller class and then extract the phpDoc comments for the class and method at hand. Then, we parse the phpDoc comment to retrieve any filter annotations and assemble these from the class and method in a single array. Then we run the filters and return the first redirection URL obtained, false otherwise. So the right time to call this method is right before calling the method on the controller class, as follows:

  1. if($redirectUrl = self::redirectFilters($this-&gt;controller, $this-&gt;method, $this-&gt;params)){
  2.   header("location:$redirectUrl");
  3.   return;
  4. }
  5.  
  6. call_user_func_array([$this-&gt;controller, $this-&gt;method], $this-&gt;params);

For any of this to work, of course, we must actually parse the phpDoc comments to extract the filter information, run the filters, and especially have filters to run in our code.

Parsing the phpDoc Comment

We must extract information from the phpDoc comments which resemble the following:

/**
Summary.
@accessFilter{filter1,filter2, filter3}
*/

For this purpose, we propose a method as follows:

  1. private static function getFiltersFromAnnotations($docComment){
  2.   preg_match('/@accessFilter:{(?<content>.+)}/i', $docComment, $content);
  3.   $content = (isset($content['content'])?$content['content']:'');
  4.   $content = explode(',',str_replace(' ', '', $content));
  5.   return $content;//this is an array
  6. }

On line 2, we extract all the accessFilter contents as elements in the $content array, with the key ‘content‘.

On line 3, we set $content to the value associated to the ‘content‘ key in $content, if any, or an empty string otherwise.

On line 4, we convert the string to an array of strings, one element per filter, without space characters.

Running the Filters

First and foremost, we must have filters to run. It is proposed to group these either in your routing class or a separate Filter class, as follows:

  1. <?php
  2. <?php
  3. class Filter extends Controller{
  4.   public static function itemOwner($params){
  5.     $theItem = self::model('Item')->find($params[0]);
  6.     if($theItem->user_id != $_SESSION['user_id']){
  7.       return '/home/index';
  8.     }else{
  9.       return false;
  10.     }
  11.   }
  12.   public static function LoginFilter($params){
  13.     if($_SESSION['user_id'] == null){
  14.       return '/login/index';
  15.     }else{
  16.       return false;
  17.     }		
  18.   }
  19. }
  20. ?>

Each of the above methods will check conditions based on the session variables, parameters, or core functionality. For example, LoginFilter verifies if a user is logged in and returns a redirection URL otherwise, or false if the user need not be redirected. The function itemOwner compares the user id of the user logged in with the user id in the record to verify if the current user is not attempting to modify someone else’s data.

The strategy to run the filters is to run them one by one until a redirection is determined or all have been run. We then return the redirection to the calling method which will send it to the routing algorithm.

  1. private static function runfilters($filters,$params){
  2.   $redirect = false;
  3.   $max = count($filters);
  4.   $i = 0;
  5.   while(!$redirect && $i < $max){
  6.     if(method_exists('Filter', $filters[$i])){
  7.       $redirect = Filter::{$filters[$i]}($params);
  8.     }else{
  9.       throw new Exception("No policy named $filters[$i]");
  10.     }
  11.     $i++;
  12.   }
  13.   return $redirect;
  14. }

Conclusion

All the pieces are in place. We can now implement redirection policies and apply them to classes and methods with a simple phpDoc comment. Note that there should only be ONE phpDoc comment per class and per method, which integrates all relevant annotations, including ours: @accessFilter. Otherwise, the ReflectionClass method getDocComment will only be able to return the one before and closest to the class or method declaration.

Categories
MVC PHP

Intro to PDO

PDO – PHP Data Objects – is a database access layer providing a uniform method of access to multiple database management systems. PDO does not account for database-specific syntax, but can allow for the process of switching databases and platforms to be fairly painless, simply by switching a connection string.

PDO - db abstraction layer

Database Support

The extension can support any database for which a PDO driver has been written. To find out which drivers you have, run the following command:

  1. print_r(PDO::getAvailableDrivers());

Connecting

Different databases may have slightly different connection methods. Below, the method to connect to MySQL databases is shown:

  1. $host = 'localhost';
  2. $dbname = 'application';
  3. $user = 'app_account';
  4. $pass = 'app_account_pass';
  5. try {   # MySQL with PDO_MYSQL
  6.   $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
  7. }
  8. catch(PDOException $e) {
  9.     echo $e->getMessage();
  10. }

Take note of the try/catch block – you should always wrap your PDO operations in a try/catch, and use the exception mechanism. $DBH stands for ‘database handle’.

You can close any connection by setting the handle to null.

  1. # close the connection
  2. $DBH = null;

You can get more information on database-specific options and/or connection strings for other databases from PHP.net.

Exceptions and PDO

PDO can use exceptions to handle errors, which means anything you do with PDO should be wrapped in a try/catch block. You can force PDO into one of three error modes by setting the error mode attribute on your newly created database handle. Here’s the syntax:

  1. $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
  2. $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
  3. $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

No matter what error mode you set, an error connecting will always produce an exception, and creating a connection should always be contained in a try/catch block.

PDO::ERRMODE_SILENT

This is the default error mode. If you leave it in this mode, you’ll have to check for errors in the way you’re probably used to if you used the mysql or mysqli extensions. The other two methods are more ideal for DRY programming.

PDO::ERRMODE_WARNING

This mode will issue a standard PHP warning, and allow the program to continue execution. It’s useful for debugging.

PDO::ERRMODE_EXCEPTION

This is the mode you should want in most situations. It fires an exception, allowing you to handle errors gracefully and hide data that might help someone exploit your system. Here’s an example of taking advantage of exceptions:

  1. # connect to the database
  2. try {
  3.   $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
  4.   $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
  5.   $DBH->prepare('DELECT name FROM people');
  6.   # Typed DELECT instead of SELECT!
  7. }
  8. catch(PDOException $e) {
  9.   echo "Error!";
  10.   file_put_contents('PDOErrors.txt', $e->getMessage(), FILE_APPEND);
  11. }

There’s an intentional error in the select statement; this will cause an exception. The exception sends the details of the error to a log file, and displays a friendly (or not so friendly) message to the user.

Insert and Update

Inserting new data, or updating existing data is one of the more common database operations. Using PDO, this is normally a two-step process. Everything covered in this section applies equally to both UPDATE and INSERT operations.

2 to 3 step insert and update

Here’s an example of the most basic type of insert:

  1. # STH means "Statement Handle"
  2. $sql = "INSERT INTO person(first_name) VALUES('Alice')";
  3. $stmt = $DBH->prepare($sql);
  4. $stmt->execute();

You could also accomplish the same operation by using the exec() method, with one less call. In most situations, you’re going to use the longer method so you can take advantage of prepared statements. Even if you’re only going to use it once, using prepared statements will help protect you from SQL injection attacks.

Prepared Statements

Using prepared statements will help protect you from SQL injection. A prepared statement is an SQL statement that can be executed multiple times just by changing the data and running the statement. It has the added advantage of automatically making the data used in the placeholders safe from SQL injection attacks, as part of the preparation. You use a prepared statement by including placeholders in your SQL. Here are three examples: one without placeholders, one with unnamed placeholders, and one with named placeholders.

  1. # no placeholders - vulnerable to SQL Injection!
  2. $STH = $DBH->prepare("INSERT INTO person(first_name, addr, city) VALUES($name, $addr, $city)");
  3. # unnamed placeholders
  4. $STH = $DBH->prepare("INSERT INTO person(first_name, addr, city) VALUES(?, ?, ?)");
  5. # named placeholders
  6. $STH = $DBH->prepare("INSERT INTO person(first_name, addr, city) VALUES(:name, :addr, :city)");

You want to avoid the first method; it’s here for comparison. The choice of using named or unnamed placeholders will affect how you set data for those statements.

Unnamed Placeholders

Hint: read this only to understand how trrible this technique actually is. For a better technique see the following sections.

  1. # unnamed placeholders
  2. $stmt = $DBH->prepare("INSERT INTO person(first_name, addr, city) VALUES(?, ?, ?)");
  3.  
  4. # assign variables to each place holder, indexed 1-3
  5. $stmt->bindParam(1, $first_name);
  6. $stmt->bindParam(2, $address);
  7. $stmt->bindParam(3, $city);
  8.  
  9. # insert one row
  10. $first_name = "Alice"
  11. $address = "123 Elm";
  12. $city = "Montreal";
  13. $stmt->execute();
  14.  
  15. # insert another row with different values
  16. $first_name = "Bob"
  17. $address = "124 Elm";
  18. $city = "Montreal";
  19. $stmt->execute();

There are two steps here. First, we assign variables to the various placeholders (lines 2-4). Then, we assign values to those placeholders and execute the statement. To send another set of data, just change the values of those variables and execute the statement again.

Does this seem a bit unwieldy for statements with a lot of parameters? It is. However, if your data is stored in an array, there’s an easy shortcut:

  1. # the data we want to insert
  2. $data = ['Cathy', '125 Elm', 'Montreal'];
  3. $stmt = $DBH->prepare("INSERT INTO person(first_name, addr, city) VALUES(?,?,?);
  4. $stmt->execute($data);

The data in the array applies to the placeholders in order. $data[0] goes into the first placeholder, $data[1] the second, etc. However, if your array indexes are not in order, this won’t work properly, and you’ll need to re-index the array.

Named Placeholders

You could probably guess the syntax, but here’s an example:

  1. # the first argument is the named placeholder name - notice named
  2. # placeholders always start with a colon.
  3. $stmt->bindParam(':name', $name);

You can use a shortcut here as well, but it works with associative arrays. Here’s an example:

  1. # the data we want to insert
  2. $data = ['first_name'=>'Cathy', 'address'=>'125 Elm', 'city'=>'Montreal']; 
  3. # the shortcut!
  4. $stmt = $DBH->prepare("INSERT INTO person(first_name, address, city) value (:first_name, :address, :city)");
  5. $stmt->execute($data);

The keys of your array do not need to start with a colon, but otherwise need to match the named placeholders. If you have an array of arrays you can iterate over them, and simply call the execute with each array of data.

Another nice feature of named placeholders is the ability to insert objects directly into your database, assuming the properties match the named fields. Here’s an example object, and how you’d perform your insert:

  1. # a simple object
  2. class person {
  3.   public $first_name;
  4.   public $address;
  5.   public $city;
  6.  
  7.   function __construct($fname,$addy,$city){
  8.     $this->first_name = $fname;
  9.     $this->address = $addy;
  10.     $this->city = $city;
  11.   }
  12.   # etc ...
  13. }
  14.  
  15. $newPerson = new person('Johnny','125 Elm','Montreal');
  16. # here's the fun part:
  17. $stmt = $DBH->prepare("INSERT INTO person(first_name, address, city) VALUE(:first_name, :address, :city)");
  18. $stmt->execute((array)$newPerson);

By casting the object to an array in the execute, the attribute names are mapped to keys and the values are set from each attribute.

Selecting Data

Fetch data into arrays or objects

Data is obtained via the ->fetch(), a method of your statement handle. Before calling fetch, it’s best to tell PDO how you’d like the data to be fetched. You have the following options:

  • PDO::FETCH_ASSOC: returns an array indexed by column name
  • PDO::FETCH_BOTH (default): returns an array indexed by both column name and number
  • PDO::FETCH_BOUND: Assigns the values of your columns to the variables set with the ->bindColumn() method
  • PDO::FETCH_CLASS: Assigns the values of your columns to properties of the named class. It will create the properties if matching properties do not exist
  • PDO::FETCH_INTO: Updates an existing instance of the named class
  • PDO::FETCH_LAZY: Combines PDO::FETCH_BOTH/PDO::FETCH_OBJ, creating the object variable names as they are used
  • PDO::FETCH_NUM: returns an array indexed by column number
  • PDO::FETCH_OBJ: returns an anonymous object with property names that correspond to the column names

In reality, there are three which will cover most situations: FETCH_ASSOC, FETCH_CLASS, and FETCH_OBJ. In order to set the fetch method, the following syntax is used:

  1. $stmt->setFetchMode(PDO::FETCH_ASSOC);

You can also set the fetch type directly within the ->fetch() method call.

FETCH_ASSOC

This fetch type creates an associative array, indexed by column name. This should be quite familiar to anyone who has used the mysql/mysqli extensions. Here’s an example of selecting data with this method:

  1. # can use the query() method when no input in the statement
  2. $tmt = $DBH->query('SELECT first_name, address, city from person');
  3. # setting the fetch mode
  4. $stmt->setFetchMode(PDO::FETCH_ASSOC);
  5. while($rec = $stmt->fetch()) {
  6.   echo $rec['first_name'] . "<br>";
  7.   echo $row['address'] . "<br>";
  8.   echo $row['city'] . "<br>";
  9. }

The while loop will continue to go through the result set one row at a time until complete.

FETCH_OBJ

This fetch type creates an object of std class for each row of fetched data. Here’s an example:

  1. # creating the statement
  2. $stmt = $DBH->query('SELECT first_name, address, city from person');
  3. # setting the fetch mode 
  4. $stmt->setFetchMode(PDO::FETCH_OBJ);
  5. # showing the results
  6. while($row = $stmt->fetch()) {
  7.   echo $row->first_name . "<br>";
  8.   echo $row->address . "<br>";
  9.   echo $row->city . "<br>";
  10. }

FETCH_CLASS

Important note: when using this fetch mode, the properties of your object are set BEFORE the constructor is called. Therefore, you can use the constructor to modify and hide the data as required for your application.

This fetch method allows you to fetch data directly into a class of your choosing. When you use FETCH_CLASS, the properties of your object are set before the constructor is called. Read that again, it’s important. If properties matching the column names don’t exist, those properties will be created (as public) for you.

This means if your data needs any transformation after it comes out of the database, it can be done automatically by your object as each object is created.

As an example, imagine a situation where the address needs to be partially obscured for each record. We could do this by operating on that property in the constructor. Here’s an example:

  1. class obscure_person {
  2.   public $first_name;
  3.   public $address;
  4.   public $city;
  5.   public $notes;
  6.   function __construct($notes = ''){
  7.     $this->address = preg_replace('/[a-z0-9]/i', '*', $this->address);
  8.     $this->notes = $notes;
  9.   }
  10. }

As data is fetched into this class, the address has all its lowercase a-z letters and digits 0-9 replaced by the character *. Now, using the class and having that data transformation occur is completely transparent:

  1. $stmt = $DBH->query('SELECT first_name, address, city from person');
  2. $stmt->setFetchMode(PDO::FETCH_CLASS, 'obscure_person');
  3. while($obj = $STH->fetch()) {
  4.   echo "Obscure $obj->first_name lives at $obj->address";
  5. }

If the address was ‘123 Elm,’ you’d see ‘*** ***’ as your output. Of course, there may be situations where you want the constructor called before the data is assigned. PDO can also accommodate this by adding PDO::FETCH_PROPS_LATE as follows:

  1. $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'obscure_person');

Now, when you repeat the previous example with this fetch mode the address will NOT be obscured, since the constructor was called before the properties were assigned.

Finally, if you really must, you can pass arguments to the constructor when fetching data into objects with PDO:

  1. $stmt->setFetchMode(PDO::FETCH_CLASS, 'secret_person', ['data']);

If you must pass different data to the constructor for each object, you can set the fetch mode inside the fetch method:

  1. $i = 0;
  2. while($rowObj = $stmt->fetch(PDO::FETCH_CLASS, 'obscure_person', [$i])) {
  3.   // operations
  4.   $i++;
  5. }

Some Other Helpful Methods

While this isn’t meant to cover everything in PDO (it’s a huge extension!) there are a few more methods you’ll want to know in order to do basic things with PDO.

  1. $DBH->lastInsertId();

The lastInsertId() method of PDO (not PDOStatement) will return the last inserted primary key value for this connection.

  1. $DBH->exec('DELETE FROM person WHERE 1');
  2. $DBH->exec("SET time_zone = '-8:00'");

The exec() method is used for operations that cannot return data other than the number of affected rows.

  1. $safe = $DBH->quote($unsafe);

The quote() method quotes strings so they are safe to use in queries. This is your fallback if you’re not using prepared statements.

  1. $rows_affected = $stmt->rowCount();

The rowCount() method returns an integer indicating the number of rows affected by an operation.

Categories
HTML MVC PHP

Deeper in Models

In this post, we will explore more database transactions in model classes with the previous Item class example. We will also create views that can support these operations.

SQL DML (Data Modification Language) are these operations that we must implement in our model classes. They comprise all CRUD (Create, Read, Update, Delete) operations, i.e., respectively INSERT, SELECT, UPDATE, and DELETE.

Create

We add a create method to the Item model class as follows:

public function create(){
  $SQL = 'INSERT INTO Item(name) VALUES (:name)';
  $stmt = self::$_connection->prepare($SQL);
  $stmt->execute(['name'=>$this->name]);
  return self::$_connection->lastInsertId();
}

In the above, we first define the SQL command we will use, with a named placeholder, :name. We then get PDO to prepare the statement; the use of prepared statements has many advantages including reusability and SQL-injection prevention (this happens later).

Next, we run the prepared statement with the execute method. The parameter is an array ([...]) containing a dictionary of values for this statement, i.e., if there is a :name placeholder in the SQL, then there must be a name key-value pair in this dictionary. The execute method will take all this data and sanitize it, preventing any special character that could be used to perform SQL injection. In other words, if you are using prepared statements in all your models such that all data is passed into the statements through the execute method, then you have been diligent in preventing SQL injection.

The final step in the create method is to return the primary key value assigned to the created record. This value is fetched by PDO’s lastInsertId() method.

We now focus on the view used to create this record. We create a view, /app/view/home/create.php that will be used for data entry by the user, as follows:

<html>
  <head><title>Create an Item</title></head>
  <body>
    <h1>Create an Item</h1>
    <form action='' method='post'>
      <label>Name:<input type='text' name='name' /></label><br />
      <input type='submit name='action' value='Create' /> <a href='/home/index'>Cancel</a>
    </form>
  </body>
</html> 

The form is submitted to the same URL from which it was called. This way, we can write the instructions to call the view and to add the record in the same controller method in HomeController:

public function create(){
  if(isset($_POST['action'])){
    $newItem = $this->model('Item');
    $newItem->name = $_POST['name'];
    $newItem->create();
    header('location:/Home/index');
  }else{
    $this->view('home/create');
  }
}

The create method is written in two branches, the first if the submit button named action is pressed. We instantiate an Item object and initialize its name attribute (its only attribute) before calling the create() method to store it. Finally, we send the browser headers to redirect to the /Home/index URL for the application, which will display the list of items. The user can also navigate back to the list of items by blicking Cancel.

Read

We now focus on a detail-view feature for the records. Bear in mind that this is especially useful in cases where records hold many fields. We first add a find(…) method to the model class:

public function find($item_id){
  $SQL = 'SELECT * FROM Item WHERE item_id = :item_id';
  $stmt = self::$_connection->prepare($SQL);
  $stmt->execute(['item_id'=>$item_id]);
  $stmt->setFetchMode(PDO::FETCH_CLASS, 'Item');
  return $stmt->fetch();
}

In contrast with the get() method we wrote in An Introduction to Models, the SQL includes a WHERE clause to choose the record based on the primary key value and this value is passed in the call to the execute() method. Finally, the is returned after a call to fetch() such that the single matching record is returned as an object, without being placed in an array.

To display the record, we build a detail view, /app/views/home/detail.php which should include all record fields in the display:

<html>
  <head><title>Item Details</title></head>
  <body>
    <h1>Item Details</h1>
    <dl>
      <dt>Name:</dt>
      <dd><?=$data->name ?></dd>
    </dl>
<a href='/home/index'>Back to list</a>
  </body>
</html> 

Above, we are assuming the single record is passed to the view and that the name attribute is displayed by the <?=expression ?> tags which are shorthand for <?php echo expression; ?>.

We complete this part with method detail in HomeController as follows:

public function detail($item_id){
  $theItem = $this->model('Item')->find($item_id);
  $this->view('home/detail', $theItem);
}

On the first line, we invoke the method to find the wanted record and store it in $theItem, which is then passed to the newly-created view on the following line.

Update

Another important part of a data-centric Web application is the ability to modify data. In a MySQL database, this is done through UPDATE SQL statements, as in the following update method added to the Item model.

public function update(){
  $SQL = 'UPDATE Item 
          SET name = :name 
          WHERE item_id = :item_id';
  $stmt = self::$_connection->prepare($SQL);
  $stmt->execute(['name'=>$this->name,
                  'item_id'=>$this->item_id]);
  return $stmt->rowCount();
}

First we write a proper SQL UPDATE statement with named placeholders where the data is meant to go. Then we prepare the statement and execute it with the needed data, from the object. Finally, we return the rowCount as feedback about how many rows were modified.

The view to support this process must allow data entry from the user AND have the data entry fields prepopulated with the existing record field values. We add the view /app/views/home/edit.php as follows:

<html>
  <head><title>Edit an item</title></head>
  <body>
    <h1>Edit an item</h1>
    <form action='' method='post'>
      <label>Name:<input type='text' name='name'
           value='<?= $data->name ?>' /></label><br />
      <input type='submit name='action' value='Save' /> <a href='/home/index'>Cancel</a>
    </form>
  </body>
</html> 

For the view to work properly, it must receive the record as its $data parameter. We put all this together in the HomeController edit method, as follows:

public function edit($item_id){
  $theItem = $this->model('Item')->find($item_id);
  if(isset($_POST['action'])){
    $theItem->name = $_POST['name'];
    $theItem->update();
    header('location:/Home/index');
  }else{
    $this->view('home/edit', $theItem);
  }
}

This method is very similar to create, in that it handles displaying a form, processes the submitted data, and redirects back to the list of all items. The distinctions are that edit operates on an existing record and thus must find and present it in the view prior to calling for updates with the user input. Naturally, if a user clicks the Cancel hyperlink, they navigate back to the index method, so this case need not be addressed here.

Delete

To complete the basic data management for the Item table, we must have the ability to delete records. We will implement the Item model class delete method as follows:

public function delete(){
  $SQL = 'DELETE FROM Item WHERE item_id = :item_id';
  $stmt = self::$_connection->prepare($SQL);
  $stmt->execute(['item_id'=>$this->item_id]);
  return $stmt->rowCount();
}

It is easy to also implement this method to take the item_id as a parameter and forego using the actual object attributes.

The pattern to process this request is familiar by now, but in a nutshell: define the DELETE statement with an :item_id placeholder; prepare the statement; execute the statement with the data to replace the placeholders; and return the number of rows affected by the change.

To make this process go smoothly, there are two possibilities: with or without a deletion confirmation view. We write a deletion confirmation view /app/views/home/delete.php as follows:

<html>
  <head><title>Delete an item</title></head>
  <body>
    <h1>Delete an item</h1>
    <form action='' method='post'>
      <label>Name:<input type='text' name='name'
        value='<?= $data->name ?>' disabled />
        </label><br />
      <input type='submit name='action' value='Delete' /> <a href='/home/index'>Cancel</a>
    </form>
  </body>
</html> 

So the user will click on the Delete button to confirm deletion and the Cancel link to cancel the deletion.

To complete the deletion implementation, we add the delete method to the HomeController as follows:

public function delete($item_id){
  $theItem = $this->model('Item')->find($item_id);
  if(isset($_POST['action'])){
    $theItem->delete();
    header('location:/Home/index');
  }else{
    $this->view('home/delete', $theItem);
  }
}

In the above, we only handle the cases when the method is called with or without the action button being clicked. These cases happen upon submitting the form and navigating to delete, respectively.

Tying it All Together

The only aspect missing from this application is the set of hyperlinks that allow navigation. Since all views have links back to /home/index and since all methods redirect to /home/index as well, the links to go to access create, detail, update, and delete methods must be implemented in the view for the index method of the HomeController class; we modify it as follows:

<html>
<head><title>Item List</title></head>
<body>
<h1>List of items</h1>
<a href='/home/create/'>Add an item</a>
<table>
<tr><th>Name</th><th>Actions</th></tr>
<?php
  foreach($data['items'] as $item){
    echo "<tr><td>$item->name</td><td>
      <a href='/home/detail/$item->item_id'>Details</a>
      <a href='/home/edit/$item->item_id'>Modify</a>
      <a href='/home/delete/$item->item_id'>Delete</a>
      </td></tr>";
  }
?>
</table>
</body>
</html>

Notice the hyperlinks all lead to URLs which will be used to invoke controller methods with their parameters. The controller methods will, in turn, call the appropriate views and process the responses to finally redirect as appropriate.

Categories
MVC PHP

An Introduction to Models

To complete the PHP MVC trio, we must take a look at Model classes. Model classes provide data access and modification functionality to MVC applications, regardless of what the data source is. In other words, the way that the Model classes are used should not reveal to the Controller classes what type of data storage medium is being used. All we wish to provide to the Controller classes is an interface to create, read, delete, and update information. And, by default, our Model classes will perform these CRUD operations in databases.

A Model Base Class

As was done with the Controller base class to provide core functionality to Controller classes, we wish to provide the base functionality to Model classes through a Model base class. In our case, this base functionality will be to connect to a MySQL-compatible database. So, to /app/core, we add Model.php:

class Model{
  protected static $_connection = null;
  public function __construct(){
    if(self::$_connection == null){
      $user = 'root';
      $password = '';
      $host = 'localhost';
      $DBName = 'test';
      self::$_connection =  new PDO("mysql:host=$host;dbname=$DBName", $user, $password);
    }
  }
}

In the above, we declare and initialize a static attribute $_connection. Static attributes are attributes of the class; only one instance of this attribute exists for the entire set of all objects in this class, i.e., one $_connection for all objects deriving from class Model. Then, one the first run of the constructor for the class, when $_connection is null, it is set to a PDO object connected to a MySQL database, called test, hosted on localhost (the same computer as the one running the PHP, which is your computer if you are running XAMPP locally). The connection is done through the ‘root’ user which has no password, by default, in the XAMPP MySQL installation. You should definitely change these settings!

Let’s not forget to adjust dependencies… modify your app/init.php file to add the new Model class:

<?php
  require_once 'core/App.php';
  require_once 'core/Controller.php';
  require_once 'core/Model.php';
?>

Building a Model Subclass

For each table in our database, we will wish to build one Model class. We say that these are Model classes, and this is semantically true. In this specific example, they are effectively subclasses of our Model class. To continue our example, we build a model for Item in the Item.php file in the /app/models folder:

class Item extends Model{
  var $name;
  public function get(){
    $SQL = 'SELECT * FROM ITEM';
    $stmt = self::$_connection->prepare($SQL);
    $stmt->execute();
    $stmt->setFetchMode(PDO::FETCH_CLASS, 'Item');
    return $stmt->fetchAll();
  }
}

In the Item model, we define a $name attribute, such that all objects of this class have this property. Then, the get() method is declared to run a SELECT * FROM ITEM query in the database. The query is defined, then prepared (this will become more useful further along as you read), to then be executed. Then, since SELECT is a read command, there will be records to fetch, we set the fetch mode to return objects of the Item class (this class). Finally, we return an array of Item objects, each representing the records read from the database.

The Database to Match

To complete this example, we must create an Item table in our test database, and have the records have item_id (for instance) and name fields. To accomplish this, go back to your xampp control panel, if this is what you are using, and on the MySQL line, click Start and, once MySQL is highlighted green, click Admin; this will open an instance of phMyAdmin.

If you have a test database in the left menu, click on it (otherwise, you certainly know how to add one). You should be prompted to add a table, if not, expand the test database in the left structure and click on New table.

At the top left, name the table Item. Let’s add the item_id field first by typing that name in the Name column and selecting the checkbox in the A_I colunm (if anything pops up, just click OK). Now let’s add the name of the item by adding name on the following line’s Name column, in the type column, select VARCHAR, in the Size colunm, enter the value 50. Click Save at the bottom right.

Now let’s add test data to the table. Click the Insert tab in the top tabs row. For example, insert ‘milk’ and ‘cookies’ in the Value column for the name field. Click one of the execute buttons on the right.

Making it Work

If you followed along with the posts and implemented the HomeController class as in Controller Classes and An Introduction to Views, all script elements can be called through your browser at http://localhost/ and display your items from the database Item table.

Categories
MVC PHP

An Introduction to Views

MVC application output is defined in views. Four our PHP MVC application, these views are defined as .php files which mainly contain HTML/CSS/JavaScript code and some PHP to output model elements as required.

It is important to note here that the output formats depend on the application we are trying to build. For example, some applications, APIs, will output JSON data only and leave it up to a separate front-end to render the UI.

Going back to our example, let’s take a look at a simple view, itemList.php, stored in the /app/views/home/ folder:

<html>
<head><title>Item List</title></head>
<body>
<h1>List of items</h1>
<ul>
<?php
  foreach($data['items'] as $item){
    echo "<li>$item->name</li>";
  }
?>
</ul>
</body>
</html>

Much of the above view is directly output HTML code. However, the code between <?php ... ?> generates one <li>...</li> element per item in the $data[‘items’] collection passed to the view by the controller. The foreach loop is one of the most often used repetition instructions in PHP Views. For more information on all that can be done to produce correct output, refer to Hello PHP! and PHP: Getting Acquainted.

The main concern addressed by the Views is to format the data output as per the requirements for the application. This formatting is not a concern that the Model or Controller classes share. Concerns are task categories that should be handled by different classes when the separation of concerns is done correctly.

Tying this to the previous examples

Consider the following controller calling the above-defined view in its index method:

class HomeController extends Controller{
public function index(){
$items = $this->model('item')->get();
$this->view('home/itemList',['items'=>$items])
}
}

The above gets data from the storage and access interface through the call to $this->model('item')->get(). We assume item elements contain a name attribute. Then, this data is passed to the view on the following line. Notice how the view name is the folder and file name (less the .php) from the /app/views/ folder.

In the next post, we will look at building a model base class and model item class to match our example.

Categories
MVC PHP

Controller classes

In MVC applications, HTTP requests lead to Controller method invocation (a.k.a. actions). It is inside controller methods that we define the application logic of an MVC application. In other words, this is where we define much of the application flow, user experience, features, etc.

The Controller base class

Given the orchestration role given to controllers, it will be their concern to invoke actions on Models (the data-handling part of the app) and call Views (the app output). We write the Controller base class to enable these methods.

Method model

Assume all model classes are contained in the /app/models folder in files with their class name.php. Furthermore, the program entry point is the index.php file in the document root.

protected function model($model){
  if(file_exists('app/models/' . $model . '.php')){
    require_once 'app/models/' . $model . '.php';
    return new $model();
  }else
    return null;
}

In the above, we check that the file exists and then require it to finally return a new object of this type. If the class definition does not exist, we return null. For the calling method, this will result in an object of the proper type to call the data processing methods.

Method view

The view method is invoked whenever the application is to produce output.

Assume all view files are located in the /app/views folder in files with their name.php. The program entry point is the index.php file in the document root.

protected function view($name, $data = []){
  if(file_exists('app/views/'.$name.'.php')){
    include('app/views/'.$name.'.php');
  } else {
    echo "ERROR: View $view not found!";
  }
}

This method checks the existence of a file and then includes it in order to produce the output defined in this file. If the file does not exist, it simply provides feedback to help the developer fix this error.

The complete Controller class

To complete the class, we simply assemble the two methods and wrap them in a class definition as follows:

class Controller{
  protected function model($model){
    if(file_exists('app/models/' . $model . '.php')){
      require_once 'app/models/' . $model . '.php';
      return new $model();
    }else
      return null;
  }
  
  protected function view($name, $data = []){
    if(file_exists('app/views/'.$name.'.php')){
      include('app/views/'.$name.'.php');
    } else {
      echo "ERROR: View $view not found!";
    }
  }
}

We will now require the Controller base class and extend it in all of the application classes in the /app/controllers folder.

Example Controller

We now explore the code for an example controller that will extend the Controller base class and use the Controller methods to produce useful results.

class HomeController extends Controller{
  public function index(){
    $items = $this->model('item')->get();
    $this->view('home/itemList',['items'=>$items])
  }
}

Given the correct model and view implementations, the above could get all items from a database table and display them in a list output for the user to see and act upon.

Tying it all together

For the controller class to be invoked, it is necessary to make sure that all dependencies are loaded properly and that the program is properly bootstrapped. To take care of the dependencies, we add a file named init.php in the /app folder:

<?php
  require_once 'core/App.php';
  require_once 'core/Controller.php';
?>

and include it from /index.php as in the following:

<?php
  require_once 'app/init.php';
  new App();
?>

With this application completed, the above-defined controller action could be called from a URL such as https://cstutoring.ca/Home/index.

Categories
MVC PHP

Routing in MVC Applications

In this post, we will explore a very naive but effective way of routing HTTP requests in Web applications.

The process for this routing scheme is to

  1. Parse the URL into its components.
  2. Map the URL components onto a Controller class, method, and parameters, if these exist.

Let’s start with the method which will be used to parse the URL:

private function parseUrl(){
    if(isset($_GET['url'])){
        return $url = explode('/', filter_var(rtrim($_GET['url'], '/'), FILTER_SANITIZE_URL));
    }
}

Here, we assume that a Query String parameter, url, contains the URL request. The method

  • firstly verifies that this parameter exists,
  • removes any leading “/” character,
  • sanitizes the URL, and
  • separates what remains into components, using the “/” character.

The explode function returns an array which would then contain, for instance, if /Teacher/StudentList were called:

['Teacher','StudentList']

Mapping to the Proper Class and Method

In this example application, all controller classes have Controller as a suffix in their names, are all contained in the /app/controllers folder in files with their class name.php. Furthermore, the program entry point is the index.php file in the document root.

The routing is handled in a class named App, written in the file App.php in /app/core as follows:

class App{
  //This class contains controller, method, & params
  //attributes, initialized with default values.
  public function __construct(){
      $url = $this->parseURL();

      if(file_exists('app/controllers/' . $url[0] . 'Controller.php')){
          $this->controller = $url[0] . 'Controller';
          unset($url[0]);
      }

      require_once 'app/controllers/' . $this->controller . '.php';
      $this->controller = new $this->controller();

      if(isset($url[1])){
          if(method_exists($this->controller, $url[1])){
              $this->method = $url[1];
              unset($url[1]);
          }
      }
      $this->params = $url ? array_values($url) : [];

      call_user_func_array([$this->controller, $this->method], $this->params);
  }

  private function parseUrl(){
      if(isset($_GET['url'])){
          return $url = explode('/', filter_var(rtrim($_GET['url'], '/'), FILTER_SANITIZE_URL));
    }
  }

}

Once the URL has been parsed into an array, the two first elements are examined to see if they match a Controller class and a method within this controller class. Then the call is made to this method, with any components of the URL leftover in the array as parameters. The process is invoked then the App class is instantiated into an object.

Handling Dependencies

We will add a file where all core dependencies for the application will be listed. We will call this file init.php and place it in the /app folder:

<?php
  require_once 'core/App.php';
?>

Tying it all together

In order for the routing to happen, it is necessary to bootstrap the application with a .htaccess file and an index.php file as follows:

<?php
  require_once 'app/init.php';
  new App();
?>

For example, the URL /Teacher/StudentList would yield a call to the StudentList method of the TeacherController class, without any parameters.

Moreover, the URL /Student/message/Alice would yield a call to the message method of the StudentController class, with one parameter containing the value Alice.