Найти тему
ZDG

Разрабатываем сайт #6: CRUD

Предыдущие части: Форма редактирования, PHP и база данных, Идентификаторы, Работаем с таблицами, Создаём базу данных, Установка MySQL

Читайте также: Как работает веб-сайт, Бэкэнд на PHP, Введение в язык PHP

В прошлой части мы сделали форму для добавления записи в базу. Для полноценной работы осталось добавить:

  • просмотр всех записей
  • просмотр одной записи
  • редактирование существующей записи
  • удаление записи

Что такое CRUD

Буквы CRUD означают Create, Read, Update, Delete – "создать", "читать", "обновить", "удалить". Это не просто буквы, а идеология проектирования пользовательского интерфейса и серверного бэкенда.

В то же время это не какие-то правила, а просто логика обмена с сервером. Она может быть разной, но должна оставаться именно логикой.

Наш файл note_update.php добавляет новую запись, и в соответствии с формулой CRUD следовало бы назвать его note_create.php. И остальные файлы на сервере назывались бы так: note_update.php, note_read.php, note_delete.php. За каждым CRUD-действием мы обращались бы к файлу с соответствующим именем.

Необходимо понимать, что названия файлов ничего не значат, мы выбираем их исходя из своей логики. Но они могут играть роль. Когда мы действительно поддерживаем CRUD во всём проекте, то нам достаточно знать только имя объекта и действие, которое надо с ним совершить. Если объект называется note, то мы можем, например, автоматически сгенерировать для него ссылки: note_create.php, note_read.php, note_update.php, note_delete.php.

CRUD-контроллер

Каждый отдельный файл из примера выше тоже можно назвать контроллером (см. шаблон проектирования MVC). Но каждый из них совершает только одно действие, а в данном случае идея в том, чтобы один контроллер мог совершать все CRUD-действия над одной моделью данных.

Но как с одним контроллером понять, какое именно действие требуется совершить?

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

action=update

Разбирая полученный POST-запрос, контроллер проанализирует параметр action и поймёт, какое действие требуется.

Впрочем, ничто не мешает передавать действие и через GET-запрос:

note_controller.php?action=update

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

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

Делаем контроллер

Давайте не будем называть его note_controller.php, а сделаем папку controllers, а в ней уже файл note.php. Тогда URL для контроллера заметок будет выглядеть так:

http://catalog/controllers/note.php

И в этой же папке мы сможем размещать все остальные контроллеры, которые появятся позже.

Составим список действий, которые поддерживаются контроллером:

view, update, delete

Почему не create, read, update, delete? Мы не обязаны буквально следовать определениям CRUD, мы должны только поддерживать выбранную логику. В данном случае нам не нужно действие create. Достаточно только update. Логика простая: если редактируется запись с пустым id, значит её нет в базе и она будет создана, если же id заполнен, то эта запись уже существует и её надо обновить.

Возможен такой случай: в результате какого-то сбоя пришёл запрос на update с пустым id. Тогда будет создана новая запись, хотя не должна была. Тогда нам бы действительно помогло отдельное действие сreate. Для действия update всегда требовался бы непустой id. Но мы сейчас ничем не рискуем и поэтому такие случаи не рассматриваем.

А вместо read будет view. Никакой разницы, просто так привычнее. Это просмотр одной записи.

Напишем логику обработки действий.

Мы сразу получаем из $_GET значения для $action и $id. Их там может и не быть, и при обращении, скажем, к $_GET['action'] возникнет ошибка. Поэтому такие обращения оборачиваем в функцию get_param(), которая предварительно проверяет наличие параметров с помощью isset(). Чтобы функция была более универсальной, т.е. работала не только с $_GET, но и с любым массивом, мы сделали ей параметр $data, в котором нужно искать ключ $name.

Далее проверяется значение $action и вызывается соответствующая функция. Если же совпадений не найдено, то вызываем функцию по умолчанию: index().

Пока что эти функции не написаны, но в данном куске кода мы сделали всё, что требуется. Теперь мы можем писать функции по одной.

Начнём с index(). Во-первых, это функция по умолчанию, которая будет работать сразу, во-вторых, нам нужно делать HTML-страницы для отображения данных. Каждая страница будет представлением (view) шаблона MVC, и вот на примере index() мы и разберёмся.

-2

Функция index() очень проста: она получает из базы список записей таблицы note, и вызывает функцию render() для отображения этого списка. Всё.

Функция render(), в свою очередь, включает в себя внешний файл. Этот механизм надо разобрать подробней.

В PHP есть два способа включить в код другой код: include() и require(). Работает элементарно: в том месте, где вы написали, например,

include('cat.php');

Будет взят текст из файла cat.php и помещён прямо в это место. И ваша программа начнёт его выполнять. Разница между include() и require() в том, что если файл не найден, то include() выдаст предупреждение, а require() выдаст фатальную ошибку.

Посмотрим на картинку ещё раз:

-3

Итак, index() вызвала render() и передала туда три параметра: 'main', 'index', $resultset. С точки зрения render() первый параметр это контейнер ($container). Что за контейнер, и что он должен содержать? Сейчас посмотрим. Так как $container на данный момент равен 'main', то включится файл

../views/main.php

И нужно переходить к нему.

Представления

Сделаем папку views на одном уровне с папкой controllers. Здесь будут храниться представления. Файл main.php будет содержать каркас типовой HTML-страницы:

-4

Здесь всё по минимуму. Именно этот текст будет включён в функцию render(). Что при этом произойдёт? Так как это не PHP-код, он находится вне тэгов <?php ... ?>. А всё, что находится вне, просто отправляется на вывод в браузер. Значит, браузер получит вот такую HTML-страницу. В которой ничего нет. Или есть?

Присмотревшись, мы видим, что этот текст также содержит следующую строчку:

<?php require("$view.php"); ?>

Это значит, что в этом месте снова начинается PHP-код, и мы внутри этого включаемого файла делаем включение ещё одного файла.

-5

Что же мы включаем? Файл, который задан переменной $view. А откуда мы её возьмём? А посмотрите на параметры функции render(): там есть $container и $view. Так как мы включаем текст контейнера изнутри функции, то все параметры нам доступны. И как мы помним, параметр $view сейчас равен 'index'. То есть контейнер main.php сейчас включит в себя файл index.php. И вот в нём уже происходит вывод списка записей:

-6

Данный HTML-код не имеет положенного заголовка, так как встраивается в контейнер main.php (собственно, поэтому он и контейнер). Иначе говоря, контейнер содержит шаблон пустой HTML-страницы, а содержание страницы добавляется путём включения одного из представлений.

Список записей это переменная $data , которая также получена в качестве параметра функцией render() и поэтому доступна.

Вкратце поясню HTML-код: мы выводим таблицу (<table>). Таблица состоит из строк (<tr>), а строки состоят из столбцов (<td>). По структуре текста это видно, надеюсь.

Первая строка таблицы содержит заголовки, поэтому там вместо <td> используем <th> – это как <td>, только со стилем шрифта для заголовков.

После заголовочной строки мы выводим в цикле столько строк, сколько записей в $data. Для этого используем PHP-конструкцию foreach() с альтернативным синтаксисом: вместо открывающей фигурной скобки "{" ставится двоеточие, а вместо закрывающей "}" ставится endforeach. Оба варианта равноправны, такой считается более читабельным посреди HTML-кода.

Не забывайте о том, что я говорил раньше: может показаться, что это HTML-код, в который включены инструкции PHP. Но нет. Это программа на PHP, и больше ничего. Всё, что находится вне тэгов <?php ... ?>, просто отправляется на вывод, то есть равносильно тому, чтобы написать на PHP:

echo "<table><tr><th>ID</th><th>... ... ... ...";

В таком виде вы бы сразу поняли, что перед вами именно программа. Но это именно она и есть. Просто, ради вашего же удобства, не надо писать echo. А вставки <?php ... ?> мы делаем только там, где надо управлять выполнением программы.

Кроме управляющей конструкции foreach(), мы используем PHP-вставки для вывода записей, но они выглядят немного по-другому:

<?= $note['id'] ?>

Это равносильно такой записи:

<?php echo $note['id'] ?>

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

-7

Пока топорно, но мы потом сделаем стили по красоте. Это совершенно отдельная тема. Главное сейчас – чтобы работал функционал.

Обратите внимание на три последних столбца в каждой строке – там содержатся ссылки на просмотр, редактирование и удаление каждой записи. С уже правильными action и id в URL. Правда, воспользоваться ими мы пока не можем, так как соответствующие функции не написаны. Но мы сегодня задали основу, так что остальное пойдёт уже по рельсам.

Повторим, что произошло:

Функция render(), получив имя контейнера, имя представления и данные, включила в себя файл-контейнер. Контейнер, в свою очередь, включил в себя файл-представление. Мы получили финальный текст HTML-страницы, составленный из фрагментов. Весь этот текст пошёл на вывод в браузер. В некоторых местах в поток вывода с помощью PHP были вставлены данные из базы. В результате браузер получил HTML-страницу и показал её.

Читайте дальше: Update