This article details the specifics of a simple PHP based web application framework. There are many different architectural patterns that one can use to develop a web application framework, and for this framework I’ve chosen the Model-View-Controller (MVC) pattern.
This article is intended for consumption by beginner-level PHP developers who have limited experience developing web applications, or by someone with a development background based upon a web programming language other than PHP and whom is familiar with the MVC pattern. It is not my intention to define every technical term or to instruct the reader how to setup a basic development environment. When in doubt, I strongly encourage you to lookup any terms you may not be familiar with, as it will only help to better your understanding of the concepts within this article.
Before continuing, I'd like to address the ever present "Why are we reinventing the wheel?" question. The answer to that is this - there are lots of great MVC frameworks, but none that I’ve been able to find are simple and flexible enough for my taste, nor were they generally paired with well documented reasoning. I hope that you’ll be able to take the concepts presented here and couple them with the code from the accompanying framework to form the basis of your future web applications. How you extend the functionality of this MVC framework is entirely up to you, and in fact, I highly encourage you to experiment and implement features specific to your development requirements.
This framework has formed the basis of a few of my small hobby-level web applications, and I’ve made a particular effort to strip down much of the functionality that I myself have added over time. This was done in an attempt to simplify the framework as much as possible. I believe that this framework, with some work, is truly not far off from a production-level MVC framework.
One important point to note is that this framework espouses convention over configuration and is a major reason why I consider this framework to be so simple. Though we are going to be using a few small configuration files to hold settings such as database connection information and other various global web site variables, for the most part, the framework’s implementation is designed with the intention that the developer using this framework will be following the basic conventions outlined in this article.
The thinking here is thus – simply defined coding conventions provide more flexibility and lessen the need for strict framework enforcement code. This results in a smaller, simpler, and increasingly easier to extend framework. It should be said, however, that the argument for and against convention over configuration, and vice versa, is beyond the scope of this article.
The development of this article has been completed on a typical LAMP (Linux, Apache, MySQL, PHP5) stack. That being said, the usage of this article’s web framework only requires that the following programs are installed and running on your system, regardless of operating system:
- Apache 2
- mod_rewrite Apache2 module
- MySQL 5
Again, please ensure that the above applications are running properly before continuing. Once you have double checked the above programs are running, you need to execute the MySQL schema dump included within the accompanying code. This schema is located in the model/db/schema.sql file and should be executed on an already existing database of your choosing.
Lastly, you will also need to properly configure the settings in the database configuration file located in the model/db/dbconfig.php file. The settings there are few and fairly intuitive. There are further instructions within the file itself.
Model-View-Controller (MVC) is an architectural pattern purpose built to ensure proper loose coupling of an application’s data layer, business logic layer, and presentation layer within GUI applications. Though without question, the MVC pattern can be useful for many development scenarios that include data, logic, and presentation components, we are specifically applying it to web application development for the purposes of this demonstration.
The underlying theme of this article will be to provide a simple PHP Framework that supports the following concept:
Through the separation of the business logic layer from the presentation layer, via usage of the data layer, we are able to make changes to each, independent of the other.
To help visualize this concept, take for example how it is often the case that we find ourselves required to refactor a web application at some point in our application’s lifetime. If we had used an alternate tightly-coupled pattern, or lack thereof, we would likely need to make changes throughout the application, as one component would be dependent on one or more other components. This would ultimately lead us to the unfortunate realization that one change requires further changes, resulting in the trickling of changes down the application stack. This commonly results in a fairly imbalanced work to productivity ratio.
One solution to this kind of problem is that of a separation of concerns. For example, the MVC pattern separates the business logic layer from the presentation layer in such a way that making a business logic layer change has little or no impact on the presentation layer, and vice versa, if the interface between the two remains the same. In the case of the MVC pattern, that interface primarily consists of data (Model) being unidirectionally passed from the business logic layer (Controller) to the presentation layer (View).
The Model contains both the actual data containers and the code used to access that data. For web applications, the Model is largely comprised of data containers based upon the application’s database schema, and the data access code is, more specifically, database access code.
I’ve chosen to use PHP5’s PDO library as the basis of the Model’s database access code. One notable feature of the PDO library is that it takes advantage of PHP5’s new Anonymous Objects. We use them to create, at runtime, our Model’s data containers such that they reflect our database’s schema exactly. The decision to use PDO in this way was based upon keeping up a transparent relationship with the database – hence following our simple, convention over configuration concept.
Many other data access layers attempt to abstract away the database in an attempt to ease database vendor lock-in or reliance. Although I believe that particular point to be valid, I have found that it is often the case that web applications are so closely coupled to the particular RDBMS in use, that it never becomes an issue that is beyond a fairly simple refactoring. The above reasons are why I’ve chosen to go with PDO as the core to this Model’s data access layer, but it is your prerogative to use any other data access pattern you wish, since ultimately, the Model is fairly loosely coupled to both the Controller and View.
The Model’s core data access layer functionality is encapsulated within the ModelBase class. This class contains the following methods, included to ease common usage patterns:
- query($sql) - Executes a SQL query (Select) and returns the result as an Anonymous Object that reflects the dataset’s columns
- queryFirst($sql) – The same as the query($sql) method above, except that it only returns the first row, instead of the entire dataset
- execute($sql) – Executes a non-query SQL statement (Update, Delete, Insert) and returns the number of rows affected
Each Model database access class will inherit from the ModelBase class and implement its particular database access methods. The convention used here is that each Model class will coincide with a table within the database schema. For example, in our demo project, we have an ArticleModel class that performs all database access logic on the “article” database table. This encourages clean, easily readable, and maintainable database access code going forward. This is again, another example of using convention over configuration since you are free to do as you wish with your individual Model classes.
All database connection information used by the ModelBase class is housed within the dbconfig.php configuration file. This is one of the exceptions to the convention over configuration rule that we mentioned earlier. The decision to put database connection information within a configuration file was based upon how often this information changes during development and that as a result of that, it should be as easy to alter as possible.
In summary, a Model within the framework has the following properties:
- Data containers are runtime created Anonymous Objects with properties that reflect the returned dataset’s columns.
- Data access code is encapsulated within a Model class that inherits from the ModelBase framework class. A Model class contains all data access code specific to a single table within a web application’s database schema.
- The ModelBase framework class uses PDO as its basis for handling the details of database access.
The View contains the code necessary to express the GUI to the user of the application, via the Controllers direction. The Controller will receive the user’s input, process that input against the applications business logic, and then pass the output of that process to the view for display.
Generally speaking, a View will render HTML as instructed by the Controller and Action that has processed a web request’s business logic. The convention for organizing a View is as follows:
- A View’s controller is defined as a folder
- A View’s controller’s action is defined as a PHP file within the controller’s folder
- All Views are contained within the the “view” base folder
- Example: view/[controller]/[action] or view/article/index
The View has the added benefit of a ViewHelper class that contains static methods that make working with common View components and HTML easy. For example, it contains a “createLinkUrl($controller, $action)” method that will auto-generate a hyperlink URL by passing in a Controller name and Action name.
In summary, a View within the framework has the following properties:
- Is comprised of a Controller folder and Action file, within the View base folder e.g. view/article/index.php
- Is passed data from the Controller via the “viewData” collection which is accessed directly by variable
- Is rendered upon a Controller’s “render($view)” or “renderWithTemplate($view, $template)” method call
- Can optionally be contained within a template
- Can optionally include a template navigation section
The Controller contains an application’s business logic, specific to a particular input. In the case of a web application, the input is usually a web request based upon a URL. A web request is normally mapped to a Controller and an Action. An Action is exactly what it sounds like: an action to be performed upon a controller. An Action within a Controller is defined as any “public” method within a Controller class. Two examples of a URL mapped to a Controller and Action is as follows:
- http://www.someurl.com/[controller]/[action] - mapped to the Controller and Action
- http://www.someurl.com/article/index - mapped to the ArticleController Controller class and the index() Action method
The concrete implementation of a Controller, in our case, is a class that inherits from the ControllerBase class as defined within the framework. Within ControllerBase are two methods designed to help you render a View, with or without a template, and pass it some data. These two methods are as follows:
- renderWithTemplate($view, $template)
These methods are meant to be called at the end of a Controller’s Action method.
In our demonstration project, I have created an ArticleController Controller class. This class handles all Article related activity. This activity is reflected in the following Action methods:
- index() – lists all Articles
- view() – displays an individual article
- add() – displays a form UI used for adding an Article
- addsubmit() – redirects after receiving a form post from the add() Action
Each one of the above methods is called via URL and individually processes business logic, then calls either “render($view)” or “renderWithTemplate($view, $template)”, passing to the View any data added to the “viewData” collection.
You’ll notice that the ArticleController class has “Controller” within its name. This is not a coincidence and is an example of the framework’s convention over configuration mantra. All controller classes must inherit from the ControllerBase class and have a name that ends with “Controller”.
In summary, a Controller within the framework has the following properties:
- Inherits from the ControllerBase class
- Has a class name that ends with “Controller” e.g. ArticleController
- Has Actions defined as public methods
- Uses the “viewData” collection to pass data to the View
- Usually calls either “renderView($view)” or “renderWithTemplate($view, $template)” after processing any Controller business logic
This article briefly overviewed the major concepts and design patterns used within a simple MVC framework written in PHP5. Details on each piece of the framework can be found within the code files that accompany this article, as nearly every line is commented to explain its purpose. If anything is particularly unclear, I urge you to review the code itself, since as a reality of development, only so much can be gleaned from the subject at hand without reading through the code itself.
It is worth noting that there are many features missing from this MVC framework, the implementation of which is left to you. The intention here was to setup a framework from which you as a developer can expand and improve as you see fit, based upon the architectural pattern set forth within the slim codebase of this demo.
I've received a few inquires into the quality of the data access layer and I'd like to reiterate that although I consider the Model to be architecturally sound, its implementation is missing some important parts that are required to make this framework production ready (SQL Injection, connection management, etc). Craig Francis made a good comment about how to work through these issues in the comments below.
Its now clear to me that the Model layer is what most people are focusing on and that it would probably help if I made a follow-up post attending to the issues that have been pointed out, in addition to some further features that really should be included to make the Model more robust. Look for this update soon as it will be part of a larger database scalability article I'm working on.