A Most Simple PHP MVC Beginners Tutorial - Require 'Mind'
A Most Simple PHP MVC Beginners Tutorial - Require 'Mind'
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.
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) :
Separation of Concerns
DRY
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.
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).
1 <?php
2 require_once('connection.php');
3 ?>
connection.php
1 <?php
2 class Db {
3 private static $instance = NULL;
4
5 private function __construct() {}
6
7 private function __clone() {}
8
9 public static function getInstance() {
10 if (!isset(self::$instance)) {
11 $pdo_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
12 self::$instance = new PDO('mysql:host=localhost;dbname=php_mvc', 'root', '', $pdo_options);
13 }
14 return self::$instance;
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.
index.php
1 <?php
2 require_once('connection.php');
3
4 if (isset($_GET['controller']) && isset($_GET['action'])) {
5 $controller = $_GET['controller'];
6 $action = $_GET['action'];
7 } else {
8 $controller = 'pages';
9 $action = 'home';
10 }
11
12 require_once('views/layout.php');
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.
1 <DOCTYPE html>
2 <html>
3 <head>
4 </head>
5 <body>
6 <header>
7 <a href='/php_mvc_blog'>Home</a>
8 </header>
9
10 <?php require_once('routes.php'); ?>
11
12 <footer>
13 Copyright
14 </footer>
15 <body>
16 <html>
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.
routes.php
1 <?php
2 function call($controller, $action) {
3 // require the file that matches the controller name
4 require_once('controllers/' . $controller . '_controller.php');
5
6 // create a new instance of the needed controller
7 switch($controller) {
8 case 'pages':
9 $controller = new PagesController();
10 break;
11 }
12
13 // call the action
14 $controller->{ $action }();
15 }
16
17 // just a list of the controllers we have and their actions
18 // we consider those "allowed" values
19 $controllers = array('pages' => ['home', 'error']);
20
21 // check that the requested controller and action are both allowed
22 // if someone tries to access something else he will be redirected to the error action of the pages controller
23 if (array_key_exists($controller, $controllers)) {
24 if (in_array($action, $controllers[$controller])) {
25 call($controller, $action);
26 } else {
27 call('pages', 'error');
28 }
29 } else {
30 call('pages', 'error');
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.
controllers/pages_controller.php
1 <?php
2 class PagesController {
3 public function home() {
4 $first_name = 'Jon';
5 $last_name = 'Snow';
6 require_once('views/pages/home.php');
7 }
8
9 public function error() {
10 require_once('views/pages/error.php');
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.
views/pages/home.php
Use those previously defined $first_name and $last_name where you see fit.
views/pages/error.php
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
routes.php
1 <?php
2 function call($controller, $action) {
3 require_once('controllers/' . $controller . '_controller.php');
4
5 switch($controller) {
6 case 'pages':
7 $controller = new PagesController();
8 break;
9 case 'posts':
10 // we need the model to query the database later in the controller
11 require_once('models/post.php');
12 $controller = new PostsController();
13 break;
14 }
15
16 $controller->{ $action }();
17 }
18
19 // we're adding an entry for the new controller and its actions
20 $controllers = array('pages' => ['home', 'error'],
21 'posts' => ['index', 'show']);
22
23 if (array_key_exists($controller, $controllers)) {
24 if (in_array($action, $controllers[$controller])) {
25 call($controller, $action);
26 } else {
27 call('pages', 'error');
28 }
29 } else {
30 call('pages', 'error');
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.
controllers/posts_controller.php
1 <?php
2 class PostsController {
3 public function index() {
4 // we store all the posts in a variable
5 $posts = Post::all();
6 require_once('views/posts/index.php');
7 }
8
9 public function show() {
10 // we expect a url of form ?controller=posts&action=show&id=x
11 // without an id we just redirect to the error page as we need the post id to find it in the database
12 if (!isset($_GET['id']))
13 return call('pages', 'error');
14
15 // we use the given id to get the right post
16 $post = Post::find($_GET['id']);
17 require_once('views/posts/show.php');
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.
models/post.php
1 <?php
2 class Post {
3 // we define 3 attributes
4 // they are public so that we can access them using $post->author directly
5 public $id;
6 public $author;
7 public $content;
8
9 public function __construct($id, $author, $content) {
10 $this->id = $id;
11 $this->author = $author;
12 $this->content = $content;
13 }
14
15 public static function all() {
16 $list = [];
17 $db = Db::getInstance();
18 $req = $db->query('SELECT * FROM posts');
19
20 // we create a list of Post objects from the database results
21 foreach($req->fetchAll() as $post) {
22 $list[] = new Post($post['id'], $post['author'], $post['content']);
23 }
24
25 return $list;
26 }
27
28 public static function find($id) {
29 $db = Db::getInstance();
30 // we make sure $id is an integer
31 $id = intval($id);
32 $req = $db->prepare('SELECT * FROM posts WHERE id = :id');
33 // the query was prepared, now we replace :id with our actual $id value
34 $req->execute(array('id' => $id));
35 $post = $req->fetch();
36
37 return new Post($post['id'], $post['author'], $post['content']);
38 }
39 }
40 ?>
Let’s modify our layout to add a link to the posts in the header.
views/layout.php
1 <DOCTYPE html>
2 <html>
3 <head>
4 </head>
5 <body>
6 <header>
7 <a href='/php_mvc_blog'>Home</a>
8 <a href='?controller=posts&action=index'>Posts</a>
9 </header>
10
11 <?php require_once('routes.php'); ?>
12
13 <footer>
14 Copyright
15 </footer>
16 <body>
17 <html>
Ok now final step we have to create the views we require in the posts controller.
views/posts/index.php
views/posts/show.php
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)
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.
by Neil Rosenstech
Sort by Best
Recommend 26 ⤤ Share
LOG IN WITH
Name
To be honest, in your example, I can see you didn't follow the SoC design principle and this example is not appropriate
for this era of web engineering. Alos, Symfony follows the HMVC (Hierarchical Model View Controller) which is known
as component based/modular but still follows the SoC principle.
You should Google the web for MVC examples/tutorials and Check the Laravel framework if you didn't yet. Btw, your
article could lead others to wrong direction.
3△ ▽ • Reply • Share ›
Actually, I did Google for PHP and MVC stuff, only to find out most articles do a great job at spreading
complicated terms with complicated examples instead of giving basic explanations on basic stuff.
Ultimately, if people reading my article end up with a better general idea of the concept then it will be mission
accomplished for me and I really don't care if they are wrong using the term "design pattern" instead of
"architectural pattern".
8△ ▽ • Reply • Share ›
Commenting as one who had "no idea at all of what MVC is", and as someone who was definitely
struggling with it after several articles/introductions, I now have a better understanding of SoC after
building a simple application. That was only possible for me by using the example in this tutorial.
Awesome! This is why I keep writing stuff and comments like yours are the best rewards. I'm glad
it helped ; )
1△ ▽ • Reply • Share ›
Azad Hind Bhagu > require mind • 2 years ago
Heartiest thanks to you friend. Your effort is something I needed very much. Being a Scholar is a
different thing. Being a teacher is another. People sometimes get into a habit of looking at the
glass from top ( and thus end up yelling the empty part)...;) ...=D..... Thanks for making the world
even better. :) It's far better than it has been explained on Symfony's website.
△ ▽ • Reply • Share ›
Thanks! I didn't have the chance to read much about symfony but from my understanding it has a
slightly different mechanism due to its use of components. From what I saw it's just some sort of
"improved" MVC where concerns are further split into more entities.
△ ▽ • Reply • Share ›
There is always room for improvement, but I could give two $hits if I didn't have the purest pattern. I'm
making websites, not space ships.
△ ▽ • Reply • Share ›
But calling things with wrong names will only cause more and more confusion. That's the point
here.
2△ ▽ • Reply • Share ›
It looks more like MVP (Model-View-Presenter) to me. In MVP, the Presenter is designed to pull
data from model and push it to views.
△ ▽ • Reply • Share ›
I like the MVP pattern myself, for the very reasons you mentioned -- code maintenance is much
easier, which is a significant attribute to have -- but it should also be noted that MVP makes it a
good bit more difficult to make truly modular code. Which may seem trivial to newer programmers,
but once they dig in a bit and write something even remotely complex, they'll begin to understand
how a true MVC pattern will help them make reusable code.
A good, and common, example of that is when trying to use AJAX to pull data from models. If
you're using the "Presenter" to pull data from the model and push it to a view, and the view is just
a dummy template that is tightly coupled with the Presenter, you aren't fetching just the data that
AJAX requested, you're also fetching an entire web page. I can't imagine many scenarios where
that is desirable...that's just one of many example of why not to consider a view nothing more than
a template file.
Unfortunately, a lot of popular software/frameworks out there label themselves as MVC when they
are not, which contributes to all the confusion.
1△ ▽ • Reply • Share ›
As another note, View in MVC isn't just a dummy template, it's a class of it's own, responsible of fetching data from
Model (which MIGHT have been altered by Controller, not necessarily), then decide - based on that data - how to
display it. E.g. using templates.
While this code works and probably is a fast and efficient way to develop things, it's not MVC. It's not bad code, but it's
not MVC either.
1△ ▽ • Reply • Share ›
BTW, I'd like to see how View classes can be implemented, and what benefits they provide.
△ ▽ • Reply • Share ›
I'd say you're half right on that one, let me explain why. The controllers are not directly fetching data, the models
are. Also, just because the directive for including the view stands right there in the controller does not mean the
concerns are not separated.
At the end of the day, the controller is delegating to some model (as it should, this could be improved but the
basic operations are there) and rendering the view, not through a separate class as you mentioned but rather
directly because for this example I did not want to clutter my code with useless things. A framework like Rails
does a lot of transformation under the hood that I do not need to do here so using a class for that seemed
overkill.
Sure there are a number of ways through which you could improve this, like extracting domain specific objects
and having a controller doing 100% delegation.. but once again this lies beyond the scope of this article.
△ ▽ • Reply • Share ›
"it renders itself" haha, yes, by the magician's wand ; ) It is SoC and in that case the controller's
responsibility is to organize data and dispatch "actionsactions" to be taken to whatever other
structure responsible for it. Saying fetching data through the model and passong it to the view are
two responsibilities is like saying rendering the title and rendering the subtitle are two different
things... damn, concerns are not separated properly in the view now. By the way, this is all mental
things... damn, concerns are not separated properly in the view now. By the way, this is all mental
masturbation, in the end, do you write good software/apps or not is what matters.
1△ ▽ • Reply • Share ›
Controller alters the model, view then renders itself (after your routes.php calls controller's action
method) based on whatever model contains. Or by using Observer pattern where Model tells View
it has changed. And tadah, there you have a mvc triad with concerns way better separated.
Don't get offended when people correct some issues rather than just taps your back and says that
you rule. And yet again, i am not saying your code is bad (because it works and does what it
promises), it just is not MVC."
1△ ▽ • Reply • Share ›
if user exists, where can I redirect to the panel, on model or controller? I can't figure out. Will my form have an action
attribute? because I need to create 2 functions on controller, the admin() - page with form - and panel() - the panel itself
-. how can I redirect the user either the answer is true or false? thanks!
△ ▽ • Reply • Share ›
Linux command line Tips: Become a master Deploying a Rails App on Your Own Server - The
11 comments • 4 years ago• Ultimate Guide
5 comments • 3 years ago•
AvatarMatt Rose — && and ; are not equivalent. try # false ; echo
"done"# false && echo "done" Avatarasksammy — Thanks for the post. I have redis on a
separate server. Should sidekiq be deployed on same or
different server as the ruby app?
Differences between has_one and belongs_to in Ruby Ruby on Rails 4 and Batman.js - Another Getting
on Rails Started Tutorial
19 comments • 4 years ago• 15 comments • 4 years ago•
AvatarDave Aronson — I keep this straight by thinking of physical Avatarrequire mind — Nice ! And to answer your previous
possessions with a name on them. When I buy, for instance, question I could not really recommend any other tutorial for
a book, I might put my name inside the cover, … two reasons : I've tried Batman and Angular so far but I …
Subscribe!
Follow us on Twitter GitHub Google+
All content copyright requireMind © 2015 • All rights reserved • Published with Octopress