Lesson 8 - Listing articles from the database in PHP (MVC)
In the previous lesson, Database wrapper, we created a database wrapper on top of the PDO PHP driver. Our database ready to go, so nothing is there to stop us from communicating with it.
Connecting
First of all, we have to connect to the database. We'll do that in index.php just before creating the router:
// Connects to the database Db::connect("127.0.0.1", "root", "", "mvc_db");
Don't forget to change the credentials to match the ones provided to you by your web hosting service, (the ones I used are the default for localhost in XAMPP). The database is now accessible in our application, so let's make a query.
Model - Article manager
We'll make a class with the logic required to work with articles. It'll contain individual SQL queries. As you may have guessed, the class will be put in the "models" folder and will be named ArticleManager. We'll create a class like this for every entity in our system. The ArticleManager class will be as follows:
<?php // Manages articles in the system class ArticleManager { // Returns an article from the database based on a URL public function getArticle($url) { return Db::queryOne(' SELECT `article_id`, `title`, `content`, `url`, `description` FROM `article` WHERE `url` = ? ', array($url)); } // Returns a list of all of the articles in the database public function getArticles() { return Db::queryAll(' SELECT `article_id`, `title`, `url`, `description` FROM `article` ORDER BY `article_id` DESC '); } }
The class has 2 methods:
- getArticle() returns an article from the database based on its URL. Notice how the URL simply comes as a method parameter because the model shouldn't know where it comes from. We did it this way because retrieving data from a user isn't a role fit for a model. We add a placeholder (question mark) in, instead of adding the variable into the query string directly. Then, we pass all of the query parameters to database as an array, which it safely inserts into the query on its own.
- getArticles() returns a list of all of the article (without their contents). Articles are ordered by ID in descending order, so they're sorted from newest to oldest. This method is only used to print a list of the existing articles (not the actual articles).
Views
We'll need 2 views. One for an article and one for the article list.
article.phtml
As for the article, we'll print its heading and then its content. The view will be as follows:
<header> <h1><?= $title ?></h1> </header> <section> <?= $content ?> </section>
We'll create the second view at the end of the lesson.
Controller
Now that we have the model and the view. Meaning that all we need now is the man in the middle - the controller, which wires it all together. The ArticleController will be as follows:
class ArticleController extends Controller { public function process($params) { // Creates a model instance which allows us to access articles $articleManager = new ArticleManager(); // Retrieves an article based on its URL $article = $articleManager->getArticle($params[0]); // If no article was found we redirect to ErrorController if (!$article) $this->redirect('error'); // HTML head $this->head = array( 'title' => $article['title'], 'description' => $article['description'], ); // Sets the template variables $this->data['title'] = $article['title']; $this->data['content'] = $article['content']; // Sets the template $this->view = 'article'; } }
The controller creates the model and retrieves the article based on its URL address. If the article isn't found, the variable is evaluated as false, and we redirect the user to the ErrorController. We set the page head based on the article, and then we pass the title and content to the view so it can be rendered. Last of all, we set the view to article.phtml.
Let's try it all out. When we open the application, we should see the home article:
Try to entering a URL of a nonexistent article. You should be redirected to the error page.
articles.phtml
The article list will be a little bit complicated since we need to render the list from an array which we get from the database. Now, how would we do that? For now, all we can do is print individual variables, and what we need to do is print items from a collection. Let's go over the PHP template syntax and expand our knowledge.
PHP template syntax
Aside from the <?= directive, PHP offers us template equivalents from the most common language constructs. Meaning that we're able to add a minimal amount of logic into a view (template) without corrupting the HTML structure, since template syntax lets us add PHP into HTML.
Without knowing anything about template syntax, we would likely attempt to render the article list into a table this way:
<h1>Article list</h1> <table> <?php foreach ($articles as $article) { echo('<tr><td><h2> <a href="article/' . $article['url'] . '"> ' . $article['title'] . '</a> </h2>' . $article['description']); echo('</td></tr>'); } ?> </table>
The template above is very confusing, we're get lost in the quotes as we concatenate strings. Let's re-write the code above using template syntax:
<h1>Article list</h1> <table> <?php foreach ($articles as $article) : ?> <tr> <td> <h2><a href="article/<?= $article['url'] ?>"><?= $article['title'] ?></a></h2> <?= $article['description'] ?> </td> </tr> <?php endforeach ?> </table>
Notice how the foreach loop ends with a colon and the PHP directive is closed afterward. This is a template version of the foreach loop. For every item it echoes, the HTML code is printed until it reaches the endforeach keyword. We can rewrite for and while loops or conditions (if) in a similar way. HTML is written as HTML, not as a string. We use the <?= notation to insert variables into HTML as like we usually do. Make a new file and name it articles.phtml with these contents in the "views" folder.
Note: Since we still haven't gotten to sanitize HTML entities in templates, we're vulnerable to XSS attacks. We'll make it right in the next lesson.
Modifying the ArticleController
The article list will be rendered by the ArticleController if we don't pass any other parameters to it. We'll simply split the controller into 2 parts using a condition. The first part will display an article, and the second will display the article list. Even though we could use two controllers to do so, we usually use one if the actions are closely related. Let's modify the process() method accordingly:
public function process($params) { // Creates a model instance which allows us to access articles $articleManager = new ArticleManager(); // The URL used to display the article has been entered if (!empty($params[0])) { // Retrieves an article based on a URL $article = $articleManager->getArticle($params[0]); // If no article was found we redirect to the ErrorController if (!$article) $this->redirect('error'); // HTML head $this->head = array( 'title' => $article['title'], 'description' => $article['description'], ); // Sets the template variables $this->data['title'] = $article['title']; $this->data['content'] = $article['content']; // Sets the template $this->view = 'article'; } else // No URL was entered, so we list all of the articles { $articles = $articleManager->getArticles(); $this->data['articles'] = $articles; $this->view = 'articles'; } }
Now let's open the article controller and leave it without entering a URL. The article list should appear:
In the next lesson, Securing templates, we'll sanitize our templates to protect us from XSS attacks.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 308x (15.94 kB)
Application includes source codes in language PHP