Маршрутизация
Примечание: Хотите узнать больше о маршрутизации? Посмотрите страницу "почему фреймворк?" для более детального объяснения.
Базовая маршрутизация в Flight осуществляется путем сопоставления шаблона URL с функцией обратного вызова или массивом класса и метода.
Flight::route('/', function(){
echo 'привет, мир!';
});
Маршруты сопоставляются в порядке их определения. Первый маршрут, который соответствует запросу, будет вызван.
Обратные вызовы/Функции
Функция обратного вызова может быть любым вызываемым объектом. Поэтому вы можете использовать обычную функцию:
function hello() {
echo 'привет, мир!';
}
Flight::route('/', 'hello');
Классы
Вы также можете использовать статический метод класса:
class Greeting {
public static function hello() {
echo 'привет, мир!';
}
}
Flight::route('/', [ 'Greeting','hello' ]);
Или создав объект сначала, а затем вызвав метод:
// Greeting.php
class Greeting
{
public function __construct() {
$this->name = 'Джон Доу';
}
public function hello() {
echo "Привет, {$this->name}!";
}
}
// index.php
$greeting = new Greeting();
Flight::route('/', [ $greeting, 'hello' ]);
// Вы также можете сделать это без создания объекта сначала
// Примечание: аргументы не будут переданы в конструктор
Flight::route('/', [ 'Greeting', 'hello' ]);
// Дополнительно вы можете использовать эту более короткую синтаксис
Flight::route('/', 'Greeting->hello');
// или
Flight::route('/', Greeting::class.'->hello');
Внедрение зависимостей через DIC (Контейнер для внедрения зависимостей)
Если вы хотите использовать внедрение зависимостей через контейнер (PSR-11, PHP-DI, Dice и т. д.), единственный тип маршрутов, где это доступно, это либо непосредственное создание объекта самостоятельно и использование контейнера для создания вашего объекта, либо вы можете использовать строки для определения класса и метода для вызова. Вы можете перейти на страницу Внедрение зависимостей для большей информации.
Вот краткий пример:
use flight\database\PdoWrapper;
// Greeting.php
class Greeting
{
protected PdoWrapper $pdoWrapper;
public function __construct(PdoWrapper $pdoWrapper) {
$this->pdoWrapper = $pdoWrapper;
}
public function hello(int $id) {
// сделать что-то с $this->pdoWrapper
$name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Привет, мир! Меня зовут {$name}!";
}
}
// index.php
// Настройте контейнер с любыми параметрами, которые вам нужны
// См. страницу Внедрения зависимостей для получения дополнительной информации о PSR-11
$dice = new \Dice\Dice();
// Не забудьте повторно присвоить переменной '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Зарегистрируйте обработчик контейнера
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Маршруты как обычно
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// или
Flight::route('/hello/@id', 'Greeting->hello');
// или
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Маршрутизация по методу
По умолчанию шаблоны маршрутов сопоставляются со всеми методами запроса. Вы можете реагировать на конкретные методы, поставив идентификатор перед URL.
Flight::route('GET /', function () {
echo 'Я получил GET запрос.';
});
Flight::route('POST /', function () {
echo 'Я получил POST запрос.';
});
// Вы не можете использовать Flight::get() для маршрутов, так как это метод
// для получения переменных, а не создания маршрута.
// Flight::post('/', function() { /* код */ });
// Flight::patch('/', function() { /* код */ });
// Flight::put('/', function() { /* код */ });
// Flight::delete('/', function() { /* код */ });
Вы также можете сопоставить несколько методов с одним обратным вызовом, используя разделитель |
:
Flight::route('GET|POST /', function () {
echo 'Я получил либо GET, либо POST запрос.';
});
Дополнительно вы можете получить объект Router, который имеет некоторые вспомогательные методы для вашего использования:
$router = Flight::router();
// сопоставляет все методы
$router->map('/', function() {
echo 'привет, мир!';
});
// GET запрос
$router->get('/users', function() {
echo 'пользователи';
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();
Регулярные выражения
Вы можете использовать регулярные выражения в своих маршрутах:
Flight::route('/user/[0-9]+', function () {
// Это будет соответствовать /user/1234
});
Хотя этот метод доступен, рекомендуется использовать именованные параметры или именованные параметры с регулярными выражениями, так как они более читаемы и проще в обслуживании.
Именованные параметры
Вы можете указать именованные параметры в своих маршрутах, которые будут переданы вашей функции обратного вызова. Это больше для читаемости маршрута, чем для чего-либо другого. Пожалуйста, смотрите раздел ниже о важной оговорке.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "привет, $name ($id)!";
});
Вы также можете включить регулярные выражения с вашими именованными параметрами, используя
разделитель :
:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// Это будет соответствовать /bob/123
// Но не будет соответствовать /bob/12345
});
Примечание: Не поддерживается сопоставление групп regex
()
с позиционными параметрами. :'(
Важная оговорка
Хотя в приведенном выше примере кажется, что @name
непосредственно связан с переменной $name
, это не так. Порядок параметров в функции обратного вызова определяет, что будет передано ей. Так что если вы измените порядок параметров в функции обратного вызова, переменные также изменятся. Вот пример:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "привет, $name ($id)!";
});
А если вы перейдете по следующему URL: /bob/123
, вывод будет привет, 123 (bob)!
.
Пожалуйста, будьте осторожны, когда настраиваете свои маршруты и функции обратного вызова.
Необязательные параметры
Вы можете указать именованные параметры, которые являются необязательными для сопоставления, обернув сегменты в круглые скобки.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// Это будет соответствовать следующим URL:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Любые необязательные параметры, которые не совпадают, будут переданы как NULL
.
Шаблоны
Сопоставление происходит только по отдельным сегментам URL. Если вы хотите сопоставить несколько
сегментов, вы можете использовать подстановочный знак *
.
Flight::route('/blog/*', function () {
// Это будет соответствовать /blog/2000/02/01
});
Чтобы направить все запросы на одну функцию обратного вызова, вы можете сделать следующее:
Flight::route('*', function () {
// Сделать что-то
});
Передача
Вы можете передать выполнение следующему соответствующему маршруту, вернув true
из вашей
функции обратного вызова.
Flight::route('/user/@name', function (string $name) {
// Проверьте некоторое условие
if ($name !== "Bob") {
// Продолжить следующий маршрут
return true;
}
});
Flight::route('/user/*', function () {
// Это будет вызвано
});
Псевдоним маршрута
Вы можете присвоить псевдоним маршруту, чтобы URL можно было динамически генерировать позже в вашем коде (например, как шаблон).
Flight::route('/users/@id', function($id) { echo 'пользователь:'.$id; }, false, 'user_view');
// позже в коде где-то
Flight::getUrl('user_view', [ 'id' => 5 ]); // вернет '/users/5'
Это особенно полезно, если ваш URL по каким-то причинам изменился. В приведенном выше примере, предположим, что пользователи были перемещены на /admin/users/@id
вместо этого.
С псевд naming в месте, вам не нужно менять там, где вы ссылаетесь на псевдоним, потому что псевдоним теперь будет возвращать /admin/users/5
, как в
примере выше.
Псевдоним маршрута все еще работает в группах:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'пользователь:'.$id; }, false, 'user_view');
});
// позже в коде где-то
Flight::getUrl('user_view', [ 'id' => 5 ]); // вернет '/users/5'
Информация о маршруте
Если вы хотите просмотреть информацию о соответствии маршрута, вы можете запросить, чтобы объект маршрута был передан вашей функции обратного вызова, передав true
в качестве третьего параметра в методе маршрута. Объект маршрута всегда будет последним параметром, переданным вашей функции обратного вызова.
Flight::route('/', function(\flight\net\Route $route) {
// Массив HTTP методов, с которыми было сопоставлено
$route->methods;
// Массив именованных параметров
$route->params;
// Сопоставление регулярного выражения
$route->regex;
// Содержит содержимое любого '*' использованного в шаблоне URL
$route->splat;
// Показывает путь URL....если вам это действительно нужно
$route->pattern;
// Показывает, какому промежуточному программному обеспечению это назначено
$route->middleware;
// Показывает псевдоним, назначенный этому маршруту
$route->alias;
}, true);
Группировка маршрутов
Могут быть случаи, когда вы хотите сгруппировать связанные маршруты вместе (например, /api/v1
).
Вы можете сделать это, используя метод group
:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Соответствует /api/v1/users
});
Flight::route('/posts', function () {
// Соответствует /api/v1/posts
});
});
Вы даже можете вложить группы групп:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() получает переменные, он не устанавливает маршрут! См. контекст объекта ниже
Flight::route('GET /users', function () {
// Соответствует GET /api/v1/users
});
Flight::post('/posts', function () {
// Соответствует POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Соответствует PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() получает переменные, он не устанавливает маршрут! См. контекст объекта ниже
Flight::route('GET /users', function () {
// Соответствует GET /api/v2/users
});
});
});
Группировка с контекстом объекта
Вы все еще можете использовать группировку маршрутов с объектом Engine
следующими способами:
$app = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {
// используйте переменную $router
$router->get('/users', function () {
// Соответствует GET /api/v1/users
});
$router->post('/posts', function () {
// Соответствует POST /api/v1/posts
});
});
Ресурсная маршрутизация
Вы можете создать набор маршрутов для ресурса, используя метод resource
. Это создаст
набор маршрутов для ресурса, который соответствует RESTful соглашениям.
Чтобы создать ресурс, сделайте следующее:
Flight::resource('/users', UsersController::class);
И что произойдет в фоновом режиме, так это то, что он создаст следующие маршруты:
[
'index' => 'GET ',
'create' => 'GET /create',
'store' => 'POST ',
'show' => 'GET /@id',
'edit' => 'GET /@id/edit',
'update' => 'PUT /@id',
'destroy' => 'DELETE /@id'
]
И ваш контроллер будет выглядеть так:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
Примечание: Вы можете просмотреть вновь добавленные маршруты с помощью
runway
, запустивphp runway routes
.
Настройка ресурсных маршрутов
Существует несколько вариантов настройки ресурсных маршрутов.
Псевдоним базы
Вы можете настроить aliasBase
. По умолчанию псевдоним является последней частью указанного URL.
Например, /users/
приведет к aliasBase
равному users
. Когда эти маршруты созданы,
псевдонимы будут users.index
, users.create
и т. д. Если вы хотите изменить псевдоним, установите aliasBase
на желаемое значение.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Только и Исключено
Вы также можете указать, какие маршруты вы хотите создать, используя параметры only
и except
.
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
Это, по сути, параметры белого и черного списков, поэтому вы можете указать, какие маршруты вы хотите создать.
Промежуточное ПО
Вы также можете указать промежуточное ПО, которое будет выполняться на каждом из маршрутов, созданных методом resource
.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
Потоковая передача
Теперь вы можете потоково передавать ответы клиенту, используя метод streamWithHeaders()
.
Это полезно для отправки больших файлов, длительных процессов или генерации больших ответов.
Потоковая маршрутизация обрабатывается немного иначе, чем обычный маршрут.
Примечание: Потоковая передача ответов доступна только в том случае, если у вас установлено
flight.v2.output_buffering
значение false.
Потоковая передача с ручными заголовками
Вы можете потоково передавать ответ клиенту, используя метод stream()
на маршруте. Если вы
это сделаете, вы должны установить все методы вручную перед тем, как что-либо вывести клиенту.
Это делается с помощью функции header()
PHP или метода Flight::response()->setRealHeader()
.
Flight::route('/@filename', function($filename) {
// очевидно, вам нужно будет санировать путь и прочее.
$fileNameSafe = basename($filename);
// Если у вас есть дополнительные заголовки, которые нужно установить здесь после выполнения маршрута,
// вы должны определить их перед тем, как что-либо вывести.
// Все они должны быть вызваны через функцию header() или
// вызов метода Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// или
Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"');
$fileData = file_get_contents('/some/path/to/files/'.$fileNameSafe);
// Обработка ошибок и прочее
if(empty($fileData)) {
Flight::halt(404, 'Файл не найден');
}
// вручную установите длину содержимого, если хотите
header('Content-Length: '.filesize($filename));
// Передать данные клиенту
echo $fileData;
// Это магическая строка
})->stream();
Потоковая передача с заголовками
Вы также можете использовать метод streamWithHeaders()
, чтобы установить заголовки перед началом потоковой передачи.
Flight::route('/stream-users', function() {
// вы можете добавить любые дополнительные заголовки, которые хотите здесь
// вы просто должны использовать header() или Flight::response()->setRealHeader()
// как бы вы ни получали свои данные, просто в качестве примера...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// Это необходимо для отправки данных клиенту
ob_flush();
}
echo '}';
// Так вы установите заголовки перед началом потоковой передачи.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// необязательный код состояния, по умолчанию 200
'status' => 200
]);