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

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

Основна маршрутизація у Flight здійснюється шляхом зіставлення шаблону URL з функцією зворотного виклику або масивом класу та методу.

Flight::route('/', function(){
    echo 'hello world!';
});

Маршрути зіставляються в порядку, в якому вони визначені. Перший маршрут, який співпадає з запитом, буде викликаний.

Функції зворотного виклику

Функція зворотного виклику може бути будь-яким об'єктом, який можна викликати. Тож ви можете використовувати звичайну функцію:

function hello() {
    echo 'hello world!';
}

Flight::route('/', 'hello');

Класи

Ви також можете використовувати статичний метод класу:

class Greeting {
    public static function hello() {
        echo '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' ]);
// Ви також можете зробити це без попереднього створення об'єкта
// Примітка: жодних аргументів не буде впроваджено в конструктор
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 "Hello, world! My name is {$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 'hello world!';
});

// 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 "hello, $name ($id)!";
});

Ви також можете включити регулярні вирази з вашими іменованими параметрами, використовуючи роздільник ::

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // Це буде відповідати /bob/123
  // Але не буде відповідати /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) {
    // Це буде відповідати наступним 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. З псевдонімами вам не потрібно змінювати всюди, де ви посилаєтеся на псевдонім, оскільки він тепер поверне /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
]);