Маршрутизация
Примечание: Хотите узнать больше о маршрутизации? Посетите страницу "why a framework?" для более подробного объяснения.
Базовая маршрутизация в Flight выполняется путём сопоставления шаблона URL с функцией-обработчиком или массивом, содержащим класс и метод.
Flight::route('/', function(){
echo 'hello world!'; // Выводит 'hello world!'
});
Маршруты сопоставляются в порядке их определения. Первый подходящий маршрут будет вызван.
Обработчики/Функции
Обработчик может быть любым вызываемым объектом. Таким образом, вы можете использовать обычную функцию:
function hello() {
echo 'hello world!'; // Выводит 'hello world!'
}
Flight::route('/', 'hello');
Классы
Вы также можете использовать статический метод класса:
class Greeting {
public static function hello() {
echo 'hello world!'; // Выводит 'hello world!'
}
}
Flight::route('/', [ 'Greeting','hello' ]);
Или создав объект сначала, а затем вызвав метод:
// Greeting.php
class Greeting
{
public function __construct() {
$this->name = 'John Doe'; // Устанавливает имя
}
public function hello() {
echo "Hello, {$this->name}!"; // Выводит приветствие
}
}
// index.php
$greeting = new Greeting();
Flight::route('/', [ $greeting, 'hello' ]);
// You also can do this without creating the object first
// Note: No args will be injected into the constructor // Примечание: Аргументы не будут внедрены в конструктор
Flight::route('/', [ 'Greeting', 'hello' ]);
// Additionally you can use this shorter syntax // Кроме того, вы можете использовать этот более короткий синтаксис
Flight::route('/', 'Greeting->hello');
// or // или
Flight::route('/', Greeting::class.'->hello');
Внедрение зависимостей через DIC (Контейнер внедрения зависимостей)
Если вы хотите использовать внедрение зависимостей через контейнер (PSR-11, PHP-DI, Dice и т.д.), это доступно только для маршрутов, где вы создаёте объект самостоятельно или определяете класс и метод строкой. Подробнее см. на странице Dependency Injection.
Вот быстрый пример:
use flight\database\PdoWrapper;
// Greeting.php
class Greeting
{
protected PdoWrapper $pdoWrapper;
public function __construct(PdoWrapper $pdoWrapper) {
$this->pdoWrapper = $pdoWrapper; // Присваивает обёртку PDO
}
public function hello(int $id) {
// Выполняет какие-то действия с $this->pdoWrapper // Выполняет действия с обёрткой PDO
$name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!"; // Выводит приветствие
}
}
// index.php
// Setup the container with whatever params you need // Настраивает контейнер с необходимыми параметрами
// See the Dependency Injection page for more information on PSR-11 // См. страницу Dependency Injection для информации о PSR-11
$dice = new \Dice\Dice();
// Don't forget to reassign the variable with '$dice = '!!!!! // Не забудьте переприсвоить переменную с '$dice = '
$dice = $dice->addRule('flight\database\PdoWrapper', [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Register the container handler // Регистрирует обработчик контейнера
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Routes like normal // Маршруты как обычно
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// or // или
Flight::route('/hello/@id', 'Greeting->hello');
// or // или
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Маршрутизация по методам
По умолчанию, шаблоны маршрутов сопоставляются со всеми методами запросов. Вы можете отвечать на конкретные методы, указав идентификатор перед URL.
Flight::route('GET /', function () {
echo 'I received a GET request.'; // Выводит сообщение о получении GET-запроса
});
Flight::route('POST /', function () {
echo 'I received a POST request.'; // Выводит сообщение о получении POST-запроса
});
// You cannot use Flight::get() for routes as that is a method // Вы не можете использовать Flight::get() для маршрутов, так как это метод
// to get variables, not create a route. // для получения переменных, а не создания маршрута
// Flight::post('/', function() { /* code */ });
// Flight::patch('/', function() { /* code */ });
// Flight::put('/', function() { /* code */ });
// Flight::delete('/', function() { /* code */ });
Вы также можете сопоставить несколько методов с одним обработчиком, используя разделитель |
:
Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.'; // Выводит сообщение о получении GET или POST запроса
});
Кроме того, вы можете получить объект Router, который имеет вспомогательные методы:
$router = Flight::router();
// maps all methods // Сопоставляет все методы
$router->map('/', function() {
echo 'hello world!'; // Выводит 'hello world!'
});
// GET request // GET-запрос
$router->get('/users', function() {
echo 'users'; // Выводит 'users'
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();
Регулярные выражения
Вы можете использовать регулярные выражения в ваших маршрутах:
Flight::route('/user/[0-9]+', function () {
// This will match /user/1234 // Это будет соответствовать /user/1234
});
Хотя этот метод доступен, рекомендуется использовать именованные параметры или именованные параметры с регулярными выражениями, так как они более читаемы и удобны в обслуживании.
Именованные параметры
Вы можете указать именованные параметры в ваших маршрутах, которые будут переданы в функцию-обработчик. Это в основном для читаемости маршрута. Пожалуйста, ознакомьтесь с разделом ниже о важном замечании.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!"; // Выводит приветствие
});
Вы также можете включить регулярные выражения с именованными параметрами, используя разделитель :
:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// This will match /bob/123 // Это будет соответствовать /bob/123
// But will not match /bob/12345 // Но не будет соответствовать /bob/12345
});
Примечание: Сопоставление групп регулярных выражений
()
с позиционными параметрами не поддерживается. :'(
Важное замечание
Хотя в примере выше кажется, что @name
напрямую связано с переменной $name
, это не так. Порядок параметров в функции-обработчике определяет, что будет передано. Таким образом, если вы измените порядок параметров в функции-обработчике, переменные также изменятся. Вот пример:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!"; // Выводит приветствие с изменённым порядком
});
И если вы перейдёте по URL: /bob/123
, вывод будет hello, 123 (bob)!
. Будьте осторожны при настройке маршрутов и функций-обработчиков.
Опциональные параметры
Вы можете указать именованные параметры, которые являются опциональными для сопоставления, обернув сегменты в скобки.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// This will match the following URLS: // Это будет соответствовать следующим URL:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Любые опциональные параметры, которые не сопоставлены, будут переданы как NULL
.
Шаблоны (Wildcards)
Сопоставление выполняется только для отдельных сегментов URL. Если вы хотите сопоставить несколько сегментов, вы можете использовать шаблон *
.
Flight::route('/blog/*', function () {
// This will match /blog/2000/02/01 // Это будет соответствовать /blog/2000/02/01
});
Чтобы маршрутизировать все запросы к одному обработчику, вы можете сделать:
Flight::route('*', function () {
// Do something // Выполнить что-то
});
Передача (Passing)
Вы можете передать выполнение следующему подходящему маршруту, вернув true
из функции-обработчика.
Flight::route('/user/@name', function (string $name) {
// Check some condition // Проверяет некоторое условие
if ($name !== "Bob") {
// Continue to next route // Продолжает к следующему маршруту
return true;
}
});
Flight::route('/user/*', function () {
// This will get called // Это будет вызвано
});
Псевдонимы маршрутов
Вы можете назначить псевдоним маршруту, чтобы URL мог динамически генерироваться позже в коде (например, в шаблоне).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); // Создаёт маршрут с псевдонимом
// later in code somewhere // Позже в коде где-то
Flight::getUrl('user_view', [ 'id' => 5 ]); // will return '/users/5' // Вернёт '/users/5'
Это особенно полезно, если URL изменится. В примере выше, предположим, что users перемещён в /admin/users/@id
. С псевдонимами на месте, вам не нужно изменять ссылки на псевдоним, так как он теперь вернёт /admin/users/5
, как в примере.
Псевдонимы маршрутов работают и в группах:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); // Создаёт маршрут в группе
});
// later in code somewhere // Позже в коде где-то
Flight::getUrl('user_view', [ 'id' => 5 ]); // will return '/users/5' // Вернёт '/users/5'
Информация о маршрутах
Если вы хотите осмотреть информацию о сопоставленном маршруте, есть 2 способа. Вы можете использовать свойство executedRoute
или запросить объект маршрута, переданный в обработчик, указав true
в качестве третьего параметра в методе маршрута. Объект маршрута всегда будет последним параметром, переданным в функцию-обработчик.
Flight::route('/', function(\flight\net\Route $route) {
// Array of HTTP methods matched against // Массив HTTP-методов, с которыми сопоставлено
$route->methods;
// Array of named parameters // Массив именованных параметров
$route->params;
// Matching regular expression // Соответствующее регулярное выражение
$route->regex;
// Contains the contents of any '*' used in the URL pattern // Содержит содержимое любого '*' в шаблоне URL
$route->splat;
// Shows the url path....if you really need it // Показывает путь URL... если это действительно нужно
$route->pattern;
// Shows what middleware is assigned to this // Показывает, какое промежуточное ПО назначено этому
$route->middleware;
// Shows the alias assigned to this route // Показывает псевдоним, назначенный этому маршруту
$route->alias;
}, true);
Или, если вы хотите осмотреть последний выполненный маршрут, вы можете сделать:
Flight::route('/', function() {
$route = Flight::router()->executedRoute; // Получает последний выполненный маршрут
// Do something with $route // Выполняет что-то с $route
// Array of HTTP methods matched against // Массив HTTP-методов, с которыми сопоставлено
$route->methods;
// Array of named parameters // Массив именованных параметров
$route->params;
// Matching regular expression // Соответствующее регулярное выражение
$route->regex;
// Contains the contents of any '*' used in the URL pattern // Содержит содержимое любого '*' в шаблоне URL
$route->splat;
// Shows the url path....if you really need it // Показывает путь URL... если это действительно нужно
$route->pattern;
// Shows what middleware is assigned to this // Показывает, какое промежуточное ПО назначено этому
$route->middleware;
// Shows the alias assigned to this route // Показывает псевдоним, назначенный этому маршруту
$route->alias;
});
Примечание: Свойство
executedRoute
будет установлено только после выполнения маршрута. Если вы попытаетесь получить к нему доступ до выполнения маршрута, оно будетNULL
. Вы также можете использовать executedRoute в промежуточном ПО!
Группировка маршрутов
Иногда вы можете захотеть сгруппировать связанные маршруты вместе (например, /api/v1
). Вы можете сделать это, используя метод group
:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matches /api/v1/users // Сопоставляется с /api/v1/users
});
Flight::route('/posts', function () {
// Matches /api/v1/posts // Сопоставляется с /api/v1/posts
});
});
Вы даже можете вкладывать группы в группы:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() gets variables, it doesn't set a route! See object context below // Flight::get() получает переменные, не устанавливает маршрут! См. контекст объекта ниже
Flight::route('GET /users', function () {
// Matches GET /api/v1/users // Сопоставляется с GET /api/v1/users
});
Flight::post('/posts', function () {
// Matches POST /api/v1/posts // Сопоставляется с POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Matches PUT /api/v1/posts // Сопоставляется с PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() gets variables, it doesn't set a route! See object context below // Flight::get() получает переменные, не устанавливает маршрут! См. контекст объекта ниже
Flight::route('GET /users', function () {
// Matches GET /api/v2/users // Сопоставляется с GET /api/v2/users
});
});
});
Группировка с контекстом объекта
Вы все еще можете использовать группировку маршрутов с объектом Engine
следующим образом:
$app = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {
// user the $router variable // Используйте переменную $router
$router->get('/users', function () {
// Matches GET /api/v1/users // Сопоставляется с GET /api/v1/users
});
$router->post('/posts', function () {
// Matches POST /api/v1/posts // Сопоставляется с POST /api/v1/posts
});
});
Группировка с промежуточным ПО
Вы также можете назначить промежуточное ПО группе маршрутов:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matches /api/v1/users // Сопоставляется с /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // or [ new MyAuthMiddleware() ] if you want to use an instance // или [ new MyAuthMiddleware() ], если вы хотите использовать экземпляр
Подробнее см. на странице group middleware.
Маршрутизация ресурсов
Вы можете создать набор маршрутов для ресурса, используя метод 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
{
}
}
Примечание: Вы можете просмотреть newly added routes with
runway
by runningphp runway routes
. // Вы можете просмотреть newly added routes withrunway
by runningphp runway routes
.
Настройка маршрутов ресурсов
Есть несколько опций для настройки маршрутов ресурсов.
Базовый псевдоним
Вы можете настроить aliasBase
. По умолчанию псевдоним — это последняя часть указанного URL. Например, /users/
приведёт к aliasBase
как users
. При создании этих маршрутов псевдонимы будут users.index
, users.create
и т.д. Если вы хотите изменить псевдоним, установите aliasBase
в желаемое значение.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]); // Устанавливает базовый псевдоним
Only и Except
Вы также можете указать, какие маршруты вы хотите создать, используя опции 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) {
// obviously you would sanitize the path and whatnot. // очевидно, вы бы проверили путь и т.д.
$fileNameSafe = basename($filename);
// If you have additional headers to set here after the route has executed // Если у вас есть дополнительные заголовки для установки после выполнения маршрута
// you must define them before anything is echoed out. // вы должны определить их перед выводом чего-либо.
// They must all be a raw call to the header() function or // Они должны быть прямым вызовом функции header() или
// a call to Flight::response()->setRealHeader() // вызовом Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// or // или
Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File not found'); // Останавливает с ошибкой 404, если файл не найден
}
// manually set the content length if you'd like // Ручная установка длины содержимого, если хотите
header('Content-Length: '.filesize($filePath));
// Stream the file to the client as it's read // Стриминг файла клиенту по ходу чтения
readfile($filePath);
// This is the magic line here // Это магическая строка
})->stream();
Стриминг с заголовками
Вы также можете использовать метод streamWithHeaders()
для установки заголовков перед началом стриминга.
Flight::route('/stream-users', function() {
// you can add any additional headers you want here // Вы можете добавить любые дополнительные заголовки здесь
// you just must use header() or Flight::response()->setRealHeader() // Вы просто должны использовать header() или Flight::response()->setRealHeader()
// however you pull your data, just as an example... // Как бы вы ни получали данные, вот пример...
$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 ',';
}
// This is required to send the data to the client // Это требуется для отправки данных клиенту
ob_flush();
}
echo '}';
// This is how you'll set the headers before you start streaming. // Так вы установите заголовки перед началом стриминга
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// optional status code, defaults to 200 // Опциональный код статуса, по умолчанию 200
'status' => 200
]);