-
Notifications
You must be signed in to change notification settings - Fork 0
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.
- 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 bymysql_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
- Frequent
- 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?
-
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");
?>
- 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
andmysql_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();
}
?>
- 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
- Define it in a file which will be "required" by all files who need to access this global variable. A good candidate is the
<?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 ;
...
?>
- 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 (theMore
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 callingdisplay_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 containsassignments.php
,lectures.php
, etc.
- 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
- Let
- 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)
- All
- 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 {...}
?>
- Increased configurability, e.g., swap the paginator types of assignments and lectures
- One just needs to swap the calls to
display_loadmore_paginator()
anddisplay_tab_paginator()
in their controllers
- One just needs to swap the calls to
- 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
- One just needs to call