Фронт-контроллер и роутинг в PHP

Фронт-контроллер и роутинг в PHP

В прошлом уроке мы добавили в контроллер 2 экшена и стали проверять в index.php GET-параметр. В зависимости от этого параметра мы решали, какой из экшенов вызвать и что передать в качестве аргументов. А что будет, когда нам на сайте понадобится более 100 страниц? Для каждого добавлять if? Согласитесь, неудобно. В этом уроке мы сделаем удобную систему для обработки адресов сайта – роутинг (от англ. routing - маршрутизация).

Если вы не работали ранее с регулярными выражениями – пройдите урок по регуляркам в PHP.

Apache RewriteEngine

Для начала немного магии. Создайте в директории www файл .htaccess и запишите в него следующее содержимое:

Код доступен только после покупки курса ООП в PHP.

Это – специальный файл с конфигурацией для веб-сервера Apache. Если забыли – то это именно он обрабатывает запросы от пользователя и передаёт их дальше интерпретатору PHP. Подробнее об этом читайте в статье "как работает PHP". Когда Apache находит в директории файл с именем .htaccess он понимает, что это его конфиг и применяет его для директории, в которой этот конфиг лежит (и для вложенных директорий тоже).

RewriteEngine – это такой механизм в сервере Apache, который позволяет перенаправлять запросы. А теперь давайте рассмотрим каждую строку файла отдельно.

  • RewriteEngine On – включаем режим перенаправления запросов
  • RewriteCond %{SCRIPT_FILENAME} !-d – если в директории есть папка, соответствующая адресу запроса, то отдать её в ответе
  • RewriteCond %{SCRIPT_FILENAME} !-f – если в директории есть файл, соответствующий адресу запроса, то вернуть его в ответе
  • RewriteRule ^(.)$ ./index.php?route=$1 [QSA,L] – если файл или папка не найдены, то для такого запроса выполнится этот пункт. В таком случае веб-сервер перенаправить этот запрос на скрипт index.php. При этом скрипту будет передан GET-параметр route со значением запрошенного адреса. $1 – это значение, выдернутое с помощью регулярки по маске ^(.)$. То есть вся адресная строка будет передана в этот GET-параметр.

Давайте теперь это проверим. Откроем в браузере адрес http://myproject.loc/abracadabra.

Опа! Видим текст «Главная страница». Значит мы попали на index.php. Давайте теперь попробуем в index.php вывести GET-параметр route. Уберём пока код, добавленный на предыдущих уроках и оставим только автозагрузку классов и вывод этого GET-параметра.

Код доступен только после покупки курса ООП в PHP.

Снова откроем тот же адрес http://myproject.loc/abracadabra и увидим следующее:

Код доступен только после покупки курса ООП в PHP.

Давайте попробуем другой адрес - http://myproject.loc/hello/username

Код доступен только после покупки курса ООП в PHP.

ЧПУ

Такие адреса через слэши называются ЧПУ – Человеко Понятные УРЛы. То есть адреса, которые нормально воспринимаются человеком.
Согласитесь
http://myproject.loc/hello/username
лучше чем
http://myproject.loc/?action=hello&name=username

На таких ЧПУ-адресах мы и будем разрабатывать нашу систему.

Роутинг

Ну а теперь мы научимся обрабатывать такие адреса красивым и простым способом – с помощью регулярных выражений.

Для начала давайте сделаем по-простому – с помощью регулярки научимся понимать, что текущий адрес: http://myproject.loc/hello/ , где - вообще любая строка.

Код доступен только после покупки курса ООП в PHP.

Обратите внимание – в качестве ограничителя шаблона регулярного выражения мы использовали тильду - ~. Мы выбрали её вместо слэша, чтобы не экранировать слэш в адресной строке. Напомню, что в качестве ограничителя может выступать вообще любой символ.

Перейдём по адресу http://myproject.loc/hello/username и увидим наши совпадения по регулярке:

Код доступен только после покупки курса ООП в PHP.

Нулевой элемент – полное совпадение по паттерну. Первый элемент – значение, попавшее в маску (.*), то есть всё, что идёт после hello/.

Давайте теперь добавим проверку того, что если $matches не пустой, то будем создавать контроллер MainController и вызывать у него экшен hello. В качестве аргумента будем передавать ему значение из массива по ключу 1.

Код доступен только после покупки курса ООП в PHP.

Посмотрим, что получилось.

Код доступен только после покупки курса ООП в PHP.

Отлично! Давайте теперь добавим обработку случая, когда мы просто зашли на http://myproject.loc/. В таком случае переменная route будет пустой строкой. Регулярка для такого случая - ^$. Да, просто начало строки и конец строки. Проще не бывает!

Код доступен только после покупки курса ООП в PHP.

Перейдём теперь на страницу http://myproject.loc/ и увидим сообщение «Главная страница».
Остаётся только добавить обработку случая, когда ни одна из этих регулярок не подошла и просто вывести сообщение о том что страница не найдена.

Давайте просто добавим в конце index.php строку:

Код доступен только после покупки курса ООП в PHP.

И проверим, что всё работает, перейдя по любому другому адресу: http://myproject.loc/blabla.

А теперь давайте посмотрим на получившийся код и подумаем, нет ли здесь чего-то общего? Да конечно есть! В обоих случаях мы проверяем регулярки и в зависимости от совпадения создаём нужный контроллер, с нужным методом и нужными аргументами. Но механизм-то одинаковый! Значит, будем делать универсальную систему роутинга.

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

Давайте создадим отдельный файл с такой конфигурацией. Пусть это будет файл src/routes.php. Запишем в него следующее содержимое:
src/routes.php

Код доступен только после покупки курса ООП в PHP.

То есть это просто массив, у которого ключи – это регулярка для адреса, а значение – это массив с двумя значениями – именем контроллера и названием метода.

Теперь вернёмся в index.php и научимся обрабатывать этот файл. Для начала давайте просто положим этот массив в отдельную переменную.

Код доступен только после покупки курса ООП в PHP.

Результат:

Код доступен только после покупки курса ООП в PHP.

Что с этим делать? Да просто пробежаться по нему foreach-ом и найти соответствие по регулярке для текущего адреса. Как только совпадение найдено, нужно остановить перебор. Звучит несложно. Давайте сделаем это!

Код доступен только после покупки курса ООП в PHP.

Я завел также специальную переменную $isRouteFound – на случай, если совпадений не было найдено, она останется false, как и до перебора. В таком случае мы выведем сообщение о том, что страница не найдена и завершим работу скрипта. В противном случае – выведем значение переменных $controllerAndAction и $matches.

Давайте проверим случай, когда нужный роут не найден - http://myproject.loc/blabla

Код доступен только после покупки курса ООП в PHP.

Всё правильно. Давайте теперь вернёмся на http://myproject.loc/

Код доступен только после покупки курса ООП в PHP.

Видим, что у нас есть имя нужного контроллера и имя метода. Всё, этого достаточно. Вот так это делается в PHP:

Код доступен только после покупки курса ООП в PHP.

Да! Прямо вот так! Переменную можно использовать в качестве имени класса при создании объекта, и даже вместо имени метода!

Зайдите на http://myproject.loc/ и убедитесь, что всё прекрасно работает.

Но у нас осталась еще проблема с аргументами для методов.

Давайте вернёмся к предыдущему варианту кода, где мы просто вывели значения переменных:

Код доступен только после покупки курса ООП в PHP.

И перейдём по адресу http://myproject.loc/hello/username
Результат:

Код доступен только после покупки курса ООП в PHP.

Видим что у нас так же есть имя контроллера и имя метода. А также нужный нам аргумент в массиве $matches.

Но мы видим, что нужные нам аргументы всегда будут только после нулевого элемента, так как в нём лежит полное совпадение по паттерну. Не беда – просто удаляем этот ненужный элемент:

Код доступен только после покупки курса ООП в PHP.

Получаем следующую картину:

Код доступен только после покупки курса ООП в PHP.

Остаётся только один вопрос – как элементы массива передать в аргументы метода? Для этого в PHP есть специальный оператор троеточия:

Код доступен только после покупки курса ООП в PHP.

Он передаст элементы массива в качестве аргументов методу в том порядке, в котором они находятся в массиве.

Теперь доводим до ума наш скрипт:

Код доступен только после покупки курса ООП в PHP.

Переходим по адресу http://myproject.loc/hello/username и видим что всё работает!

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

Ах да, наш index.php - скрипт, в котором происходит обработка входящих запросов и создаются другие контроллеры, называется фронт-контроллером.