Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Класс-репозиторий

Запросы SQL, разбросанные по действиям, трудно находить и менять. Их собирают в один класс — репозиторий: у него методы вроде «все статьи» и «статья по номеру», а остальной код не знает, что за ними стоит SQL. Этот класс заменяет модель из больших фреймворков.

Где лежит

Репозитории кладут в src/Repository/. Класс PostRepository там уже есть — с массивом вместо базы. Перепишите его.

Чтение списка

// src/Repository/PostRepository.php
namespace App\Repository;
 
use PDO;
 
final class PostRepository
{
    public function __construct(private PDO $pdo) {}
 
    public function all(): array
    {
        return $this->pdo
            ->query('SELECT id, title, created_at FROM posts ORDER BY id DESC')
            ->fetchAll();
    }
}

Объект PDO приходит через конструктор: контейнер создаёт его по описанию из прошлого урока. Действие ListPostsAction менять не нужно — оно по-прежнему вызывает all(). Откройте /posts: список теперь приходит из базы.

Одна запись

Значения от пользователя никогда не вклеивают в текст запроса. Вместо них ставят знак вопроса, а значение передают отдельно — это подготовленный запрос:

// src/Repository/PostRepository.php
public function find(int $id): ?array
{
    $statement = $this->pdo->prepare('SELECT * FROM posts WHERE id = ?');
    $statement->execute([$id]);
 
    return $statement->fetch() ?: null;
}

Метод возвращает массив с полями статьи или null, если строки с таким номером нет.

Использование в действии

// src/Action/ShowPostAction.php
namespace App\Action;
 
use App\Repository\PostRepository;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
use Slim\Views\Twig;
 
final class ShowPostAction
{
    public function __construct(private PostRepository $posts, private Twig $twig) {}
 
    public function __invoke(Request $request, Response $response, array $args): Response
    {
        $post = $this->posts->find((int) $args['id']);
 
        if ($post === null) {
            throw new HttpNotFoundException($request);
        }
 
        return $this->twig->render($response, 'posts/show.twig', ['post' => $post]);
    }
}
{# templates/posts/show.twig #}
{% extends 'layout.twig' %}
 
{% block content %}
    <h1>{{ post.title }}</h1>
    <p>{{ post.body }}</p>
{% endblock %}

Адрес /posts/1 покажет статью, а /posts/999 — страницу 404: исключение HttpNotFoundException перехватывает посредник ошибок.

Добавление записи

// src/Repository/PostRepository.php
public function create(string $title, string $body): int
{
    $statement = $this->pdo->prepare('INSERT INTO posts (title, body) VALUES (?, ?)');
    $statement->execute([$title, $body]);
 
    return (int) $this->pdo->lastInsertId();
}

Метод возвращает номер новой строки. Он понадобится в уроке о формах.

Готовые библиотеки

Когда запросов становится много, вместо ручного SQL подключают библиотеку. Eloquent, ORM из Laravel, ставится пакетом illuminate/database и работает отдельно от Laravel. Doctrine DBAL (doctrine/dbal) даёт построитель запросов без моделей. Обе подключают так же, как PDO: описывают в контейнере и получают через конструктор.

Частая ошибка

Запрос вида "SELECT * FROM posts WHERE id = " . $args['id'] открывает путь SQL-инъекции — посетитель может дописать к запросу свой SQL. Ограничение маршрута [0-9]+ не спасает: завтра его могут убрать. Любое значение извне передавайте через prepare() и execute().

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.