Для демонстрации работы проекта во время лекции, я использовал OpenServer. В нём нужно добавить эту папку (conference.backend)
внутрь директории domains
и добавить в файл hosts
запись вида: 127.0.0.1 conference.backend
Файл hosts
на OS Windows находится тут:
C:\Windows\System32\drivers\etc
После этих действий, нужно перезапустить OpenServer, тогда обращение к домену http://conference.backend
будет обращаться к нашему серверу.
Люди, которые умеют запускать Apache сервер без OpenServer в предыдущей инструкции не нуждаются ;-)
Для корректного поддержания роутинга в нашем бекенд приложении, необходимо внести изменения в файл .htaccess
, чтобы сервер передавал роут и метод запроса в суперглобальный массив $_GET
в едином обработчике index.php
.
Для этого наш файл .htaccess
содержит следующий блок:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) /index.php?method=%{REQUEST_METHOD}&page=$1 [QSA,L]
</IfModule>
Здесь мы делаем следующее:
RewriteCond %{REQUEST_FILENAME} !-f
- предотвращаем чтение файлов через адресную строкуRewriteCond %{REQUEST_FILENAME} !-d
- предотвращаем чтение директорий через адресную строкуRewriteRule (.*) /index.php?method=%{REQUEST_METHOD}&page=$1 [QSA,L]
- эта строка творит основную магию, она перенаправляет все запросы на нашindex.php
файл, куда вquery-параметры
подставляет метод запроса в полеmethod=
, а в полеpage=
подставляет страницу, к которой мы обращались
Эти параметры будут доступны в файле index.php
в массиве $_GET['method']
и $_GET['page']
.
В приложении мы используем обрабатываем лишь два вида URL:
http://conference.backend/users
http://conference.backend/news
Соответственно, если мы обращаемся к первому URL, то $_GET['page']
будет равно строке 'users'
, если ко второму, то 'news'
.
Наш сервер перенаправляет все запросы на единую точку входа в приложение, на файл index.php
. В самом начале файла мы проставляем HTTP-заголовки, нужные для корректной работы с нашим Фронтендом.
Первые два заголовка нужны для того, чтобы защита браузера не мешала обращаться к нашему бекенду (Access-Control-Allow-Origin: *
), при чём здесь звёздочка означает вообще все возможные домены. В реальных приложениях лучше ограничивать только своими доменами или вообще чтобы бекенд и фронтенд находились на одном домене.
Следующий заголовок позволяет защите браузера корректно обрабатывать запросы с перечисленными методами. Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
означает, что браузер не будет препятствовать при совершении запросов с этими методами к нашему серверу.
Далее, весь код обёрнут в конструкцию
try {
...
} catch (...) {
...
}
Это сделано для того, чтобы обрабатывать непредвиденные ошибки на сервере. Если возникает какая-то непредвиденная ошибка в коде, то наш интерпретатор попадает в блок catch
, который проставляет код ответа 500
и возвращает строку 'Internal server error'
.
С помощью этого можно будет понять, что возникла ошибка именно сервера, а не ошибка в запросе к серверу.
Схожие конструкции можно увидеть далее в коде сервера повсеместно, я не буду их ещё раз описывать. Они могут содержать коды 400
, 404
и соответствующие коды ошибок.
После строки `exit();` скрипт прекращает своё действие!
Поэтому, эта функция используется, чтобы обрабатывать ошибки.
Внутри блока try {...}
сперва идёт как раз таки подобная проверка, она смотрит, был ли вообще добавлен какой-то роут, если нет, возвращает ошибку 404.
После этого, мы сохраняем название роута в переменную $route
и инициализируем переменную $data
/, куда мы запишем данные, полученные после обработки запроса и вернём их на клиент.
После этого следует конструкция switch ()...case
, которая заменяет множественный вызов if() {...} else if () {...} else {}
, где мы обрабатываем каждый метод запроса по-своему.
В конце файла index.php
мы проверяем, появилось ли что-то в переменной $data
, если да, возвращаем это. Данные в эту переменную записываются с помощью обработчиков, находящихся в файле functions.php
.
-
GET http://conference.backend/users
- получить всех пользователей -
GET http://conference.backend/users?id=1
- получить пользователя с id=1 -
POST http://conference.backend/users?login=логин&name=имя&password=пароль
- добавить пользователя, передав логин, имя и пароль -
DELETE http://conference.backend/users?id=1
- удалить пользователя с id=1 -
GET http://conference.backend/news
- получить все новости -
GET http://conference.backend/news?id=1
- получить новости с id=1 -
POST http://conference.backend/news?title=заголовок&text=текст&author=автор
- добавить новость, передав заголовок, текст и имя автора -
DELETE http://conference.backend/news?id=1
- удалить новость с id=1
В настоящих приложениях мы конечно бы использовали систему управления базами данных, но мы сейчас находимся на самом начальном уровне абстракции и упрощаем наше приложение. Поэтому, все данные хранятся в формате JSON
и находятся в папке ./data/.
Далее, здесь вы можете добавить сколько необходимо файлов с данными, чтобы масштабировать своё приложение.
Все наши функции-обработчики и вспомогательные функции находятся в файле functions.php
. Здесь находятся обработчик GET-запросов, обработчик POST-запросов, обработчик DELETE-запросов и вспомогательные функции.
Начну с описания вспомогательных функций, т.к. они используются в функциях-обработчиках.
Здесь присутствуют всего лишь две вспомогательные функции:
string makeRoutePath(string $route)
- функция принимает 1 аргумент (строку) и делает путь до нашей директорииdata
. При этом, роутом должно быть название нужного файла без расширения (без.json
в конце). Возвращает функция строку, являющуюся абсолютным путём до директории с файлами. При этом, мы используем суперглобальную переменную__DIR__
, с помощью которой нам не важно, какая OS используется на сервере, путь будет корректным.mixed array_find(array $haystack, callable $callback)
- функция принимает два аргумента, это массив$haystack
, в котором мы будем что-то искать, и функция$callback
, которая будет следить за корректностью поиска. Внутри функции мы перебираем входящий массив с помощью циклаforeach
, внутри мы выполняем нашу функцию$callback
и делаем проверку, если функция вернула истину, мы возвращаем элемент, на котором находимся, прерывая выполнение цикла. Это значит, что функция$callback
проверяет соответствие элемента нашему сложному условию, находящемуся в функции.
Этот обработчик должен брать данные на сервере и возвращать их.
Функция array getData(string $route, array $params)
принимает два аргумента, это $route
, по которому мы будем искать, и $params
- прочие параметры, которые мы будем передавать в обработчик.
В самом начале функции мы создаём путь до файла с данными по переданному роуту.
Далее следует проверка на существование этого файла и, если его нет, то мы возвращаем ошибку 404
с текстом Not found
.
Далее, мы конвертируем полученный JSON-файл с данными в php-массив и делаем проверку на существование поля id
в переданном массиве $params
. Если он там есть, то это означает, что запрос был выполнен на сервер таким образом:
http://conference.backend/news?id=1
(например id=1).
Это будет означать, что нужно вернуть только новость с id 1, иначе - вернуть все.
В настоящих приложениях с большими данными обычно есть такие переменные, как `limit` и `offset`.
Переменная лимит указывает сколько записей возвращать за один запрос, а оффсет - откуда стоит начать.
Чтобы понять, как оно должно работать - представьте себе "Пагинацию" любого интернет-магазина.
Этот обработчик должен добавлять новые записи в наше хранилище.
Функция array postData(string $route, array $params)
- принимает такие же аргументы, как и getData()
. Начало функции выполняет такие же действия - получает путь до нужного файла и проверяет его существование. Дальше - преобразует полученный JSON-файл в php-массив.
После этого инициализируется переменная $newData
, куда будут добавлены данные для записи в файл. После этого происходит обработка роутов.
Такое разделение присутствует потому, что для каждой сущности (новость, пользователь, товар, машина, запись, комментарий и т.п.) будет своя структура, в которой они будут храниться.
Каждый обработчик делает следующее:
- В самом начале проверяет наличие всех необходимых полей, переданных в переменной
$params
для новостей тут, для пользователей тут и, если чего-то не хватает, возвращает ошибку400
, обозначающую проблему в запросе. - Записывает в переменную
$newData
новые данные для записи
После этих двух действий, наш обработчик выйдет из конструкции switch ()...case
и создаст новый id для новой записи, взяв id
последней записи в массиве $data
и прибавив к нему 1.
Далее, новые добавляются в конец массива $data
и происходит запись в файл, где хранятся эти данные.
В конце обработчика проверяется корректность записи в файл и возвращаются на клиент новые данные.
Обратите внимание, что новые данные вернутся с новым id, а с клиента id не должен приходить!
Этот обработчик должен удаляет записи из хранилища по id.
Начало обработчика аналогично предыдущим двум обработчикам. Далее, происходит поиск той записи, которую нужно удалить с помощью нашей вспомогательной функции array_find()
и запись сохраняется в переменную $dataToDelete
.
Поиск записи, которую нужно удалить, происходит для того, чтобы далее мы могли проверить, а нужно ли вообще что-то удалять и сделать проверку.
После этого, мы фильтруем наш исходный массив с помощью встроенной в php функции array_filter()
и обновленныё результат записываем в файл.
Этих функций должно хватить, чтобы можно было построить приложение, способное управлять данными так, как нам это нужно. Обычно добавляют ещё два метода PUT
, для полной перезаписи существующих данных, и PATCH
для частичной записи данных в ту или иную сущность.
Так же, используют СУБД и проверку авторизации. С помощью нашей не сложной архитектуры, можно с лёгкостью добавить проверки аутентификации пользователей в файле index.php
.
Писать бекенд на php было выбрано не случайно, ведь вы с лёгкостью сможете найти хостинг, на котором настроен php сервер и можете утомиться искать хостинг на node.js\python\ruby\java\etc...
Чтобы начать пользоваться приложением в реальной жизни, достаточно арендовать любой хостнг, с сервером Apache, поддерживающий PHP и загрузить файлы бекенда туда, привязав к ним какой-нибудь IP или домен.