From Spaghetti To MVC Posted on June 5th, 2015
This is not a technical description of the Model View Controller(MVC) pattern. It's not intended to be a functioning MVC implementation. This is pseudo workable code that explains the benefits of MVC over spaghetti code and how to transition your application from spaghetti to MVC.
MVC is about code separation, breaking your code into three layers: models, views, and controllers. Rather than explaining how each of these layers interact, I find the best way to adapt your code to a new programming concept is to see it in action. That being said, lets jump into a 90s php blog homepage.
<?php require __DIR__ . '/../bootstrap.php'; ?>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to my blog</title>
</head>
<body>
<?php $articles = DB::getInstance()->query(
'SELECT * FROM posts where published=1 ORDER BY publish_date desc LIMIT 20'
); ?>
<?php foreach($articles as $article) ?>
<?php $author = DB::getInstance()->queryOne(
'SELECT * FROM users WHERE id = ' . $article['author_id']);
?>
<article>
<header>
<h1><?= $article['title'] ?></h1>
<h3>Posted by <a href=\"/profile.php?id=<?= $author['id'] ?>\"><?= $author['name'] ?></a></h3>
</header>
<p><?= $article['body'] ?></p>
</article>
<?php endforeach; ?>
</body>
</html>
At the very top of the page is generic bootstrap file, what it does is not important. Then the page displays some HTML followed by database queries and more html in what will be come an endless cycle html and sql. In this big mess there are three different actions, bootstrapping, querying the database, and rendering HTML. We'll ignore the bootstrap file since its solved by Front Controller, rather than MVC. That leaves two actions, database queries and HTML rendering. In MVC the HTML is the view layer and should be separated from our database queries as demonstrated below.
<?php
require __DIR__ . '/../bootstrap.php';
$articles = DB::getInstance()->query(
'SELECT * FROM posts where published=1 ORDER BY publish_date desc LIMIT 20'
);
foreach($articles as $index => $article) {
$author = DB::getInstance()->queryOne(
'SELECT * FROM users WHERE id = ' . $article['author_id']
);
$articles[$index]['author'] = $author;
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to my blog</title>
</head>
<body>
<?php foreach($articles as $article) ?>
<article>
<header>
<h1><?= $article['title'] ?></h1>
<h3>Posted by <a href=\"/profile.php?id=<?= $article['author']['id'] ?>\"><?= $article['author']['name'] ?></a></h3>
</header>
<p><?= $article['body'] ?></p>
</article>
<?php endforeach; ?>
</body>
</html>
This shows the logical divide between the view and the other portions of the code. We can move php block at the top of the file into its own file. Since this code will eventually become our controller we'll call it HomeController.php
<?php
class HomeController
{
public function get()
{
$articles = DB::getInstance()->query(
'SELECT * FROM posts where published=1 ORDER BY publish_date desc LIMIT 20'
);
foreach($articles as $index => $article) {
$author = DB::getInstance()->queryOne(
'SELECT * FROM users WHERE id = ' . $article['author_id']
);
$articles[$index]['author'] = $author;
}
return ['homepage.tpl', ['articles' => $articles]];
}
}
The above controller returns an array which contains the name of the view file and an array of variables that should be accessible in the view file. The important part here is the separation of the view layer from the rest of our code. There is no longer HTML jumbled in with our database queries.
HomeController can be further separated into 2 code segments: data retrieval(SQL Queries) and data preparation(matching authors with their blog posts).
public function get()
{
$articles = DB::getInstance()->query(
'SELECT * FROM posts
WHERE published=1
ORDER BY publish_date DESC
LIMIT 20
');
// Assume this returns an associative array
// where the array index is the primary key
// of the database row.
$author_ids = array_column($articles, 'author_id');
$authors = DB::getInstance()->queryMap(
'SELECT * FROM users
WHERE in (' . implode(',', $author_ids) . ')'
);
foreach($articles as $index => $article) {
$articles[$index]['author'] = $authors[$article_id];
}
return ['homepage.tpl', ['articles' => $articles]];
}
We can now see the division between data access and data preparation. We'll move the data access into the model layer. Model layers have traditionally been a combination of business logic(Does this user have permission to write a blog post?) and data access (Give me this author's blog posts.).
class User {
public static function getByIds(array $ids) {
return DB::getInstance()->queryMap(
'SELECT * FROM users WHERE in (' . implode(',', $ids) . ')'
);
}
}
class Post {
public static function getRecentPublishedPosts($limit)
{
return DB::getInstance()->query(
'SELECT * FROM posts
WHERE published=1
ORDER BY publish_date DESC
LIMIT ' . $limit
);
}
}
Notice that the query for retrieving Posts is in a different class than the query for retrieving Users. Generally, you'll have 1 class representing each table in your database.
We can now update controller to use our new models to access data.
public function get()
{
$articles = Post::getRecentPublishedPosts(20);
$authors = User::getByIds(array_column($articles, 'author_id'))
foreach($articles as $index => $article) {
$articles[$index]['author'] = $authors[$article_id];
}
return ['homepage.tpl', ['articles' => $articles]];
}
In the end we have 4 files which compose the three different sections of MVC.
<?php
// file: controller/HomeController.php
class HomeController
{
public function get()
{
$articles = Post::getRecentPublishedPosts(20);
$authors = User::getByIds(array_column($articles, 'author_id'))
foreach($articles as $index => $article) {
$articles[$index]['author'] = $authors[$article_id];
}
return ['homepage.tpl', ['articles' => $articles]];
}
}
// file: model/User.php
class User {
public static function getByIds(array $ids) {
return DB::getInstance()->queryMap(
'SELECT * FROM users WHERE in (' . implode(',', $ids) . ')'
);
}
}
// file: model/Post.php
class Post {
public static function getRecentPublishedPosts($limit)
{
return DB::getInstance()->query(
'SELECT * FROM posts
WHERE published=1
ORDER BY publish_date DESC
LIMIT ' . $limit
);
}
}
// views/homepage.tpl
<!DOCTYPE html>
<html>
<head>
<title>Welcome to my blog</title>
</head>
<body>
<?php foreach($articles as $article) ?>
<article>
<header>
<h1><?= $article['title'] ?></h1>
<h3>Posted by <a href=\"/profile.php?id=<?= $article['author']['id'] ?>\"><?= $article['author']['name'] ?></a></h3>
</header>
<p><?= $article['body'] ?></p>
</article>
<?php endforeach; ?>
</body>
</html>
This example is the missing glue that ties the incoming request to the controller execution and controller result to the view rendering, but its implementation is not specific to MVC. The importance of MVC is the separation of your code into logical groupings.
The code and comments above are not meant to be a strict definition or realistic implementation of the MVC pattern. Their designed to show the transition a developer could make from an old fashion php page to a mvc application. Separation of concerns ends up being an extremely important part of programming. Keeping your code small and modular helps other developers or even you 6 months later understand what is going on.