A Most Simple PHP MVC Beginners Tutorial
A few months ago, a friend of mine started taking courses about web development. Like (almost) everyone else he began learning PHP and soon enough he was asked to develop a small handmade MVC application. So he went on the internet and started searching easily understandable tutorials, only to find out there was not that many good guides out there. Let’s fix this.
Disclaimer: this tutorial is just my take on the subject. I do not guarantee that it will solve your problem. MVC is a design pattern really easy to grasp when you’ve been working with it, yet a bit odd when you come across the first time. The application I will create is probably perfectly imperfect, this was originally a quick afternoon draft from a guy that has been working with Ruby on Rails the last couple years, it goes without saying that PHP was remotely a memory. Anyways, if you find anything in this article that you think could threaten the future of the universe, please yell at me in the comments below.
Application Github repo: https://github.com/Raindal/php_mvc
Let’s begin with the boring part.
MVC, what the f***?
Right now I’m guessing you have at least a basic understanding of PHP. You’ve probably been building small websites or applications already, to get a general idea of the possibilities.
I suppose you’ve been starting with putting everything in one file and you quickly moved on to using “includes” and “requires” (better even require_once) just to make your code more readable, easier to follow for you or someone else. You see, that transition you made was for your code to be clearer, and therefore, maintainable. Maybe you even know about such things as Separation of Concerns and DRY (Do not Repeat Yourself). If you don’t know about those I suggest you take a quick look at it cause you’ll come across that stuff a lot more than you’d think if you plan on following the white rabbit.
Here are some useful resources on the matter, just get a vague idea, do not get stuck on those (read the introductions at least I guess) :
Well, MVC is a design pattern that suggests you organize your code in a way concerns are properly separated, and yet interact with each other in a certain fashion.
MVC stands for Model View Controller as you may already know. That makes 3 types of concerns, I know this sounds a bit esoteric but I have faith you’ll get it by reading on. Just to make things simpler, just imagine you have an application and in that application you have at least those 3 folders : models, views and controllers. Those folders contain specific files (no shit Sherlock). Now, those files are classified that way for a good reason: they have to carry on different tasks.
In the end: models fetch and mold data which is in turn sent to the view and therefore diplayed. The controller is managing the process.
Building an example app
Let’s get the basic stuff done: create a database php_mvc that contains a table posts with 3 columns id (INT PRIMARY auto_increment), author (VARCHAR 255, note that in a real life situation you could also put an index on this one as searching through posts with a sepcific author may happen quite often) and content (TEXT).
Let’s move on to creating our first file: index.php.
The first thing it’s going to do is making sure we have something to query the database any time later in the execution.
As this is a pretty specific thing we’re gonna put it in a specific file that we’re going to require in our index.php.
Note that I’m working in WAMP. My project root is at C:\wamp\www\php_mvc_blog
1 2 3 |
|
Let’s write the actual file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
You may have to change the 12th line to match your database user/password. Here my user is root
and I do not have a password.
As you may have noticed we’re just creating a singleton class. This allows us to return an instance of the connection and always the same as we only need one to execute all our queries. As the class is a singleton, we make __construct()
and __clone()
private so that no one can call new Db()
. It has an $instance
variable that retains the connection object (PDO here). In the end, we have access to our “connection object” through Db::getInstance()
.
Note that singletons are not the only way to handle that, it’s just been a common way for years but this may not be the best approach depending on your needs: see this post on StackOverflow for instance.
Anyways, we can go on with our index file.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Here is the whole file. Note that this index.php file is going to receive all the requests. This means that every link would have to point to /?x=y or /index.php?x=y.
The if statement is gonna check whether we have the parameters controller and action set and store them in variables. If we do not have such parameters we just make pages the default controller and home the default action. You may not know where we’re going with this but you should get the idea by reading on. Every request when hiting our index file is going to be routed to a controller (just a file defining a class) and an action in that controller (just a method).
Finally we require the only part of our application that does not (theoretically) change: the layout.
Let’s create this file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
As you can see, we put it in a new views/ folder because it is something displayed on the users’ screen, in other words it is rendered. It contains only our header with the different links you could find in a basic menu and a footer. Note that while I’m working on WAMP for this guide the root of my application is not / but /php_mvc_blog (the name of my folder under www/ in WAMP).
In the middle we require another file: routes.php. The only part we still need is the main area of our page. We can determine what view we need to put there depending on our previously set $controller and $action variables. The routes.php file is gonna take care of that.
This file is not in the views folder but at the root of our application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Ok so we want our routes.php to output the html that was requested one way or another. To fetch the right view (file containing the html we need) we have 2 things: a controller name and an action name.
We can write a function call
that will take those 2 arguments and call the action of the controller as done in the previous code sample.
We’re doing great. Let’s continue and write our first controller for the call function to find the file.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The class is rather straightforward. We have 2 public functions as expected: home()
and error()
.
As you can see, the first one is also defining some variables. We’re doing this so we can later ouput their values in the view and not clutter our view with variables definition and other computation
not related to anything visual. Separation of concerns.
To keep things simple, we usually name the view after the action name and we store it under the controller name.
Let’s create both our views.
1 2 3 |
|
Use those previously defined $first_name and $last_name where you see fit.
1 2 3 |
|
Fantastic! We have a working sample of our MVC app. Navigate to localhost/php_mvc_blog and you should see the following:
Home
Hello there Jon Snow!
You successfully landed on the home page. Congrats!
Copyright
Improve the app with models and some data management
What we want now is to be able to query our database in a clean way and display the results.
Say we want to be able to fetch a list of posts and display those, and same thing for one particular post.
Let’s modify our routes.php file to reflect that behaviour.
Here is the final file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Just pay attention to where I add a comment, those parts where modified.
We can create the posts controller now and we will finish with the model.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
As you may have noticed, the pages controller is managing static pages of our application whereas the posts controller is managing what we call a resource.
The resource here is a post. A resource can usually be listed (index action), showed (show action), created, updated and destroyed.
We will keep the focus on the first two as building a fully functional app is not the point of this article.
Post::all()
and Post::find()
refer to the model Post that has not yet been written. One of the best piece of advice I was ever given was to write the final code as I want it to be. This way
I’m sure I’m writing the most beautiful code I can come up with and then I make sure it works.
So here, when I fetch the posts I want it to look like $posts = Post::all();
as this is clear and simple.
Now we can move on to writing the model.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Our model is a class with 3 attributes.
It has a constructor so we can call $post = new Post('Neil', 'A Most Simple PHP MVC Beginners Tutorial')
if need be.
I think this is pretty much straightforward but let me know in the comments below if something needs more explanation.
Let’s modify our layout to add a link to the posts in the header.
Here is the final file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Ok now final step we have to create the views we require in the posts controller.
1 2 3 4 5 6 7 8 |
|
And
1 2 3 4 |
|
We need to have some data in the database so add some manually as we do not have a feature for this yet.
I’m using phpmyadmin for this matter.
I created 2 posts :
- one with author “Jon Snow” and content “The Wall will fall.”
- the other with author “Khal Drogo” and content “I may be dead but I’m still the most awesome character.” (and you shall not disagree on that one)
Now go ahead and test everything in your browser.
What you’ve seen here is a way of separating the concerns in an MVC style.
Note that a lot of stuff is open to interpretation. This way of seeing things is mostly inspired by what I’m used to do with Rails.
If you want to go further down the road you should check out some framework like Symfony for PHP, Django for Python or Rails for Ruby.
That’s it folks! Hope it helps. Until next time.