Делаем вывод статей на сайте из базы данных

30.06.2023 в 07:57
18505
+1045

Итак, в прошлом уроке мы с вами создали класс для работы с базой данных и получили с его помощью две статьи. В этом уроке мы научимся выводить эти статьи на нашем сайте.

Давайте заглянем в наш шаблон.

templates/main/main.php

<?php include __DIR__ . '/../header.php'; ?>
<?php foreach ($articles as $article): ?>
    <h2><?= $article['name'] ?></h2>
    <p><?= $article['text'] ?></p>
    <hr>
<?php endforeach; ?>
<?php include __DIR__ . '/../footer.php'; ?>

Как видим, здесь у нас требуются ключи ‘name’ и ‘text’. И они есть у наших статей! Нам достаточно только передать эти статьи в шаблон, чтобы вывести их.

src/MyProject/Controllers/MainController.php

<?php

namespace MyProject\Controllers;

use MyProject\Services\Db;
use MyProject\View\View;

class MainController
{
    /** @var View */
    private $view;

    /** @var Db */
    private $db;

    public function __construct()
    {
        $this->view = new View(__DIR__ . '/../../../templates');
        $this->db = new Db();
    }

    public function main()
    {
        $articles = $this->db->query('SELECT * FROM `articles`;');
        $this->view->renderHtml('main/main.php', ['articles' => $articles]);
    }
}

Снова обновим страничку.
Статьи отрендеренные

Как видим, нужные данные успешно были использованы в шаблоне.
Давайте добавим на нашем сайте ещё одну страничку, на которой будет выводиться только одна статья.

Давайте создадим новый контроллер.

src/MyProject/Controllers/ArticlesController.php

<?php

namespace MyProject\Controllers;

use MyProject\Services\Db;
use MyProject\View\View;

class ArticlesController
{
    /** @var View */
    private $view;

    /** @var Db */
    private $db;

    public function __construct()
    {
        $this->view = new View(__DIR__ . '/../../../templates');
        $this->db = new Db();
    }

    public function view()
    {
        echo 'Здесь будет получение статьи и рендеринг шаблона';
    }
}

Добавим новый роут. Пусть наши статьи будут открываться по адресу типа: http://myproject.loc/articles/1, где вместо 1 может быть любой другой id статьи.

src/routes.php

<?php

return [
    '~^articles/(\d+)$~' => [\MyProject\Controllers\ArticlesController::class, 'view'],
    '~^$~' => [\MyProject\Controllers\MainController::class, 'main'],
];

Давайте проверим, что наш роут успешно обрабатывается:
ЧПУ

Отлично, давайте теперь сделаем запрос в базу, в котором получим статью с нужным id.

src/MyProject/Controllers/ArticlesController.php

<?php

namespace MyProject\Controllers;

use MyProject\Services\Db;
use MyProject\View\View;

class ArticlesController
{
    /** @var View */
    private $view;

    /** @var Db */
    private $db;

    public function __construct()
    {
        $this->view = new View(__DIR__ . '/../../../templates');
        $this->db = new Db();
    }

    public function view(int $articleId)
    {
        $result = $this->db->query(
            'SELECT * FROM `articles` WHERE id = :id;',
            [':id' => $articleId]
        );
        var_dump($result);
    }
}

Посмотрим на результат снова.
Одна статья в виде массива

Получили массив, в котором есть статья с id = 1.
Давайте посмотрим, что получится, если запросить статью с id = 2.
Вторая статья в виде массива

Всё снова хорошо отработало.
А что будет, если запросить статью, которой в базе нет?
Пустой массив

Мы получим пустой массив. Отлично, теперь давайте попробуем обработать эти две ситуации.

public function view(int $articleId)
{
    $result = $this->db->query(
        'SELECT * FROM `articles` WHERE id = :id;',
        [':id' => $articleId]
    );

    if ($result === []) {
        // Здесь обрабатываем ошибку
        return;
    }

    $this->view->renderHtml('articles/view.php', ['article' => $result[0]]);
}

Добавим шаблон для вывода одной статьи:

templates/articles/view.php

<?php include __DIR__ . '/../header.php'; ?>
    <h1><?= $article['name'] ?></h1>
    <p><?= $article['text'] ?></p>
<?php include __DIR__ . '/../footer.php'; ?>

И проверим, что всё ок.
Вывод статьи в шаблоне

Пришла пора добавить шаблон для страницы с ошибкой, когда что-то не найдено. Создадим ещё один шаблончик.

templates/errors/404.php

<h1>Страница не найдена</h1>

И будем подключать этот шаблон для случаев, когда наша статья не нашлась.

src/MyProject/Controllers/ArticlesController.php

...
public function view(int $articleId)
{
    $result = $this->db->query(
        'SELECT * FROM `articles` WHERE id = :id;',
        [':id' => $articleId]
    );

    if ($result === []) {
        $this->view->renderHtml('errors/404.php');
        return;
    }

    $this->view->renderHtml('articles/view.php', ['article' => $result[0]]);
}
...

Попробуем теперь открыть страничку с несуществующей статьёй.
Страница не найдена

Однако, просто так написать о том, что страница не найдена не верно. Важно при этом вернуть код ответа для страницы, который даст понять поисковым системам, что эту страницу индексировать не нужно. Если мы откроем панель разработчика в Google Chrome и перезагрузим страничку, мы увидим, что текущий код ответа – 200. Это стандартный код ответа, говорящий о том, что со страничкой всё хорошо.
200 код ответа

Нам же нужно вернуть код 404 – он говорит о том, что страница не найдена. Задать код ответа можно при помощи функции http_response_code(). В качестве аргумента ей передаётся код, который нужно вернуть.

Давайте отредактируем наш метод renderHtml() в классе View. Добавим возможность передавать код ответа.

src/MyProject/View/View.php

<?php

namespace MyProject\View;

class View
{
    private $templatesPath;

    public function __construct(string $templatesPath)
    {
        $this->templatesPath = $templatesPath;
    }

    public function renderHtml(string $templateName, array $vars = [], int $code = 200)
    {
        http_response_code($code);
        extract($vars);

        ob_start();
        include $this->templatesPath . '/' . $templateName;
        $buffer = ob_get_contents();
        ob_end_clean();

        echo $buffer;
    }
}

По умолчанию, если мы не передадим третьим аргументом код, будет возвращён 200-ый, иначе – заданный нами.

Остаётся только добавить передачу нужного кода в том месте, где мы вызываем рендеринг страницы с ошибкой.

src/MyProject/Controllers/ArticlesController.php

<?php

namespace MyProject\Controllers;

use MyProject\Services\Db;
use MyProject\View\View;

class ArticlesController
{
    /** @var View */
    private $view;

    /** @var Db */
    private $db;

    public function __construct()
    {
        $this->view = new View(__DIR__ . '/../../../templates');
        $this->db = new Db();
    }

    public function view(int $articleId)
    {
        $result = $this->db->query(
            'SELECT * FROM `articles` WHERE id = :id;',
            [':id' => $articleId]
        );

        if ($result === []) {
            $this->view->renderHtml('errors/404.php', [], 404);
            return;
        }

        $this->view->renderHtml('articles/view.php', ['article' => $result[0]]);
    }
}

Снова проверяем, обновив страничку.
404 код ответа

Вжух! Получили нужный код ошибки.

Итак, мы с вами сделали вывод списка статей и вывод каждой статьи отдельно. Уже немало. Давайте теперь сделаем ссылки на странице со списком, которые будут вести на отдельную статью. Для этого нам нужно только немного поправить шаблон.

templates/main/main.php

<?php include __DIR__ . '/../header.php'; ?>
<?php foreach ($articles as $article): ?>
    <h2><a href="/articles/<?= $article['id'] ?>"><?= $article['name'] ?></a></h2>
    <p><?= $article['text'] ?></p>
    <hr>
<?php endforeach; ?>
<?php include __DIR__ . '/../footer.php'; ?>

Зайдём в корень нашего сайтика и увидим, что теперь мы можем переходить по каждой статье отдельно.
Ссылки на статьи

На этом данный урок заканчивается, а в следующих мы научимся работать с базой данных, получая из неё объекты, а не массивы.

Текущая версия проекта на гитхабе.

loader
30.06.2023 в 07:57
18505
+1045
Домашнее задание

В экшне ArticlesController::view() после получения статьи, добавьте ещё один запрос на получение автора этой статьи из таблицы users. Выведите nickname автора в шаблоне.

Комментарии
Этот урок набрал набрал достаточно большое количество комментариев и дальнейшее его комментирование отключено. Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку, посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали. Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Логические задачи с собеседований