Michael Girouard's Blog
Before I get into any sort of detail, I think it's important to say that there is more than one way to approach an MVC. Go ahead, ask a few seasoned developers how they would hack together a project like this and each one will give you a completely different answer. I'm going to do my best to simplify the process as much as I possibly can without damaging the stability, security, and scalability of the framework.
In my previous article, I announced that I would be documenting the process of developing a simple MVC framework. In this one I will go into a little more detail about each of the specific components of our MVC and will discuss the series of events which occur each time a page loads, otherwise known as the page load scenario.
We’re going to start by outlining a typical page load scenario, and then how to write code around it so we can gain more control over each part. By the end of this post we’ll have a solid understanding of what code needs to be written, and how it all will fit together.
As the acronym would suggest, a URI identifies a resource. A typical web application may use a URI like: /viewCategory.php?id=123&output=json. This system is tried and true and always works — but it can still be improved upon. This URI could be rewritten as: /category/123.json.
There are a few key things to note about this URI structure:
URI restructuring has become a common practice with most frameworks today. Apache users will use mod_rewrite, IIS users will use ISAPI Rewrite, and there are other solutions for other server platforms. When implemented properly, a well rewritten URI can prove to be a valuable site component which brings an added layer of usability and search engine optimization. But still, we can’t rely upon an external technology as a core part of the framework — it should be as “out of the box” as possible.
It’s also important to mention that the query string should not be modified by the framework in any way. This leaves it entirely up to the developer whether or not to use the query string to pass data into the application and not an assumption made by the framework. Instead of passing the information through the query string, it is advised that the extra data be passed as a path directly after the index.php in the file name; for example: /index.php/some/information.
During the page load scenario, a single, central object oversees the entire execution — this is known as the front controller. The front controller loads all necessary components of the application and provides an API to access them. In this particular case, it loads the route, the request, the view, and the controller (the application controller).
If anything were to go wrong anywhere throughout the process, the front controller knows about it and handles the problem gracefully.
After the front controller is loaded, the next object to be instantiated during the page load scenario is the request object. This most likely will be a singleton instance and will contain all request data (_GET, _POST, and _FILE). Additional information about the request will also be stored in the request object. This includes the request method, actual URI, and evaluated URI (which is used when creating the route object).
It’s quite obvious that most of this information is already found in PHP’s superglobals, so at first glance it may not seem like it makes sense to duplicate it here; however, it affords us the ability to filter the data as it comes in and as it goes out.
The route represents the extra data from the URI mentioned above. Routes provides valuable information about a resource and how it is to be accessed. In order for a route to be instantiated properly, a request object will need to exist.
Routes are more than just path strings. They are dynamic and constantly changing. For example, imagine a scenario where no extra data is provided in the URI, but you still wish to load a default application controller. In this case, the route object would need to be injected with the default data. The only reasonable solution is to write an API for it which allows developers to add and remove path components at will. Really, routes should extend a base Path class… but we’ll worry about that later.
Views represent the information presented to the client who requested the data. Since clients may vary, various output formats are required. This fact alone makes the view, quite possibly, the most flexible component in our MVC. For example, when it comes to web browsers, the most common output format will be HTML. If a JavaScript application were to request the same resource, JSON should be served up. The possibilities are endless so special attention to the design of this package is critical.
Some MVC implementations suggest allowing full access to models; this one however, will not. The view will have an API allowing data to be manipulated before it is sent to the client as a response.
Although a view could stand on its own if instantiated directly, the problem arises as to which view to create. This job, will be assigned to the route as it knows all of the information it needs to create an instance of the correct view.
Controllers (more specifically, application controllers) are classes designated to handle requests to resources which are managed by the MVC. They also act as a liaison between models and the view.
Traditionally, MVC applications use the first two components of the route path to identify the controller class and the method (also known as an action) to call. For example, the route /blog/view/1234 would load the Blog class, call the view method with a parameter of “1234“. There is nothing wrong with this approach, but it still could be optimized further. Ideally, all controllers would act as a REST service whereby the action could be omitted.
REST-friendly controllers are simple: whenever a GET request comes in, the get method is called on the controller. Likewise, if a POST, PUT, or DELETE request comes in, the post, put or delete method is called respective to the request method. According to most REST services, GET requests are used to locate data, POST request are used to create data, PUT requests are used to update data, and DELETE requests are used to destroy data.
There is one problem though: web browsers don’t support PUT or DELETE based requests. This can be overcome by using POST requests in place of PUT and DELETE, but by adding an additional field to the posted data which correctly identifies the request.
Models are classes designed to represent data. Most web applications are powered by a relational database of some kind so it’s not uncommon to find that models relate to a specific database table and/or row. A lot of frameworks take that to another level and allow their model classes to implement an ActiveRecord style interface with convenience functions to find, save, and delete data that the model represents.
For this MVC, models will much more simple. If models just represent data, and nothing more, that means they can represent any kind of data and thus maximizing component reuse. If you’re worried about loosing all the convenience that ActiveRecord offers, simple inherit from the ActiveRecord class and code as you normally would.
So there you have it. Everything you just read sums up the core framework components for our MVC. In future articles, I will examine revisit each component in detail and provide code and unit tests. After all, you were really here for the code anyway right?
I encourage everyone to offer their feedback on any level. This is an open source project so you have as much say in it as I do.
[Part One of this series can be found here.]