Маршрутизация

Примечание: Хотите узнать больше о маршрутизации? Посмотрите страницу "почему фреймворк?" для более детального объяснения.

Базовая маршрутизация в 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
]);