Skip to content

PHP Refactoring and Model View Controller Pattern

yicui edited this page Apr 8, 2013 · 2 revisions

A sample project is created for this lecture note.

What’s Bad about our PHP Code so far?

  • Too much redundant code
<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    <div id="container">
      <div id="header">...</div>
      <div id="navigation">...</div>
    ...
    </div>
  </body>
</html>
  • The code appeared in the above tag pairs in each PHP file has little or no difference
  • MySQL statements scattered everywhere
    • Frequent mysql_connect followed by mysql_close as users navigate betweeen pages, which creates unnecessary load on database
    • An exhaustive search & replace is forced upon the change of database server password
    • Lengthy mysql_query statements: unless you're really familiar with the database schema, you can hardly make anything work
  • echo statements scattered everywhere
    • You must ensure the look & feel of your layout (e.g., accordion) to remain consistent across all pages
  • It would be nice to see some features become generic & interchangeable
    • Why can’t I freely switch tab-based pagination or loadmore pagination on any page?
    • How hard is it to add a new page or a new view template to the system?

Removing Code Redundancy

  • include, include_once, require, require_once
    • _once ensures that the PHP code contained within the file only appear once
  • If the file you "require" does not exist, PHP will abort
    • But if you only "include" the file, PHP will carry on even if the file is missing.
  • Extract the common header & footer code into separate files
    • "require" header since it’s very important to the presentability of your website
    • "include" footer because it has smaller importance
<?php
  require_once("header.php");
  include("footer.php");
?>

Refactoring Functionalities via Modularization

  • Use functions to factor out operations related to database
    • Common practice is to concentrate CRUD (create, read, update, delete) operations regarding one table into one PHP file
    • For example, we can introduce these functions to operations related with assignments: get_assignments_count, get_assignments, update_assignment, etc.
    • One only needs to call these functions without understanding the database table structures
    • We can further create a PHP file calling mysql_connect and mysql_select_db, and have all other database-related code to "require" this file
<?php
  $db = mysql_connect("localhost", "root", "12345");
  mysql_select_db("teaching", $db) or die();
?>
  • In this way, your password only shows once in the entire codebase, and you can change username or password without interfering the rest of the codebase
  • Introduce standard error reporting functions to replace the ad hoc die() statements
    • We can further categorize over error types, such as database error, user input error, file IO error, etc.
<?php
  function display_db_error($error_message) {
    echo '<div>' . PHP_EOL;
    echo '  <h1>Database Error</h1>' . PHP_EOL;
    echo '  <p>Error message: ' . $error_message . '</p>' . PHP_EOL;
    echo '</div>' . PHP_EOL;
    die();
  }
?>

Enhancing Configurability

  • Our codebase is generic enough to host any course, provided the course materials are properly populated into our database
  • But the keyword "CS292" appear in too many places, which is needed by all database operations
    • If you want to configure your website to host another course, you will have to search & replace all, which is risky & ugly
  • Use a global variable $course_num to store the keyword "CS292"
    • Define it in a file which will be "required" by all files who need to access this global variable. A good candidate is the database.php connecting to the database
    • Then you only need to reassign the value to $course_num once to make your website host another course
<?php
  $db = mysql_connect("localhost", "root", "12345");
  mysql_select_db("teaching", $db) or die();
  $course_num = "CS292";
?>
  • But you must remember to declare global to $course_num when calling it
<?php
  require_once("database.php");
  global $course_num ;
  ...
?>

Separating Data from its Presentation

  • Accordion and paginators are rather generic means to visualize your data
    • Either lectures or assignments can be shown in tab-based patinator or loadmore-paginator, and not necessarily always hidden in an accordion (maybe a grid)
  • Define View functions which focuses on creating DOM structure, regardless of what data it will host
    • display_element(): create any HTML element given its tag type, attributes, and text
    • display_composite_element(): create any HTML element with children, and children's children, etc.
    • display_loadmore_paginator(): An empty division followed by an anchor element (the More button) containing the URL for the loadmore action
    • display_tab_paginator(): create a division consists of a tab area with tabs each containing a URL to load records of different ranges, and a content area ready to contain and display these records
    • display_accordion: create an unordered list of items, each containing a <h3> header (for the title of the item) and a division (for the content of the item which can be conveniently created by calling display_composite_element())
  • See where we are going now? A Model-View-Controller (MVC) Framework
    • Conveniently, also how we manage our code directories
    • models directory contains *Model.php
    • views directory contains *View.php
    • controllers directory contains assignments.php, lectures.php, etc.

Some General Advice on Writing MVC Code

  • Make light controllers, because whenever there are changes or additions, controller code will be updated
    • In contrast, the model code and view code rarely change
    • Light controller doesn't necessarily mean small amount of code, because the functionality and featureset of your website keeps expanding !!
  • Push as much work as possible to Model and View
    • Let *Model.php handle input validation and translate query results from database-dependent format to generic format (e.g., associative array)
    • Likewise, let *View.php verify the data it is about to display and handle the jobs of generating HTML tags
  • An easy-to-remember guidance that shouldn't be strictly followed
    • All mysql* statements should appear in *Model.php (then you could switch a database that is not MySQL or any ORM (object-relation-mapping) model)
    • All echo statements should appear in *View.php (even error messages should be echoed by either model and view, but not controller)
  • Group similar user requests into the same controller differentiated by GET or POST parameters, a primitive form of request routing
    • e.g., below is the structure of the assignments controller
<?php
  // Load more assignments through the loadmore patinator
  if (isset($_GET["loadmore"])) {...}
  // Update assignments in the Edit-in-place fashion
  else if (isset($_GET["assignmenttitle"]) && isset($_GET["duedate"])) {...}
  // If no inputs, it should be the first visit to the page, set up the page framework 
  else {...}
?>

Benefits of Making Your Code MVC Style

  • Increased configurability, e.g., swap the paginator types of assignments and lectures
    • One just needs to swap the calls to display_loadmore_paginator() and display_tab_paginator() in their controllers
  • Great extensibility, e.g., adding a new view or new model without changing the existing codebase
    • display_loadmore_paginator() function creates a new grid view, which can be readily used by lectures, assignments, or any content
    • When the students table is introduced, we just need to write the model and controller code, and reuse any existing view functions to display student records
  • Openness to changes, e.g., decorating the students page with a Add a student button
    • One just needs to call display_element() to create the button regardless which view is used to display student records
Clone this wiki locally