Learn

Досліджуйте Flight

Flight - це швидкий, простий, розширювальний фреймворк для PHP. Він досить універсальний і може використовуватися для створення будь-якого виду веб-додатку. Він створений з урахуванням простоти та написаний так, що його легко зрозуміти і використовувати.

Важливі Концепції Фреймворку

Чому Фреймворк?

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

Крім того, відмінний урок був створений @lubiana. Хоча він не вдається у великі деталі щодо Flight зокрема, цей посібник допоможе вам зрозуміти деякі з основних концепцій навколо фреймворку і чому вони корисні. Ви можете знайти урок тут.

Flight у порівнянні з іншими фреймворками

Якщо ви мігруєте з іншого фреймворку, такого як Laravel, Slim, Fat-Free або Symfony до Flight, ця сторінка допоможе вам зрозуміти відмінності між ними.

Основні Теми

Автозавантаження

Досліджуйте, як автозавантажувати власні класи у вашому додатку.

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

Досліджуйте, як керувати маршрутами для вашого веб-додатку. Це також включає групування маршрутів, параметри маршруту та проміжне програмне забезпечення.

Проміжне Програмне Забезпечення

Досліджуйте, як використовувати проміжне програмне забезпечення для фільтрації запитів та відповідей у вашому додатку.

Запити

Досліджуйте, як обробляти запити та відповіді у вашому додатку.

Відповіді

Досліджуйте, як надсилати відповіді вашим користувачам.

Події

Досліджуйте, як використовувати систему подій для додавання користувацьких подій у вашому додатку.

HTML Шаблони

Досліджуйте, як використовувати вбудований рушій представлень для рендерингу ваших HTML шаблонів.

Безпека

Досліджуйте, як захистити ваш додаток від поширених загроз безпеки.

Конфігурація

Досліджуйте, як налаштувати фреймворк для вашого додатку.

Розширення Flight

Досліджуйте, як розширити фреймворк, додаючи власні методи і класи.

Події та Фільтрація

Досліджуйте, як використовувати систему подій для додавання хуків до ваших методів і внутрішніх методів фреймворку.

Контейнер Впровадження Залежностей

Досліджуйте, як використовувати контейнери впровадження залежностей (DIC) для управління залежностями вашого додатку.

API Фреймворку

Досліджуйте основні методи фреймворку.

Міграція до v3

Зворотна сумісність на більшості зберігалася, але є деякі зміни, про які вам слід знати при міграції з v2 до v3.

Виправлення Помилок

Є деякі поширені проблеми, з якими ви можете зіткнутися при використанні Flight. Ця сторінка допоможе вам усунути ці проблеми.

Learn/flight_vs_laravel

Flight проти Laravel

Що таке Laravel?

Laravel — це повнофункціональний фреймворк, який має всі можливості та чудову екосистему, орієнтовану на розробників, але з витратами на продуктивність і складність. Метою Laravel є забезпечити розробників найвищим рівнем продуктивності та спростити виконання звичних завдань. Laravel є відмінним вибором для розробників, які прагнуть створити повнофункціональну web-додаток для підприємств. Це має певні компроміси, зокрема щодо продуктивності та складності. Вивчити основи Laravel може бути легко, але досягти майстерності у фреймворку може знадобитися певний час.

Також є багато модулів Laravel, і розробники часто відчувають, що єдиний спосіб вирішити проблеми — це використання цих модулів, тоді як насправді ви могли б просто використовувати іншу бібліотеку або написати свій власний код.

Переваги в порівнянні з Flight

Недоліки в порівнянні з Flight

Learn/migrating_to_v3

Міграція на v3

Зворотна сумісність в основному зберігається, але є деякі зміни, про які вам слід знати при міграції з v2 на v3.

Поведінка буферизації виходу (3.5.0)

Буферизація виходу — це процес, при якому вихід, згенерований PHP-скриптом, зберігається в буфері (внутрішньому для PHP) перед відправкою клієнту. Це дозволяє вам змінювати вихід перед тим, як він буде відправлений клієнту.

В MVC-застосунку Контролер є "менеджером", і він керує тим, що робить представлення. Генерація виходу поза контролером (або в випадку Flight іноді анонімна функція) порушує шаблон MVC. Ця зміна має на меті більше відповідати шаблону MVC і зробити фреймворк більш передбачуваним та легким у використанні.

У v2 буферизація виходу оброблялася таким чином, що вона не завжди закривала свій власний буфер виходу, що ускладнювало юнит-тестування і стримінг. Для більшості користувачів ця зміна може не вплинути на вас. Однак, якщо ви відправляєте контент поза викликами та контролерами (наприклад, у хуку), ви, швидше за все, зіткнетеся з проблемами. Відправка контенту в хуках і до фактичного виконання фреймворку, можливо, працювала раніше, але тепер не буде працювати.

Де ви можете мати проблеми

// index.php
require 'vendor/autoload.php';

// просто приклад
define('START_TIME', microtime(true));

function hello() {
    echo 'Привіт Світ';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // це насправді буде добре
    echo '<p>Цю фразу Привіт Світ вам приніс лист "H"</p>';
});

Flight::before('start', function(){
    // такі речі викличуть помилку
    echo '<html><head><title>Моя Сторінка</title></head><body>';
});

Flight::route('/', function(){
    // це насправді просто добре
    echo 'Привіт Світ';

    // Це також має бути добре
    Flight::hello();
});

Flight::after('start', function(){
    // це викличе помилку
    echo '<div>Ваша сторінка завантажилася за '.(microtime(true) - START_TIME).' секунд</div></body></html>';
});

Увімкнення поведінки рендерингу v2

Ви все ще можете зберегти свій старий код у тому вигляді, в якому він є, без переписування, щоб заставити його працювати з v3? Так, можете! Ви можете увімкнути поведінку рендерингу v2, встановивши параметр конфігурації flight.v2.output_buffering в true. Це дозволить вам продовжити використовувати стару поведінку рендерингу, але рекомендується виправити це на майбутнє. У v4 фреймворка це буде видалено.

// index.php
require 'vendor/autoload.php';

Flight::set('flight.v2.output_buffering', true);

Flight::before('start', function(){
    // Тепер це буде просто добре
    echo '<html><head><title>Моя Сторінка</title></head><body>';
});

// більше коду 

Зміни в Диспетчері (3.7.0)

Якщо ви безпосередньо викликали статичні методи для Dispatcher, такі як Dispatcher::invokeMethod(), Dispatcher::execute(), і т.д., вам потрібно буде оновити свій код, щоб не викликати ці методи безпосередньо. Dispatcher був перетворений на об'єктно-орієнтований, щоб контейнери впровадження залежностей могли використовуватися простіше. Якщо вам потрібно викликати метод, подібно до того, як це робив Dispatcher, ви можете вручну використовувати щось на кшталт $result = $class->$method(...$params); або call_user_func_array() замість цього.

Зміни halt() stop() redirect() та error() (3.10.0)

За замовчуванням поведінка до 3.10.0 полягала в очищенні як заголовків, так і тіла відповіді. Це було змінено на очищення лише тіла відповіді. Якщо вам потрібно очистити також заголовки, ви можете використовувати Flight::response()->clear().

Learn/configuration

Налаштування

Ви можете налаштувати певні поведінки Flight, встановлюючи значення конфігурації через метод set.

Flight::set('flight.log_errors', true);

Доступні налаштування конфігурації

Нижче наведено список усіх доступних налаштувань конфігурації:

Налаштування завантажувача

Також є ще одне налаштування конфігурації для завантажувача. Це дозволить вам автозавантажувати класи з _ в імені класу.

// Увімкнення завантаження класів з підкресленнями
// За замовчуванням: true
Loader::$v2ClassLoading = false;

Змінні

Flight дозволяє зберігати змінні, щоб їх можна було використовувати в будь-якому місці вашого додатка.

// Зберігайте вашу змінну
Flight::set('id', 123);

// В іншому місці вашого додатка
$id = Flight::get('id');

Щоб перевірити, чи була встановлена змінна, ви можете зробити:

if (Flight::has('id')) {
  // Виконати дію
}

Ви можете очистити змінну, зробивши:

// Очищає змінну id
Flight::clear('id');

// Очищає всі змінні
Flight::clear();

Flight також використовує змінні для цілей конфігурації.

Flight::set('flight.log_errors', true);

Обробка помилок

Помилки та виключення

Всі помилки та виключення перехоплюються Flight і передаються в метод error. За замовчуванням поведінка полягає в тому, щоб надіслати загальний HTTP 500 Internal Server Error відповідь з деякою інформацією про помилку.

Ви можете переозначити цю поведінку для своїх потреб:

Flight::map('error', function (Throwable $error) {
  // Обробка помилки
  echo $error->getTraceAsString();
});

За замовчуванням помилки не записуються в веб-сервер. Ви можете активувати це, змінивши конфігурацію:

Flight::set('flight.log_errors', true);

Не знайдено

Коли URL не можна знайти, Flight викликає метод notFound. За замовчуванням поведінка полягає в тому, щоб надіслати відповідь HTTP 404 Not Found з простим повідомленням.

Ви можете переозначити цю поведінку для своїх потреб:

Flight::map('notFound', function () {
  // Обробка не знайдено
});

Learn/security

Безпека

Безпека є важливою справою, коли мова йде про веб-додатки. Ви хочете переконатися, що ваш додаток є безпечним, а дані ваших користувачів - в безпеці. Flight надає ряд функцій, щоб допомогти вам захистити ваші веб-додатки.

Заголовки

HTTP заголовки - один з найбільш простих способів захистити ваші веб-додатки. Ви можете використовувати заголовки, щоб запобігти кліковій атаці, XSS та іншим атакам. Існує кілька способів, якими ви можете додати ці заголовки до вашого додатку.

Два чудових веб-сайти для перевірки безпеки ваших заголовків - це securityheaders.com та observatory.mozilla.org.

Додати вручну

Ви можете вручну додати ці заголовки, використовуючи метод header об'єкта Flight\Response.

// Встановіть заголовок X-Frame-Options, щоб запобігти кліковій атаці
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Встановіть заголовок Content-Security-Policy, щоб запобігти XSS
// Зверніть увагу: цей заголовок може бути дуже складним, тому ви, напевно,
// повинні проконсультуватися з прикладами в Інтернеті для вашого додатку
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// Встановіть заголовок X-XSS-Protection, щоб запобігти XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// Встановіть заголовок X-Content-Type-Options, щоб запобігти MIME sniffing
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// Встановіть заголовок Referrer-Policy, щоб контролювати, скільки інформації про реферера надсилається
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// Встановіть заголовок Strict-Transport-Security, щоб примусити HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// Встановіть заголовок Permissions-Policy, щоб контролювати, які функції та API можуть використовуватися
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Ці заголовки можна додати на початку ваших файлів bootstrap.php або index.php.

Додати як фільтр

Ви також можете додати їх у фільтрі/хуку, як у наступному прикладі:

// Додати заголовки у фільтрі
Flight::before('start', function() {
    Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
    Flight::response()->header("Content-Security-Policy", "default-src 'self'");
    Flight::response()->header('X-XSS-Protection', '1; mode=block');
    Flight::response()->header('X-Content-Type-Options', 'nosniff');
    Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
    Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
    Flight::response()->header('Permissions-Policy', 'geolocation=()');
});

Додати як проміжне програмне забезпечення

Ви також можете додати їх як клас проміжного програмного забезпечення. Це хороший спосіб зберегти ваш код чистим і організованим.

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

class SecurityHeadersMiddleware
{
    public function before(array $params): void
    {
        Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
        Flight::response()->header("Content-Security-Policy", "default-src 'self'");
        Flight::response()->header('X-XSS-Protection', '1; mode=block');
        Flight::response()->header('X-Content-Type-Options', 'nosniff');
        Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
        Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        Flight::response()->header('Permissions-Policy', 'geolocation=()');
    }
}

// index.php або де б ви не мали ваші маршрути
// Зверніть увагу, цей пустий рядок група діє як глобальне проміжне програмне забезпечення для
// всіх маршрутів. Звісно, ви могли б зробити те ж саме і просто додати
// це тільки до певних маршрутів.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // інші маршрути
}, [ new SecurityHeadersMiddleware() ]);

Підробка міжсайтових запитів (CSRF)

Підробка міжсайтових запитів (CSRF) - це тип атаки, коли зловмисний веб-сайт може змусити браузер користувача надіслати запит до вашого веб-сайту. Це може бути використано для виконання дій на вашому веб-сайті без відома користувача. Flight не надає вбудованого механізму захисту CSRF, але ви можете легко реалізувати свій власний, використовуючи проміжне програмне забезпечення.

Налаштування

Спочатку вам потрібно згенерувати токен CSRF і зберегти його в сесії користувача. Потім ви можете використовувати цей токен у ваших формах і перевіряти його, коли форма подається.

// Сгенеруйте токен CSRF і зберігайте його в сесії користувача
// (припускаючи, що ви створили об'єкт сесії та під'єднали його до Flight)
// дивіться документацію про сесії для отримання додаткової інформації
Flight::register('session', \Ghostff\Session\Session::class);

// Вам потрібно згенерувати лише один токен на сесію (щоб це працювало
// через кілька вкладок та запитів для одного й того ж користувача)
if(Flight::session()->get('csrf_token') === null) {
    Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
<!-- Використовуйте токен CSRF у своїй формі -->
<form method="post">
    <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
    <!-- інші поля форми -->
</form>

Використання Latte

Ви також можете налаштувати власну функцію для виводу токену CSRF у ваших шаблонах Latte.

// Налаштуйте власну функцію для виводу токену CSRF
// Зверніть увагу: Вид було налаштовано на Latte як движок видання
Flight::view()->addFunction('csrf', function() {
    $csrfToken = Flight::session()->get('csrf_token');
    return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});

І тепер у ваших шаблонах Latte ви можете використовувати функцію csrf(), щоб вивести токен CSRF.

<form method="post">
    {csrf()}
    <!-- інші поля форми -->
</form>

Просто і зрозуміло, так?

Перевірка токену CSRF

Ви можете перевірити токен CSRF, використовуючи фільтри подій:

// Це проміжне програмне забезпечення перевіряє, чи є запит POST-запитом і, якщо так, перевіряє, чи є токен CSRF дійсним
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // захоплюємо токен csrf з полів форми
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Недійсний токен CSRF');
            // або для JSON-відповіді
            Flight::jsonHalt(['error' => 'Недійсний токен CSRF'], 403);
        }
    }
});

Або ви можете використовувати клас проміжного програмного забезпечення:

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

class CsrfMiddleware
{
    public function before(array $params): void
    {
        if(Flight::request()->method == 'POST') {
            $token = Flight::request()->data->csrf_token;
            if($token !== Flight::session()->get('csrf_token')) {
                Flight::halt(403, 'Недійсний токен CSRF');
            }
        }
    }
}

// index.php або де б ви не мали ваші маршрути
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // інші маршрути
}, [ new CsrfMiddleware() ]);

Міжсайтове скриптування (XSS)

Міжсайтове скриптування (XSS) - це тип атаки, коли зловмисний веб-сайт може впровадити код у ваш веб-сайт. Більшість таких можливостей з'являється з значень форм, які заповнюватимуть ваші кінцеві користувачі. Ви повинні ніколи не довіряти виводу ваших користувачів! Завжди вважайте, що всі з них - найкращі хакери у світі. Вони можуть впровадити шкідливий JavaScript або HTML у вашу сторінку. Цей код може бути використаний для викрадення інформації від ваших користувачів або виконання дій на вашому веб-сайті. Використовуючи клас видів Flight, ви можете легко екранізувати вивід, щоб запобігти атакам XSS.

// Припустимо, що користувач розумний і намагається використати це як своє ім'я
$name = '<script>alert("XSS")</script>';

// Це відсканує вивід
Flight::view()->set('name', $name);
// Це виведе: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Якщо ви використовуєте щось на кшталт Latte, зареєстрованого як ваш клас видання, 
// це також автоматично екранізує це.
Flight::view()->render('template', ['name' => $name]);

SQL-ін'єкція

SQL-ін'єкція - це тип атаки, коли зловмисний користувач може впровадити SQL-код у вашу базу даних. Це може бути використано для викрадення інформації з вашої бази даних або виконання дій на вашій базі даних. Знову ви повинні ніколи не довіряти виходу ваших користувачів! Завжди вважайте, що вони намагаються заподіяти шкоду. Ви можете використовувати підготовлені запити у ваших об'єктах PDO, щоб запобігти SQL-ін'єкції.

// Припустимо, ви зареєстрували Flight::db() як ваш об'єкт PDO
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// Якщо ви використовуєте клас PdoWrapper, це легко можна зробити в один рядок
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// Ви можете зробити те ж саме з об'єктом PDO з ? заповнювачами
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

// Обіцяйте, що ви ніколи НЕ зробите щось таке...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// адже що, якщо $username = "' OR 1=1; -- ";
// Після того, як запит будується, виглядає це
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Це виглядає дивно, але це дійсний запит, який спрацює. Насправді,
// це дуже поширена SQL-ін'єкція, яка поверне усіх користувачів.

CORS

Обмін ресурсами між різними джерелами (CORS) - це механізм, який дозволяє багатьом ресурсам (наприклад, шрифтам, JavaScript тощо) на веб-сторінці бути запитуваними з іншого домену, відмінного від домену, з якого ресурс походить. Flight не має вбудованого функціоналу, але це можна легко обробити з допомогою хуку, який виконується перед викликом методу Flight::start().

// app/utils/CorsUtil.php

namespace app\utils;

class CorsUtil
{
    public function set(array $params): void
    {
        $request = Flight::request();
        $response = Flight::response();
        if ($request->getVar('HTTP_ORIGIN') !== '') {
            $this->allowOrigins();
            $response->header('Access-Control-Allow-Credentials', 'true');
            $response->header('Access-Control-Max-Age', '86400');
        }

        if ($request->method === 'OPTIONS') {
            if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
                $response->header(
                    'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
                );
            }
            if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
                $response->header(
                    "Access-Control-Allow-Headers",
                    $request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
                );
            }

            $response->status(200);
            $response->send();
            exit;
        }
    }

    private function allowOrigins(): void
    {
        // Налаштуйте свої дозволені хости тут.
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        $request = Flight::request();

        if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
        }
    }
}

// index.php або де б ви не мали ваші маршрути
$CorsUtil = new CorsUtil();

// Це потрібно виконати перед тим, як запуститься start.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Обробка помилок

Сховати чутливі деталі помилок у продакшені, щоб уникнути витоку інформації зловмисникам.

// У вашому bootstrap.php або index.php

// у flightphp/skeleton це в app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
    ini_set('display_errors', 0); // Вимкнути відображення помилок
    ini_set('log_errors', 1);     // Логувати помилки натомість
    ini_set('error_log', '/path/to/error.log');
}

// У ваших маршрутах або контролерах
// Використовуйте Flight::halt() для контрольованих відповідей на помилки
Flight::halt(403, 'Доступ заборонено');

Санітаризація вводу

Ніколи не довіряйте вводу користувачів. Санітаризуйте його перед обробкою, щоб запобігти проникненню шкідливих даних.


// Припустимо запит $_POST з $_POST['input'] та $_POST['email']

// Санітаризація строкового вводу
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Санітаризація електронної пошти
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);

Хешування паролів

Зберігайте паролі у безпеці та перевіряйте їх безпечно, використовуючи вбудовані функції PHP.

$password = Flight::request()->data->password;
// Хешуйте пароль при зберіганні (наприклад, під час реєстрації)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Перевірте пароль (наприклад, під час входу)
if (password_verify($password, $stored_hash)) {
    // Пароль співпадає
}

Обмеження швидкості

Захистіть від атак типу brute force, обмежуючи темп запитів за допомогою кешу.

// Припустимо, що у вас встановлено та зареєстровано flightphp/cache
// Використовуючи flightphp/cache в проміжному програмному забезпеченні
Flight::before('start', function() {
    $cache = Flight::cache();
    $ip = Flight::request()->ip;
    $key = "rate_limit_{$ip}";
    $attempts = (int) $cache->retrieve($key);

    if ($attempts >= 10) {
        Flight::halt(429, 'Забагато запитів');
    }

    $cache->set($key, $attempts + 1, 60); // Скинути через 60 секунд
});

Висновок

Безпека є важливою справою, і важливо переконатися, що ваші веб-додатки безпечні. Flight надає ряд функцій, щоб допомогти вам захистити ваші веб-додатки, але важливо завжди залишатися пильним і переконатися, що ви робите все можливе, щоб захистити дані своїх користувачів. Завжди вважайте найгірше і ніколи не довіряйте вводу ваших користувачів. Завжди екранізуйте вихід і використовуйте підготовлені запити, щоб запобігти SQL ін'єкції. Завжди використовуйте проміжне програмне забезпечення, щоб захистити свої маршрути від атак CSRF та CORS. Якщо ви виконаєте всі ці дії, ви будете на шляху до створення безпечних веб-додатків.

Learn/routing

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

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

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

Learn/flight_vs_symfony

Flight vs Symfony

Що таке Symfony?

Symfony — це набір повторно використовуваних компонентів PHP і PHP фреймворк для веб-проектів.

Стандартна основа, на якій побудовані найкращі PHP додатки. Виберіть будь-який з 50 автономних компонентів, доступних для ваших власних додатків.

Прискорте створення та підтримку ваших PHP веб-додатків. Припиніть повторювані завдання кодування і насолоджуйтесь можливістю контролювати свій код.

Плюси в порівнянні з Flight

Мінуси в порівнянні з Flight

Learn/flight_vs_another_framework

Порівняння Flight з іншим фреймворком

Якщо ви мігруєте з іншого фреймворка, такого як Laravel, Slim, Fat-Free або Symfony до Flight, ця сторінка допоможе вам зрозуміти відмінності між цими двома.

Laravel

Laravel — це повнофункціональний фреймворк, який має всі принади та дивовижну екосистему, орієнтовану на розробників, але вартість цього — продуктивність і складність.

Дивіться порівняння між Laravel і Flight.

Slim

Slim — це мікрофреймворк, схожий на Flight. Він розроблений для того, щоб бути легким і простим у використанні, але може бути трохи складнішим, ніж Flight.

Дивіться порівняння між Slim і Flight.

Fat-Free

Fat-Free — це повноцінний фреймворк у набагато меншому пакеті. Хоча в ньому є всі інструменти в ящику, він має архітектуру даних, яка може ускладнити деякі проекти більше, ніж потрібно.

Дивіться порівняння між Fat-Free і Flight.

Symfony

Symfony — це модульний фреймворк корпоративного рівня, який розроблений для того, щоб бути гнучким і масштабованим. Для менших проектів або нових розробників Symfony може бути трохи приголомшуючим.

Дивіться порівняння між Symfony і Flight.

Learn/dependency_injection_container

Контейнер для впровадження залежностей

Вступ

Контейнер для впровадження залежностей (DIC) — це потужний інструмент, який дозволяє вам керувати залежностями вашого застосунку. Це ключова концепція в сучасних PHP фреймворках і використовується для управління створенням та налаштуванням об'єктів. Деякі приклади бібліотек DIC: Dice, Pimple, PHP-DI та league/container.

DIC - це вишуканий спосіб сказати, що він дозволяє вам створювати та керувати вашими класами в централізованому місці. Це корисно, коли вам потрібно передавати один і той же об'єкт кільком класам (як-от вашим контролерам). Простий приклад може допомогти це зрозуміти.

Основний приклад

Старий спосіб виконання завдань може виглядати так:


require 'vendor/autoload.php';

// клас для управління користувачами з бази даних
class UserController {

    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

$User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass'));
Flight::route('/user/@id', [ $UserController, 'view' ]);

Flight::start();

Ви можете побачити з наведеного вище коду, що ми створюємо новий об'єкт PDO і передаємо його нашому класу UserController. Це нормально для малих застосунків, але коли ваш застосунок росте, ви виявите, що створюєте один і той же об'єкт PDO в кількох місцях. Ось тут DIC стає в нагоді.

Ось той же приклад, використовуючи DIC (використовуючи Dice):


require 'vendor/autoload.php';

// той же клас, що й вище. Нічого не змінилося
class UserController {

    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function view(int $id) {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);

        print_r($stmt->fetch());
    }
}

// створюємо новий контейнер
$container = new \Dice\Dice;
// не забудьте перепризначити його, як показано нижче!
$container = $container->addRule('PDO', [
    // спільний означає, що той же об'єкт буде повертатися щоразу
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// Це реєструє обробник контейнера, щоб Flight знав, що використовувати його.
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// тепер ми можемо використовувати контейнер для створення нашого UserController
Flight::route('/user/@id', [ 'UserController', 'view' ]);
// або, альтернативно, ви можете визначити маршрут таким чином
Flight::route('/user/@id', 'UserController->view');
// або
Flight::route('/user/@id', 'UserController::view');

Flight::start();

Можливо, вам здається, що до прикладу було додано багато зайвого коду. Чарівність виникає, коли у вас є інший контролер, який потребує об'єкт PDO.


// Якщо всі ваші контролери мають конструктор, який потребує об'єкт PDO
// кожен з маршрутів нижче автоматично буде мати його впровадженим!!!
Flight::route('/company/@id', 'CompanyController->view');
Flight::route('/organization/@id', 'OrganizationController->view');
Flight::route('/category/@id', 'CategoryController->view');
Flight::route('/settings', 'SettingsController->view');

Додатковим бонусом від використання DIC є те, що модульне тестування стає значно легшим. Ви можете створити змодельований об'єкт і передати його своєму класу. Це величезна перевага, коли ви пишете тести для свого застосунку!

PSR-11

Flight може також використовувати будь-який контейнер, сумісний з PSR-11. Це означає, що ви можете використовувати будь-який контейнер, який реалізує інтерфейс PSR-11. Ось приклад, як використовувати контейнер PSR-11 від League:


require 'vendor/autoload.php';

// той же клас UserController, що й вище

$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::class)
    ->addArgument('mysql:host=localhost;dbname=test')
    ->addArgument('user')
    ->addArgument('pass');
Flight::registerContainerHandler($container);

Flight::route('/user', [ 'UserController', 'view' ]);

Flight::start();

Це може бути трохи більш розлогим, ніж попередній приклад з Dice, але все одно виконує свою роботу з такими ж перевагами!

Користувацький обробник DIC

Ви також можете створити власний обробник DIC. Це корисно, якщо у вас є власний контейнер, який ви хочете використовувати, який не є PSR-11 (Dice). Дивіться основний приклад для того, як це зробити.

Крім того, є деякі корисні значення за замовчуванням, які полегшать вам життя при використанні Flight.

Екземпляр двигуна

Якщо ви використовуєте екземпляр Engine у своїх контролерах/проміжних програмах, ось як ви його налаштуєте:


// Десь у вашому файлі завантаження
$engine = Flight::app();

$container = new \Dice\Dice;
$container = $container->addRule('*', [
    'substitutions' => [
        // Ось тут ви передаєте екземпляр
        Engine::class => $engine
    ]
]);

$engine->registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// Тепер ви можете використовувати екземпляр Engine у своїх контролерах/проміжних програмах

class MyController {
    public function __construct(Engine $app) {
        $this->app = $app;
    }

    public function index() {
        $this->app->render('index');
    }
}

Додавання інших класів

Якщо у вас є інші класи, які ви хочете додати до контейнера, з Dice це легко, оскільки вони автоматично будуть визначатися контейнером. Ось приклад:


$container = new \Dice\Dice;
// Якщо вам не потрібно впроваджувати нічого в свій клас
// ви не повинні нічого визначати!
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

class MyCustomClass {
    public function parseThing() {
        return 'річ';
    }
}

class UserController {

    protected MyCustomClass $MyCustomClass;

    public function __construct(MyCustomClass $MyCustomClass) {
        $this->MyCustomClass = $MyCustomClass;
    }

    public function index() {
        echo $this->MyCustomClass->parseThing();
    }
}

Flight::route('/user', 'UserController->index');

Learn/middleware

Middleware маршрути

Flight підтримує маршрути та групи маршрутів для проміжного програмного забезпечення. Проміжне програмне забезпечення — це функція, яка виконується перед (або після) зворотного виклику маршруту. Це чудовий спосіб додати перевірки автентифікації API у вашому коді або підтвердити, що користувач має дозвіл на доступ до маршруту.

Основне проміжне програмне забезпечення

Ось базовий приклад:

// Якщо ви надасте лише анонімну функцію, вона буде виконана перед зворотним викликом маршруту. 
// немає "після" функцій проміжного програмного забезпечення, крім класів (див. нижче)
Flight::route('/path', function() { echo ' Тут я!'; })->addMiddleware(function() {
    echo 'Проміжне програмне забезпечення першим!';
});

Flight::start();

// Це виведе "Проміжне програмне забезпечення першим! Тут я!"

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

Класи проміжного програмного забезпечення

Проміжне програмне забезпечення також може бути зареєстроване як клас. Якщо вам потрібен функціонал "після", ви повинні використовувати клас.

class MyMiddleware {
    public function before($params) {
        echo 'Проміжне програмне забезпечення першим!';
    }

    public function after($params) {
        echo 'Проміжне програмне забезпечення останнім!';
    }
}

$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Тут я! '; })->addMiddleware($MyMiddleware); // також ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// Це виведе "Проміжне програмне забезпечення першим! Тут я! Проміжне програмне забезпечення останнім!"

Обробка помилок проміжного програмного забезпечення

Скажімо, у вас є проміжне програмне забезпечення для автентифікації, і ви хочете перенаправити користувача на сторінку входу, якщо він не автентифікований. У вас є кілька варіантів на вибір:

  1. Ви можете повернути false з функції проміжного програмного забезпечення, і Flight автоматично поверне помилку 403 Заборонено, але без кастомізації.
  2. Ви можете перенаправити користувача на сторінку входу за допомогою Flight::redirect().
  3. Ви можете створити власну помилку в межах проміжного програмного забезпечення та зупинити виконання маршруту.

Основний приклад

Ось простий приклад return false;:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            return false;
        }

        // оскільки це правда, все продовжує йти далі
    }
}

Приклад перенаправлення

Ось приклад перенаправлення користувача на сторінку входу:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            Flight::redirect('/login');
            exit;
        }
    }
}

Приклад користувацької помилки

Скажімо, вам потрібно викинути JSON-помилку, оскільки ви створюєте API. Ви можете зробити це ось так:

class MyMiddleware {
    public function before($params) {
        $authorization = Flight::request()->headers['Authorization'];
        if(empty($authorization)) {
            Flight::jsonHalt(['error' => 'Ви повинні бути увійшли, щоб отримати доступ до цієї сторінки.'], 403);
            // або
            Flight::json(['error' => 'Ви повинні бути увійшли, щоб отримати доступ до цієї сторінки.'], 403);
            exit;
            // або
            Flight::halt(403, json_encode(['error' => 'Ви повинні бути увійшли, щоб отримати доступ до цієї сторінки.']);
        }
    }
}

Групування проміжного програмного забезпечення

Ви можете додати групу маршрутів, і тоді кожен маршрут у цій групі матиме таке ж проміжне програмне забезпечення. Це корисно, якщо ви хочете групувати кілька маршрутів за, наприклад, проміжним програмним забезпеченням Auth, щоб перевірити ключ API в заголовку.


// додано в кінець методу групи
Flight::group('/api', function() {

    // Цей "порожній" виглядаючий маршрут насправді відповідатиме /api
    Flight::route('', function() { echo 'api'; }, false, 'api');
    // Це відповідатиме /api/users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // Це відповідатиме /api/users/1234
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

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


// додано в кінець методу групи
Flight::group('', function() {

    // Це все ще /users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // А це все ще /users/1234
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Learn/filtering

Фільтрація

Flight дозволяє вам фільтрувати методи до і після їх виклику. Немає зазначених хуків, які потрібно запам'ятовувати. Ви можете фільтрувати будь-які з методів за замовчуванням фреймворку, а також будь-які користувацькі методи, які ви налаштували.

Функція фільтра виглядає так:

function (array &$params, string &$output): bool {
  // Код фільтра
}

Використовуючи передані змінні, ви можете маніпулювати вхідними параметрами та/або виходом.

Ви можете запустити фільтр перед методом, зробивши:

Flight::before('start', function (array &$params, string &$output): bool {
  // Зробіть щось
});

Ви можете запустити фільтр після методу, зробивши:

Flight::after('start', function (array &$params, string &$output): bool {
  // Зробіть щось
});

Ви можете додати скільки завгодно фільтрів до будь-якого методу. Вони будуть викликані в порядку, в якому вони оголошені.

Ось приклад процесу фільтрації:

// Співвіднести користувацький метод
Flight::map('hello', function (string $name) {
  return "Привіт, $name!";
});

// Додати фільтр перед
Flight::before('hello', function (array &$params, string &$output): bool {
  // Маніпулюйте параметром
  $params[0] = 'Фред';
  return true;
});

// Додати фільтр після
Flight::after('hello', function (array &$params, string &$output): bool {
  // Маніпулюйте виходом
  $output .= " Гарного вам дня!";
  return true;
});

// Викликати користувацький метод
echo Flight::hello('Боб');

Це повинно відображати:

Привіт Фред! Гарного вам дня!

Якщо ви визначили кілька фільтрів, ви можете розірвати ланцюг, повернувши false в будь-якій з ваших функцій фільтра:

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'один';
  return true;
});

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'два';

  // Це завершить ланцюг
  return false;
});

// Це не буде викликано
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'три';
  return true;
});

Зверніть увагу, що основні методи, такі як map і register, не можна фільтрувати, оскільки їх викликають безпосередньо, а не динамічно.

Learn/requests

Запити

Flight інкапсулює HTTP запит в один об'єкт, до якого можна доступитися, виконавши:

$request = Flight::request();

Типові випадки використання

Коли ви працюєте з запитом в веб-додатку, зазвичай ви хочете отримати заголовок, або параметр $_GET чи $_POST, або можливо навіть сирий зміст запиту. Flight надає простий інтерфейс для всіх цих функцій.

Ось приклад отримання параметра рядка запиту:

Flight::route('/search', function(){
    $keyword = Flight::request()->query['keyword'];
    echo "Ви шукаєте: $keyword";
    // запит до бази даних або щось інше з $keyword
});

Ось приклад можливої форми з методом POST:

Flight::route('POST /submit', function(){
    $name = Flight::request()->data['name'];
    $email = Flight::request()->data['email'];
    echo "Ви надіслали: $name, $email";
    // зберегти в базі даних або щось інше з $name та $email
});

Властивості об'єкта запиту

Об'єкт запиту надає такі властивості:

Ви можете доступитися до властивостей query, data, cookies та files як масивів або об'єктів.

Отже, щоб отримати параметр рядка запиту, ви можете зробити:

$id = Flight::request()->query['id'];

Або ви можете зробити:

$id = Flight::request()->query->id;

Сирий зміст запиту

Щоб отримати сирий HTTP зміст запиту, наприклад, обробляючи запити PUT, ви можете зробити:

$body = Flight::request()->getBody();

JSON Вхідні дані

Якщо ви надсилаєте запит з типом application/json і даними {"id": 123} це буде доступно з властивості data:

$id = Flight::request()->data->id;

$_GET

Ви можете доступитися до масиву $_GET через властивість query:

$id = Flight::request()->query['id'];

$_POST

Ви можете доступитися до масиву $_POST через властивість data:

$id = Flight::request()->data['id'];

$_COOKIE

Ви можете доступитися до масиву $_COOKIE через властивість cookies:

$myCookieValue = Flight::request()->cookies['myCookieName'];

$_SERVER

Є доступна скорочена функція для доступу до масиву $_SERVER через метод getVar():


$host = Flight::request()->getVar['HTTP_HOST'];

Доступ до завантажених файлів через $_FILES

Ви можете доступитися до завантажених файлів через властивість files:

$uploadedFile = Flight::request()->files['myFile'];

Обробка завантажень файлів (v3.12.0)

Ви можете обробляти завантаження файлів, використовуючи фреймворк з деякими допоміжними методами. Це в основному зводиться до витягування даних файлу з запиту та переміщення їх у нове місце.

Flight::route('POST /upload', function(){
    // Якщо у вас є поле введення <input type="file" name="myFile">
    $uploadedFileData = Flight::request()->getUploadedFiles();
    $uploadedFile = $uploadedFileData['myFile'];
    $uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});

Якщо ви завантажили кілька файлів, ви можете пройтися по ним:

Flight::route('POST /upload', function(){
    // Якщо у вас є поле введення <input type="file" name="myFiles[]">
    $uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
    foreach ($uploadedFiles as $uploadedFile) {
        $uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
    }
});

Примітка безпеки: Завжди перевіряйте та очищуйте введення користувача, особливо під час обробки завантажень файлів. Завжди перевіряйте тип розширень, які ви дозволите завантажувати, але також слід перевіряти "магічні байти" файлу, щоб впевнитися, що це дійсно той тип файлу, який користувач стверджує, що це. Існують статті та бібліотеки, які можуть допомогти з цим.

Заголовки запиту

Ви можете доступитися до заголовків запиту, використовуючи методи getHeader() або getHeaders():


// Можливо, вам потрібен заголовок Authorization
$host = Flight::request()->getHeader('Authorization');
// або
$host = Flight::request()->header('Authorization');

// Якщо вам потрібно забрати всі заголовки
$headers = Flight::request()->getHeaders();
// або
$headers = Flight::request()->headers();

Зміст запиту

Ви можете доступитися до сирого змісту запиту, використовуючи метод getBody():

$body = Flight::request()->getBody();

Метод запиту

Ви можете доступитися до методу запиту, використовуючи властивість method або метод getMethod():

$method = Flight::request()->method; // насправді викликає getMethod()
$method = Flight::request()->getMethod();

Примітка: Метод getMethod() спочатку отримує метод з $_SERVER['REQUEST_METHOD'], потім його можна переписати через $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'], якщо він існує, або $_REQUEST['_method'], якщо він існує.

URL запитів

Існує кілька допоміжних методів для збирання частин URL для зручності.

Повний URL

Ви можете доступитися до повного URL запиту, використовуючи метод getFullUrl():

$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar

Базовий URL

Ви можете доступитися до базового URL, використовуючи метод getBaseUrl():

$url = Flight::request()->getBaseUrl();
// Зверніть увагу, без кінцевого слешу.
// https://example.com

Парсинг запитів

Ви можете передати URL до методу parseQuery(), щоб розпарсити рядок запиту в асоціативний масив:

$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']

Learn/api

Методи API фреймворку

Flight розроблений для того, щоб бути простим у використанні та розумінні. Нижче наведено повний набір методів для фреймворку. Він складається з основних методів, які є регулярними статичними методами, а також розширювальних методів, які є відображеними методами, що можуть бути відфільтровані або перевизначені.

Основні методи

Ці методи є основними для фреймворку і не можуть бути перевизначені.

Flight::map(string $name, callable $callback, bool $pass_route = false) // Створює кастомний метод фреймворку.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Реєструє клас для методу фреймворку.
Flight::unregister(string $name) // Скасовує реєстрацію класу для методу фреймворку.
Flight::before(string $name, callable $callback) // Додає фільтр перед методом фреймворку.
Flight::after(string $name, callable $callback) // Додає фільтр після методу фреймворку.
Flight::path(string $path) // Додає шлях для автоматичного завантаження класів.
Flight::get(string $key) // Отримує змінну, встановлену Flight::set().
Flight::set(string $key, mixed $value) // Встановлює змінну в рамках движка Flight.
Flight::has(string $key) // Перевіряє, чи встановлено змінну.
Flight::clear(array|string $key = []) // Очищає змінну.
Flight::init() // Ініціалізує фреймворк з його стандартними налаштуваннями.
Flight::app() // Отримує екземпляр об'єкта програми.
Flight::request() // Отримує екземпляр об'єкта запиту.
Flight::response() // Отримує екземпляр об'єкта відповіді.
Flight::router() // Отримує екземпляр об'єкта маршрутизатора.
Flight::view() // Отримує екземпляр об'єкта перегляду.

Розширювальні методи

Flight::start() // Запускає фреймворк.
Flight::stop() // Зупиняє фреймворк і відправляє відповідь.
Flight::halt(int $code = 200, string $message = '') // Зупиняє фреймворк з необов'язковим кодом статусу та повідомленням.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Відображає шаблон URL на колбек.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Відображає шаблон URL запиту POST на колбек.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Відображає шаблон URL запиту PUT на колбек.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Відображає шаблон URL запиту PATCH на колбек.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Відображає шаблон URL запиту DELETE на колбек.
Flight::group(string $pattern, callable $callback) // Створює групування для URL, шаблон має бути рядком.
Flight::getUrl(string $name, array $params = []) // Генерує URL на основі псевдоніму маршруту.
Flight::redirect(string $url, int $code) // Перенаправляє на інший URL.
Flight::download(string $filePath) // Завантажує файл.
Flight::render(string $file, array $data, ?string $key = null) // Відображає шаблонний файл.
Flight::error(Throwable $error) // Відправляє відповідь HTTP 500.
Flight::notFound() // Відправляє відповідь HTTP 404.
Flight::etag(string $id, string $type = 'string') // Виконує ETag HTTP кешування.
Flight::lastModified(int $time) // Виконує кешування HTTP останнього модифікованого.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Відправляє відповідь JSON.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Відправляє відповідь JSONP.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Відправляє відповідь JSON і зупиняє фреймворк.
Flight::onEvent(string $event, callable $callback) // Реєструє слухача подій.
Flight::triggerEvent(string $event, ...$args) // Запускає подію.

Будь-які кастомні методи, додані за допомогою map та register, також можуть бути відфільтровані. Для прикладів того, як відобразити ці методи, дивіться посібник Розширення Flight.

Learn/why_frameworks

Чому фреймворк?

Деякі програмісти рішуче проти використання фреймворків. Вони стверджують, що фреймворки є надмірними, повільними і важкими для навчання. Вони кажуть, що фреймворки є непотрібними і що ви можете написати кращий код без них. Звичайно, є деякі слушні зауваження щодо недоліків використання фреймворків. Однак також є багато переваг використання фреймворків.

Причини використання фреймворка

Ось кілька причин, чому ви можете хотіти розглянути можливість використання фреймворка:

Flight — це мікро-фреймворк. Це означає, що він невеликий і легкий. Він не надає такої ж функціональності, як більші фреймворки, такі як Laravel або Symfony. Однак він забезпечує багато функціональності, яку ви потребуєте для створення веб-додатків. Його також легко навчитися і використовувати. Це робить його хорошим вибором для швидкого і легкого створення веб-додатків. Якщо ви новачок у фреймворках, Flight є чудовим початковим фреймворком для старту. Він допоможе вам дізнатися про переваги використання фреймворків, не перевантажуючи вас занадто великою складністю. Після того, як ви отримаєте деякий досвід з Flight, вам буде легше перейти на більш складні фреймворки, такі як Laravel або Symfony, однак Flight все ще може скласти успішну надійну програму.

Що таке маршрутизація?

Маршрутизація — це основа фреймворка Flight, але що це таке насправді? Маршрутизація — це процес взяття URL-адреси і зіставлення її з конкретною функцією у вашому коді. Це спосіб, яким ви можете змусити ваш веб-сайт виконувати різні дії в залежності від запитуваної URL-адреси. Наприклад, ви можете захотіти показати профіль користувача, коли вони відвідують /user/1234, але показати список всіх користувачів, коли вони відвідують /users. Це все робиться через маршрутизацію.

Це може працювати приблизно так:

І чому це важливо?

Наявність належного централізованого маршрутизатора може значно спростити ваше життя! Це може бути важко зрозуміти на перший погляд. Ось кілька причин, чому:

Я впевнений, що ви знайомі з методом "скрипт за скриптом" для створення веб-сайту. У вас може бути файл під назвою index.php, який містить безліч if операцій для перевірки URL-адреси, а потім виконання конкретної функції на основі URL-адреси. Це форма маршрутизації, але вона не дуже організована і може вийти з-під контролю швидко. Система маршрутизації Flight є набагато більш організованим і потужним способом обробки маршрутизації.

Це?


// /user/view_profile.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    viewUserProfile($id);
}

// /user/edit_profile.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    editUserProfile($id);
}

// і т.д...

Або це?


// index.php
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
Flight::route('/user/@id/edit', [ 'UserController', 'editUserProfile' ]);

// У вашому app/controllers/UserController.php
class UserController {
    public function viewUserProfile($id) {
        // зробити щось
    }

    public function editUserProfile($id) {
        // зробити щось
    }
}

Сподіваюсь, ви вже почали бачити переваги використання централізованої системи маршрутизації. Це значно легше керувати та розуміти в довгостроковій перспективі!

Запити та відповіді

Flight забезпечує простий і легкий спосіб обробки запитів і відповідей. Це основа того, що робить веб-фреймворк. Він приймає запит від браузера користувача, обробляє його, а потім надсилає відповідь. Це спосіб, яким ви можете створювати веб-додатки, які виконують такі дії, як показ профілю користувача, дозволяють користувачу входити в систему або дозволяють користувачу публікувати новий блог.

Запити

Запит — це те, що браузер користувача надсилає на ваш сервер, коли вони відвідують ваш веб-сайт. Цей запит містить інформацію про те, що користувач хоче зробити. Наприклад, він може містити інформацію про те, яку URL-адресу хоче відвідати користувач, які дані хоче надіслати користувач на ваш сервер, або який тип даних хоче отримати користувач від вашого сервера. Важливо знати, що запит є тільки для читання. Ви не можете змінити запит, але можете читати з нього.

Flight забезпечує простий спосіб доступу до інформації про запит. Ви можете отримати доступ до інформації про запит, використовуючи метод Flight::request(). Цей метод повертає об'єкт Request, який містить інформацію про запит. Ви можете використовувати цей об'єкт, щоб отримати інформацію про запит, наприклад, URL, метод або дані, які користувач надіслав на ваш сервер.

Відповіді

Відповідь — це те, що ваш сервер надсилає назад браузеру користувача, коли вони відвідують ваш веб-сайт. Ця відповідь містить інформацію про те, що ваш сервер хоче зробити. Наприклад, вона може містити інформацію про те, які дані ваш сервер хоче надіслати користувачу, які дані ваш сервер хоче отримати від користувача, або які дані ваш сервер хоче зберегти на комп'ютері користувача.

Flight надає простий спосіб надіслати відповідь браузеру користувача. Ви можете надіслати відповідь, використовуючи метод Flight::response(). Цей метод приймає об'єкт Response як аргумент і надсилає відповідь браузеру користувача. Ви можете використовувати цей об'єкт, щоб надіслати відповідь до браузера користувача, таку як HTML, JSON або файл. Flight допомагає вам автоматично генерувати деякі частини відповіді, щоб упростити процес, але в кінцевому підсумку ви маєте контроль над тим, що ви надсилаєте назад користувачу.

Learn/responses

Відповіді

Flight допомагає генерувати частину заголовків відповіді для вас, але ви контролюєте більшу частину того, що ви повертаєте користувачу. Іноді ви можете безпосередньо отримати доступ до об'єкта Response, але зазвичай ви будете використовувати екземпляр Flight, щоб надіслати відповідь.

Відправка базової відповіді

Flight використовує ob_start() для буферизації виходу. Це означає, що ви можете використовувати echo або print, щоб надіслати відповідь користувачу, а Flight захопить її і надішле назад користувачу з відповідними заголовками.


// Це надішле "Hello, World!" до браузера користувача
Flight::route('/', function() {
    echo "Hello, World!";
});

// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!

Як альтернатива, ви можете викликати метод write(), щоб додати до тіла також.


// Це надішле "Hello, World!" до браузера користувача
Flight::route('/', function() {
    // докладно, але робить свою справу, коли вам це потрібно
    Flight::response()->write("Hello, World!");

    // якщо ви хочете отримати тіло, яке ви встановили на цей момент
    // ви можете зробити це так
    $body = Flight::response()->getBody();
});

Коди статусу

Ви можете встановити код статусу відповіді, використовуючи метод status:

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "Hello, World!";
    } else {
        Flight::response()->status(403);
        echo "Заборонено";
    }
});

Якщо ви хочете отримати поточний код статусу, ви можете використовувати метод status без аргументів:

Flight::response()->status(); // 200

Встановлення тіла відповіді

Ви можете встановити тіло відповіді, використовуючи метод write, однак, якщо ви щось echo або print, воно буде захоплено і надіслано як тіло відповіді через буферизацію виходу.

Flight::route('/', function() {
    Flight::response()->write("Hello, World!");
});

// те ж саме, що

Flight::route('/', function() {
    echo "Hello, World!";
});

Очиснення тіла відповіді

Якщо ви хочете очистити тіло відповіді, ви можете використовувати метод clearBody:

Flight::route('/', function() {
    if($someCondition) {
        Flight::response()->write("Hello, World!");
    } else {
        Flight::response()->clearBody();
    }
});

Виконання колбеку на тілі відповіді

Ви можете виконати колбек на тілі відповіді, використовуючи метод addResponseBodyCallback:

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);
});

// Це стисне всі відповіді для будь-якого маршруту
Flight::response()->addResponseBodyCallback(function($body) {
    return gzencode($body, 9);
});

Ви можете додати кілька колбеків, і вони будуть виконані в порядку, в якому були додані. Оскільки це може приймати будь-який викликаний, він може приймати масив класу [ $class, 'method' ], замикання $strReplace = function($body) { str_replace('hi', 'there', $body); };, або ім'я функції 'minify', якщо у вас є функція для мінімізації вашого коду html, наприклад.

Примітка: Колбеки маршрутів не працюватимуть, якщо ви використовуєте параметр конфігурації flight.v2.output_buffering.

Колбек для конкретного маршруту

Якщо ви хочете, щоб це застосовувалось лише до конкретного маршруту, ви можете додати колбек безпосередньо в маршрут:

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);

    // Це стисне лише відповідь для цього маршруту
    Flight::response()->addResponseBodyCallback(function($body) {
        return gzencode($body, 9);
    });
});

Опція Middleware

Ви також можете використовувати middleware, щоб застосувати колбек до всіх маршрутів через middleware:

// MinifyMiddleware.php
class MinifyMiddleware {
    public function before() {
        // Застосуйте колбек тут на об'єкті response().
        Flight::response()->addResponseBodyCallback(function($body) {
            return $this->minify($body);
        });
    }

    protected function minify(string $body): string {
        // мінімізуйте тіло якимось чином
        return $body;
    }
}

// index.php
Flight::group('/users', function() {
    Flight::route('', function() { /* ... */ });
    Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);

Встановлення заголовка відповіді

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


// Це надішле "Hello, World!" до браузера користувача у простому тексті
Flight::route('/', function() {
    Flight::response()->header('Content-Type', 'text/plain');
    // або
    Flight::response()->setHeader('Content-Type', 'text/plain');
    echo "Hello, World!";
});

JSON

Flight надає підтримку для надсилання JSON та JSONP відповідей. Щоб надіслати JSON відповідь, ви передаєте деякі дані для JSON кодування:

Flight::json(['id' => 123]);

Примітка: За замовчуванням Flight надішле заголовок Content-Type: application/json з відповіддю. Він також використовуватиме константи JSON_THROW_ON_ERROR і JSON_UNESCAPED_SLASHES під час кодування JSON.

JSON з кодом статусу

Ви також можете передати код статусу як другий аргумент:

Flight::json(['id' => 123], 201);

JSON з гарною розміткою

Ви також можете передати аргумент до останньої позиції, щоб увімкнути гарну розмітку:

Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);

Якщо ви змінюєте параметри, передані в Flight::json(), і хочете простішу синтаксис:

Flight::map('json', function($data, $code = 200, $options = 0) {
    Flight::_json($data, $code, true, 'utf-8', $options);
}

// І тепер це можна використовувати так
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON і зупинка виконання (v3.10.0)

Якщо ви хочете надіслати JSON відповідь і зупинити виконання, ви можете використовувати метод jsonHalt. Це корисно для випадків, коли ви перевіряєте авторизацію, і якщо користувач не авторизований, ви можете миттєво надіслати JSON відповідь, очистити вміст тіла і зупинити виконання.

Flight::route('/users', function() {
    $authorized = деякаПеревіркаАвторизації();
    // Перевірте, чи користувач авторизований
    if($authorized === false) {
        Flight::jsonHalt(['error' => 'Неавторизований'], 401);
    }

    // Продовжте з рештою маршруту
});

До версії v3.10.0 вам потрібно було зробити щось подібне:

Flight::route('/users', function() {
    $authorized = деякаПеревіркаАвторизації();
    // Перевірте, чи користувач авторизований
    if($authorized === false) {
        Flight::halt(401, json_encode(['error' => 'Неавторизований']));
    }

    // Продовжте з рештою маршруту
});

JSONP

Для запитів JSONP ви можете необов'язково передати параметр запиту, який ви використовуєте, щоб визначити вашу функцію колбеку:

Flight::jsonp(['id' => 123], 'q');

Отже, коли ви здійснюєте GET запит, використовуючи ?q=my_func, ви повинні отримати вихід:

my_func({"id":123});

Якщо ви не передасте параметр запиту, він за замовчуванням буде jsonp.

Перенаправлення на іншу URL-адресу

Ви можете перенаправити поточний запит, використовуючи метод redirect() і передаючи нову URL-адресу:

Flight::redirect('/new/location');

За замовчуванням Flight надсилає HTTP код статусу 303 ("See Other"). Ви можете необов'язково встановити кастомний код:

Flight::redirect('/new/location', 401);

Зупинка

Ви можете зупинити фреймворк в будь-який момент, викликавши метод halt:

Flight::halt();

Ви також можете вказати необов'язковий HTTP код статусу і повідомлення:

Flight::halt(200, 'Давайте повернемося...');

Виклик halt знищить будь-який контент відповіді до цього моменту. Якщо ви хочете зупинити фреймворк і вивести поточну відповідь, використовуйте метод stop:

Flight::stop();

Очищення даних відповіді

Ви можете очистити тіло відповіді та заголовки, використовуючи метод clear(). Це очистить будь-які заголовки, призначені відповіді, очистить тіло відповіді і встановить код статусу 200.

Flight::response()->clear();

Очищення тіла відповіді тільки

Якщо ви хочете очистити лише тіло відповіді, ви можете використовувати метод clearBody():

// Це все ще зберігатиме будь-які заголовки, встановлені на об'єкті response().
Flight::response()->clearBody();

HTTP кешування

Flight надає вбудовану підтримку для кешування на рівні HTTP. Якщо умова кешування виконана, Flight поверне HTTP 304 Not Modified відповідь. Наступного разу, коли клієнт запитає той же ресурс, йому буде запропоновано використовувати їх локально закешовану версію.

Кешування на рівні маршруту

Якщо ви хочете кешувати вашу всю відповідь, ви можете використовувати метод cache() і передати час для кешування.


// Це кешуватиме відповідь на 5 хвилин
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo 'Цей контент буде кешовано.';
});

// Альтернативно, ви можете використовувати рядок, який передали б
// методу strtotime()
Flight::route('/news', function () {
  Flight::response()->cache('+5 minutes');
  echo 'Цей контент буде кешовано.';
});

Останнє змінено

Ви можете використовувати метод lastModified і передати UNIX-мітку часу, щоб встановити дату і час, коли сторінка востаннє була змінена. Клієнт продовжуватиме використовувати свій кеш, поки значення останнього змінення не зміниться.

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo 'Цей контент буде кешовано.';
});

ETag

Кешування ETag подібне до Last-Modified, за винятком того, що ви можете вказати будь-який ідентифікатор, який ви хочете для ресурсу:

Flight::route('/news', function () {
  Flight::etag('my-unique-id');
  echo 'Цей контент буде кешовано.';
});

Пам'ятайте, що виклик або lastModified, або etag і обидва встановлять і перевірять значення кешу. Якщо значення кешу однакове між запитами, Flight негайно надішле відповідь HTTP 304 і зупинить обробку.

Завантаження файлу (v3.12.0)

Існує допоміжний метод для завантаження файлу. Ви можете використовувати метод download і передати шлях.

Flight::route('/download', function () {
  Flight::download('/path/to/file.txt');
});

Learn/events

Система подій у Flight PHP (v3.15.0+)

Flight PHP вводить легку та інтуїтивно зрозумілу систему подій, яка дозволяє вам реєструвати та активувати власні події у вашій програмі. З додаванням Flight::onEvent() та Flight::triggerEvent(), ви тепер можете підключатися до ключових моментів життєвого циклу вашого додатку або визначати свої власні події, щоб зробити ваш код більш модульним та розширювальним. Ці методи є частиною відображуваних методів Flight, що означає, що ви можете перевизначити їх поведінку відповідно до ваших потреб.

Цей посібник охоплює все, що вам потрібно знати, щоб почати працювати з подіями, включаючи чому вони цінні, як їх використовувати та практичні приклади, щоб допомогти новачкам зрозуміти їхню силу.

Чому варто використовувати події?

Події дозволяють вам розділити різні частини вашого додатку так, щоб вони не залежали занадто сильно один від одного. Це розділення — часто називане декуплінгом — робить ваш код легшим для оновлення, розширення чи налагодження. Замість того, щоб писати все в одному великому шматку, ви можете розділити вашу логіку на менші, незалежні частини, які реагують на конкретні дії (події).

Уявіть, що ви розробляєте додаток для блогу:

Без подій, ви б змусили все це в одну функцію. З подіями, ви можете розділити це: одна частина зберігає коментар, інша активує подію на кшталт 'comment.posted', а окремі слухачі обробляють електронну пошту та логування. Це зберігає ваш код чистішим та дозволяє вам додавати або видаляти функції (як-от сповіщення) без втручання в основну логіку.

Загальні застосування

Реєстрація слухачів подій

Щоб прослухати подію, використовуйте Flight::onEvent(). Цей метод дозволяє вам визначити, що повинно відбутися, коли подія виникає.

Синтаксис

Flight::onEvent(string $event, callable $callback): void

Як це працює

Ви "підписуєтеся" на подію, кажучи Flight, що робити, коли це відбувається. Колбек може приймати аргументи, передані з тригера події.

Система подій Flight є синхронною, що означає, що кожен слухач подій виконується в послідовності, один за одним. Коли ви активуєте подію, всі зареєстровані слухачі для цієї події будуть виконані до завершення, перш ніж ваш код продовжиться. Це важливо розуміти, оскільки це відрізняється від асинхронних систем подій, де слухачі можуть виконуватись паралельно або в пізніший час.

Простий приклад

Flight::onEvent('user.login', function ($username) {
    echo "Ласкаво просимо назад, $username!";
});

Тут, коли активується подія 'user.login', вона привітає користувача за іменем.

Ключові моменти

Активація подій

Щоб зробити подію активною, використовуйте Flight::triggerEvent(). Це говорить Flight виконати всіх слухачів, зареєстрованих для цієї події, передаючи будь-які дані, які ви надаєте.

Синтаксис

Flight::triggerEvent(string $event, ...$args): void

Простий приклад

$username = 'alice';
Flight::triggerEvent('user.login', $username);

Це активує подію 'user.login' і передає 'alice' слухачу, якого ми визначили раніше, який виведе: Ласкаво просимо назад, alice!.

Ключові моменти

Реєстрація слухачів подій

...

Зупинка подальших слухачів: Якщо слухач повертає false, жоден з наступних слухачів для цієї події не буде виконано. Це дозволяє вам зупинити ланцюг подій на основі конкретних умов. Пам'ятайте, що порядок слухачів має значення, оскільки перший, хто повертає false, зупинить решту від виконання.

Приклад:

Flight::onEvent('user.login', function ($username) {
    if (isBanned($username)) {
        logoutUser($username);
        return false; // Зупиняє наступні слухачі
    }
});
Flight::onEvent('user.login', function ($username) {
    sendWelcomeEmail($username); // цей ніколи не буде надіслано
});

Перевизначення методів подій

Flight::onEvent() та Flight::triggerEvent() доступні для розширення, що означає, що ви можете переозначити, як вони працюють. Це чудово підходить для просунутих користувачів, які хочуть налаштувати систему подій, наприклад, додати логування або змінити, як події передаються.

Приклад: Налаштування onEvent

Flight::map('onEvent', function (string $event, callable $callback) {
    // Логувати кожну реєстрацію події
    error_log("Додано новий слухач події для: $event");
    // Викликати стандартну поведінку (припускаючи внутрішню систему подій)
    Flight::_onEvent($event, $callback);
});

Тепер кожного разу, коли ви реєструєте подію, вона записується перед продовженням.

Чому перевизначати?

Де розмістити ваші події

Як новачок, ви можете запитати: де мені реєструвати всі ці події в моєму додатку? Простота Flight означає, що немає суворих правил — ви можете помістити їх туди, де це має сенс для вашого проекту. Проте, збереження їх в організованому вигляді допоможе вам підтримувати ваш код в міру зростання вашого додатку. Ось кілька практичних варіантів і найкращих практик, адаптованих до легкості Flight:

Варіант 1: У вашому основному index.php

Для невеликих додатків або швидких прототипів ви можете зареєструвати події прямо у вашому файлі index.php, поряд з вашими маршрутами. Це утримує все в одному місці, що є прийнятним, коли простота є вашим пріоритетом.

require 'vendor/autoload.php';

// Реєстрація подій
Flight::onEvent('user.login', function ($username) {
    error_log("$username увійшов о " . date('Y-m-d H:i:s'));
});

// Визначення маршрутів
Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "Увійшов!";
});

Flight::start();

Варіант 2: Окремий файл events.php

Для трохи більших додатків розгляньте можливість перенесення реєстрацій подій у спеціалізований файл, такий як app/config/events.php. Включіть цей файл у ваш index.php перед вашими маршрутами. Це імітує те, як маршрути часто організовані в app/config/routes.php у проектах Flight.

// app/config/events.php
Flight::onEvent('user.login', function ($username) {
    error_log("$username увійшов о " . date('Y-m-d H:i:s'));
});

Flight::onEvent('user.registered', function ($email, $name) {
    echo "Електронний лист надіслано на $email: Ласкаво просимо, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';

Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "Увійшов!";
});

Flight::start();

Варіант 3: Поруч з місцем, де їх активують

Ще один підхід - реєструвати події ближче до місця, де їх активують, наприклад, всередині контролера або визначення маршруту. Це добре працює, якщо подія специфічна для однієї частини вашого додатку.

Flight::route('/signup', function () {
    // Реєструвати подію тут
    Flight::onEvent('user.registered', function ($email) {
        echo "Сповіщення надіслано на $email!";
    });

    $email = 'jane@example.com';
    Flight::triggerEvent('user.registered', $email);
    echo "Зареєстровано!";
});

Найкраща практика для Flight

Порада: Групуйте за метою

У events.php, групуйте пов’язані події (наприклад, всі події, пов’язані з користувачами, разом) з коментарями для чіткості:

// app/config/events.php
// Події користувачів
Flight::onEvent('user.login', function ($username) {
    error_log("$username увійшов");
});
Flight::onEvent('user.registered', function ($email) {
    echo "Ласкаво просимо до $email!";
});

// Події сторінок
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]);
});

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

Приклади для новачків

Давайте пройдемося через кілька реальних сценаріїв, щоб показати, як працюють події і чому вони корисні.

Приклад 1: Логування входу користувача

// Крок 1: Реєстрація слухача
Flight::onEvent('user.login', function ($username) {
    $time = date('Y-m-d H:i:s');
    error_log("$username увійшов о $time");
});

// Крок 2: Активуйте це у вашому додатку
Flight::route('/login', function () {
    $username = 'bob'; // Уявімо, що це приходить з форми
    Flight::triggerEvent('user.login', $username);
    echo "Привіт, $username!";
});

Чому це корисно: Код входу не повинен знати про логування — він просто активує подію. Ви можете пізніше додати більше слухачів (наприклад, відправити вітальний лист) без зміни маршруту.

Приклад 2: Сповіщення про нових користувачів

// Слухач для нових реєстрацій
Flight::onEvent('user.registered', function ($email, $name) {
    // Імітувати відправлення електронного листа
    echo "Електронний лист надіслано на $email: Ласкаво просимо, $name!";
});

// Активуйте це, коли хтось реєструється
Flight::route('/signup', function () {
    $email = 'jane@example.com';
    $name = 'Jane';
    Flight::triggerEvent('user.registered', $email, $name);
    echo "Дякуємо за реєстрацію!";
});

Чому це корисно: Логіка реєстрації зосереджена на створенні користувача, тоді як подія обробляє сповіщення. Ви могли б додати більше слухачів (наприклад, залогувати реєстрацію) пізніше.

Приклад 3: Очистка кешу

// Слухач для очищення кешу
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]); // Очистити кеш сесії, якщо це доречно
    echo "Кеш очищено для сторінки $pageId.";
});

// Активуйте це, коли сторінка редагується
Flight::route('/edit-page/(@id)', function ($pageId) {
    // Уявімо, що ми оновили сторінку
    Flight::triggerEvent('page.updated', $pageId);
    echo "Сторінка $pageId оновлена.";
});

Чому це корисно: Код редагування не повинен піклуватися про кешування — він просто сигналізує про оновлення. Інші частини програми можуть реагувати на це за потребою.

Найкращі практики

Система подій у Flight PHP, з Flight::onEvent() та Flight::triggerEvent(), дає вам простий, але потужний спосіб створювати гнучкі додатки. Дозволяючи різним частинам вашого додатку спілкуватися одне з одним через події, ви можете зберігати ваш код організованим, повторно використаним і легким для розширення. Чи ви логіруєте дії, надсилаєте сповіщення чи управляєте оновленнями, події допомагають вам робити це без заплутування вашої логіки. Більше того, з можливістю перевизначення цих методів, ви отримуєте свободу налаштувати систему під ваші потреби. Почніть з маленької події і спостерігайте, як вона трансформує структуру вашого додатку!

Вбудовані події

Flight PHP поставляється з кількома вбудованими подіями, які ви можете використовувати для підключення до життєвого циклу фреймворку. Ці події активуються в певні моменти в циклі запит/відповідь, дозволяючи вам виконувати власну логіку, коли відбуваються певні дії.

Список вбудованих подій

Learn/templates

HTML Views та Шаблони

Flight надає деяку базову функціональність шаблонізації за замовчуванням.

Flight дозволяє вам замінити базовий механізм перегляду просто зареєструвавши свій клас представлення. Прокрутіть вниз, щоб побачити приклади використання Smarty, Latte, Blade та інших!

Вбудований механізм перегляду

Щоб відобразити шаблон представлення, викликайте метод render з ім'ям файлу шаблону та необов'язковими даними шаблону:

Flight::render('hello.php', ['name' => 'Bob']);

Дані шаблону, які ви передаєте, автоматично інжектуються у шаблон і можуть бути доступні як локальна змінна. Шаблони — це просто PHP файли. Якщо вміст файлу шаблону hello.php є:

Hello, <?= $name ?>!

Вихід буде:

Hello, Bob!

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

Flight::view()->set('name', 'Bob');

Змінна name тепер доступна у всіх ваших представленнях. Тому ви можете просто зробити:

Flight::render('hello');

Зверніть увагу, що при вказуванні імені шаблону в методі render ви можете не вказувати розширення .php.

За замовчуванням Flight буде шукати каталог views для файлів шаблонів. Ви можете вказати альтернативний шлях для ваших шаблонів, задавши наступну конфігурацію:

Flight::set('flight.views.path', '/path/to/views');

Макети

Зазвичай веб-сайти мають один шаблон макета з мінливим вмістом. Щоб відобразити вміст, який слід використовувати в макеті, ви можете передати необов'язковий параметр до методу render.

Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');

Ваше представлення тепер матиме збережені змінні, звані headerContent та bodyContent. Ви можете відобразити свій макет, зробивши:

Flight::render('layout', ['title' => 'Home Page']);

Якщо файли шаблонів виглядають ось так:

header.php:

<h1><?= $heading ?></h1>

body.php:

<div><?= $body ?></div>

layout.php:

<html>
  <head>
    <title><?= $title ?></title>
  </head>
  <body>
    <?= $headerContent ?>
    <?= $bodyContent ?>
  </body>
</html>

Вихід буде:

<html>
  <head>
    <title>Home Page</title>
  </head>
  <body>
    <h1>Hello</h1>
    <div>World</div>
  </body>
</html>

Smarty

Ось як ви можете використовувати шаблонний механізм Smarty для своїх представлень:

// Завантажте бібліотеку Smarty
require './Smarty/libs/Smarty.class.php';

// Зареєструйте Smarty як клас представлення
// Також передайте функцію зворотного виклику для налаштування Smarty при завантаженні
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
  $smarty->setCacheDir('./cache/');
});

// Призначте дані шаблону
Flight::view()->assign('name', 'Bob');

// Відобразіть шаблон
Flight::view()->display('hello.tpl');

Для повноти ви також повинні переозначити стандартний метод render Flight:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

Latte

Ось як ви можете використовувати шаблонний механізм Latte для своїх представлень:

// Зареєструйте Latte як клас представлення
// Також передайте функцію зворотного виклику для налаштування Latte при завантаженні
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) {
  // Тут Latte буде кешувати ваші шаблони, щоб прискорити процес
    // Однією з цікавих функцій Latte є те, що він автоматично оновлює ваш
    // кеш, коли ви вносите зміни до ваших шаблонів!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Скажіть Latte, де буде коренева директорія для ваших представлень.
    $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});

// І завершіть так, щоб ви могли правильно використовувати Flight::render()
Flight::map('render', function(string $template, array $data): void {
  // Це як $latte_engine->render($template, $data);
  echo Flight::view()->render($template, $data);
});

Blade

Ось як ви можете використовувати шаблонний механізм Blade для своїх представлень:

Спочатку вам потрібно встановити бібліотеку BladeOne за допомогою Composer:

composer require eftec/bladeone

Тоді ви можете налаштувати BladeOne як клас представлення в Flight:

<?php
// Завантажте бібліотеку BladeOne
use eftec\bladeone\BladeOne;

// Зареєструйте BladeOne як клас представлення
// Також передайте функцію зворотного виклику для налаштування BladeOne при завантаженні
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
  $views = __DIR__ . '/../views';
  $cache = __DIR__ . '/../cache';

  $blade->setPath($views);
  $blade->setCompiledPath($cache);
});

// Призначте дані шаблону
Flight::view()->share('name', 'Bob');

// Відобразіть шаблон
echo Flight::view()->run('hello', []);

Для повноти ви також повинні переозначити стандартний метод render Flight:

<?php
Flight::map('render', function(string $template, array $data): void {
  echo Flight::view()->run($template, $data);
});

У цьому прикладі файл шаблону hello.blade.php може виглядати ось так:

<?php
Hello, {{ $name }}!

Вихід буде:

Hello, Bob!

Дотримуючись цих кроків, ви можете інтегрувати механізм шаблонів Blade з Flight і використовувати його для візуалізації ваших представлень.

Learn/flight_vs_fat_free

Flight vs Fat-Free

Що таке Fat-Free?

Fat-Free (ласкаво відомий як F3) є потужним, але простим у використанні мікрофреймворком PHP, призначеним для допомоги у створенні динамічних і надійних веб-додатків - швидко!

Flight порівнюється з Fat-Free в багатьох аспектах і, мабуть, є найближчим родичем у термінах функціональності та простоти. Fat-Free має багато функцій, яких немає у Flight, але також має і багато функцій, які є у Flight. Fat-Free починає демонструвати свою старість і вже не є так популярним, як раніше.

Оновлення стають менш частими, а спільнота не така активна. Код досить простий, але іноді відсутність дисципліни в синтаксисі може ускладнити читання та розуміння. Він працює з PHP 8.3, але сам код все ще виглядає так, ніби живе у PHP 5.3.

Плюси у порівнянні з Flight

Мінуси у порівнянні з Flight

Learn/extending

Розширення

Flight розроблений як розширювальний фреймворк. Фреймворк постачається з набором за замовчуванням методів та компонентів, але дозволяє вам відображати свої власні методи, реєструвати свої власні класи або навіть перевизначати існуючі класи та методи.

Якщо ви шукаєте DIC (Контейнер впровадження залежностей), перейдіть на Сторінка контейнера впровадження залежностей.

Відображення методів

Щоб відобразити свій власний простий кастомний метод, використовуйте функцію map:

// Відобразіть свій метод
Flight::map('hello', function (string $name) {
  echo "привіт $name!";
});

// Викличте свій кастомний метод
Flight::hello('Боб');

Хоча можливо створити прості кастомні методи, рекомендується просто створювати стандартні функції в PHP. Це має автозаповнення в IDE і легше читається. Еквівалентом наведеного вище коду буде:

function hello(string $name) {
  echo "привіт $name!";
}

hello('Боб');

Це використовується більше, коли вам потрібно передати змінні у ваш метод, щоб отримати очікуване значення. Використання методу register() як наведено нижче більше підходить для передачі конфігурацій і потім виклику вашого попередньо налаштованого класу.

Реєстрація класів

Щоб зареєструвати свій власний клас і налаштувати його, використовуйте функцію register:

// Зареєструйте свій клас
Flight::register('user', User::class);

// Отримайте екземпляр свого класу
$user = Flight::user();

Метод реєстрації також дозволяє вам передавати параметри в конструктор вашого класу. Отже, коли ви завантажуєте свій кастомний клас, він буде попередньо ініціалізований. Ви можете визначити параметри конструктора, передавши додатковий масив. Ось приклад завантаження з'єднання з базою даних:

// Зареєструйте клас з параметрами конструктора
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);

// Отримайте екземпляр свого класу
// Це створить об'єкт з визначеними параметрами
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();

// і якщо вам це знадобиться пізніше у вашому коді, ви просто знову викликаєте той же метод
class SomeController {
  public function __construct() {
    $this->db = Flight::db();
  }
}

Якщо ви передасте додатковий параметр зворотного виклику, він буде виконаний негайно після конструкції класу. Це дозволяє вам виконати будь-які процедури налаштування для вашого нового об'єкта. Функція зворотного виклику приймає один параметр, екземпляр нового об'єкта.

// Зворотний виклик отримає об'єкт, що був сконструйований
Flight::register(
  'db',
  PDO::class,
  ['mysql:host=localhost;dbname=test', 'user', 'pass'],
  function (PDO $db) {
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  }
);

За замовчуванням, кожного разу, коли ви завантажуєте свій клас, ви отримуєте спільний екземпляр. Щоб отримати новий екземпляр класу, просто передайте false як параметр:

// Спільний екземпляр класу
$shared = Flight::db();

// Новий екземпляр класу
$new = Flight::db(false);

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

Логування

Flight не має вбудованої системи логування, однак, досить легко використовувати бібліотеку логування з Flight. Ось приклад, що використовує бібліотеку Monolog:

// index.php або bootstrap.php

// Зареєструйте логгер з Flight
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
    $log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});

Тепер, коли він зареєстрований, ви можете використовувати його у вашій програмі:

// У вашому контролері або маршруті
Flight::log()->warning('Це повідомлення про попередження');

Це запише повідомлення у файл журналу, який ви вказали. Що якщо ви хочете зафіксувати щось, коли відбувається помилка? Ви можете використовувати метод error:

// У вашому контролері або маршруті

Flight::map('error', function(Throwable $ex) {
    Flight::log()->error($ex->getMessage());
    // Відобразіть свою кастомну сторінку помилки
    include 'errors/500.html';
});

Ви також можете створити базову систему APM (Моніторинг продуктивності додатка), використовуючи методи before та after:

// У вашому файлі старту

Flight::before('start', function() {
    Flight::set('start_time', microtime(true));
});

Flight::after('start', function() {
    $end = microtime(true);
    $start = Flight::get('start_time');
    Flight::log()->info('Запит '.Flight::request()->url.' тривав ' . round($end - $start, 4) . ' секунд');

    // Ви також можете додати свої заголовки запиту або відповіді
    // щоб зафіксувати їх (будьте обережні, бо це буде 
    // багато даних, якщо у вас багато запитів)
    Flight::log()->info('Заголовки запиту: ' . json_encode(Flight::request()->headers));
    Flight::log()->info('Заголовки відповіді: ' . json_encode(Flight::response()->headers));
});

Перевизначення методів фреймворку

Flight дозволяє вам перевизначати свою стандартну функціональність відповідно до ваших потреб, не вносячи зміни в код. Ви можете переглянути всі методи, які можете перевизначити тут.

Наприклад, коли Flight не може співвіднести URL з маршрутом, він викликає метод notFound, який надсилає загальний HTTP 404 відповідь. Ви можете перевизначити цю поведінку за допомогою методу map:

Flight::map('notFound', function() {
  // Відобразіть кастомну 404 сторінку
  include 'errors/404.html';
});

Flight також дозволяє вам замінити основні компоненти фреймворку. Наприклад, ви можете замінити стандартний клас Router своїм кастомним класом:

// Зареєструйте свій кастомний клас
Flight::register('router', MyRouter::class);

// Коли Flight завантажує екземпляр Router, він завантажить ваш клас
$myrouter = Flight::router();

Методи фреймворку, такі як map та register, однак не можуть бути перевизначені. Ви отримаєте помилку, якщо спробуєте це зробити.

Learn/flight_vs_slim

Flight vs Slim

Що таке Slim?

Slim - це мікро-фреймворк PHP, який допомагає вам швидко писати прості, але потужні веб-додатки та API.

Багато з натхнення для деяких з функцій v3 Flight насправді прийшло від Slim. Групування маршрутів і виконання проміжного програмного забезпечення в конкретному порядку - це дві функції, які були натхнені Slim. Slim v3 з'явився, спрямований на простоту, але були змішані відгуки щодо v4.

Переваги у порівнянні з Flight

Недоліки у порівнянні з Flight

Learn/autoloading

Автозавантаження

Автозавантаження є концепцією в PHP, де ви вказуєте директорію або директорії для завантаження класів. Це набагато вигідніше, ніж використовувати require або include для завантаження класів. Це також є вимогою для використання пакетів Composer.

За замовчуванням будь-який клас Flight автоматично завантажується завдяки composer. Однак, якщо ви хочете автозавантажити власні класи, ви можете використовувати метод Flight::path(), щоб вказати директорію для завантаження класів.

Основний приклад

Припустимо, у нас є дерево директорій, як наведено нижче:

# Приклад шляху
/home/user/project/my-flight-project/
├── app
│   ├── cache
│   ├── config
│   ├── controllers - містить контролери для цього проекту
│   ├── translations
│   ├── UTILS - містить класи лише для цього застосунку (це все в верхньому регістрі для прикладу далі)
│   └── views
└── public
    └── css
    └── js
    └── index.php

Ви, мабуть, помітили, що це та сама структура файлів, що і на цьому сайті документації.

Ви можете вказати кожну директорію для завантаження так:


/**
 * public/index.php
 */

// Додати шлях до автозавантажувача
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

/**
 * app/controllers/MyController.php
 */

// не потрібна неймспейсінг

// Всіх автозавантажених класів рекомендується використовувати Pascal Case (кожне слово з великої літери, без пробілів)
// Починаючи з 3.7.2, ви можете використовувати Pascal_Snake_Case для ваших імен класів, запустивши Loader::setV2ClassLoading(false);
class MyController {

    public function index() {
        // зробіть щось
    }
}

Неймспейси

Якщо ви маєте неймспейси, це стає дуже простим для реалізації. Вам слід використовувати метод Flight::path(), щоб вказати кореневу директорію (не кореневу директорію документа чи папку public/) вашого застосунку.


/**
 * public/index.php
 */

// Додати шлях до автозавантажувача
Flight::path(__DIR__.'/../');

Тепер ваш контролер може виглядати так. Посмотріть на приклад нижче, але зверніть увагу на коментарі для важливої інформації.

/**
 * app/controllers/MyController.php
 */

// неймспейси є обов'язковими
// неймспейси є такими ж, як структура директорій
// неймспейси повинні відповідати такому ж регістру, як структура директорій
// неймспейси та директорії не можуть містити жодних підкреслень (якщо не встановлено Loader::setV2ClassLoading(false))
namespace app\controllers;

// Всіх автозавантажених класів рекомендується використовувати Pascal Case (кожне слово з великої літери, без пробілів)
// Починаючи з 3.7.2, ви можете використовувати Pascal_Snake_Case для ваших імен класів, запустивши Loader::setV2ClassLoading(false);
class MyController {

    public function index() {
        // зробіть щось
    }
}

І якщо ви хочете автозавантажити клас у вашій директорії утиліт, ви будете робити в основному те ж саме:


/**
 * app/UTILS/ArrayHelperUtil.php
 */

// неймспейс повинен відповідати структурі директорії та регістру (зауважте, що директорія UTILS у верхньому регістрі
//     як у дереві файлів вище)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // зробіть щось
    }
}

Підкреслення в іменах класів

Починаючи з 3.7.2, ви можете використовувати Pascal_Snake_Case для ваших імен класів, запустивши Loader::setV2ClassLoading(false);. Це дозволить вам використовувати підкреслення в ваших іменах класів. Це не рекомендується, але це доступно для тих, хто це потребує.


/**
 * public/index.php
 */

// Додати шлях до автозавантажувача
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

/**
 * app/controllers/My_Controller.php
 */

// не потрібна неймспейсінг

class My_Controller {

    public function index() {
        // зробіть щось
    }
}

Learn/troubleshooting

Виправлення несправностей

Ця сторінка допоможе вам вирішити поширені проблеми, з якими ви можете зіткнутися при використанні Flight.

Поширені проблеми

404 Не знайдено або несподівана поведінка маршруту

Якщо ви бачите помилку 404 Не знайдено (але клянетесь, що це дійсно там і це не помилка), це може бути проблемою з тим, що ви повертаєте значення у вашому маршруті замість того, щоб просто виводити його. Причина цього є навмисною, але може підкочуватися до деяких розробників.


Flight::route('/hello', function(){
    // Це може викликати помилку 404 Не знайдено
    return 'Hello World';
});

// Що ви, напевно, хочете
Flight::route('/hello', function(){
    echo 'Hello World';
});

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

Клас не знайдено (автозавантаження не працює)

Можливо, є кілька причин, чому це не відбувається. Нижче наведено кілька прикладів, але переконайтеся, що ви також переглянули розділ автозавантаження.

Неправильне ім'я файлу

Найпоширенішою є те, що ім'я класу не відповідає імені файлу.

Якщо у вас є клас під назвою MyClass, то файл повинен називатися MyClass.php. Якщо у вас є клас під назвою MyClass, а файл називається myclass.php, тоді автозавантажувач не зможе його знайти.

Неправильний простір імен

Якщо ви використовуєте простори імен, то простір імен має відповідати структурі каталогу.

// код

// якщо ваш MyController знаходиться в каталозі app/controllers і має простір імен
// це не спрацює.
Flight::route('/hello', 'MyController->hello');

// вам потрібно вибрати один з цих варіантів
Flight::route('/hello', 'app\controllers\MyController->hello');
// або якщо у вас є оператор use на початку

use app\controllers\MyController;

Flight::route('/hello', [ MyController::class, 'hello' ]);
// також можна записати
Flight::route('/hello', MyController::class.'->hello');
// також...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);

path() не визначено

У скелетній програмі це визначено в файлі config.php, але для того, щоб ваші класи були знайдені, вам потрібно переконатися, що метод path() визначено (ймовірно, до кореня вашого каталогу) перед тим, як ви спробуєте його використовувати.


// Додайте шлях до автозавантажувача
Flight::path(__DIR__.'/../');

Guides/blog

Будівництво простого блогу з Flight PHP

Цей посібник проведе вас через створення базового блогу з використанням фреймворку Flight PHP. Ви налаштуєте проект, визначите маршрути, керуватимете дописами з JSON та відображатимете їх за допомогою движка шаблонів Latte, демонструючи простоту та гнучкість Flight. Наприкінці у вас буде функціональний блог з головною сторінкою, індивідуальними сторінками дописів та формою створення.

Потреби

Крок 1: Налаштування вашого проекту

Почніть зі створення нового каталогу проекту та встановлення Flight через Composer.

  1. Створіть каталог:

    mkdir flight-blog
    cd flight-blog
  2. Встановіть Flight:

    composer require flightphp/core
  3. Створіть публічний каталог: Flight використовує єдину точку входу (index.php). Створіть папку public/ для цього:

    mkdir public
  4. Основний index.php: Створіть public/index.php з простою маршрутом "hello world":

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
       echo 'Привіт, Flight!';
    });
    
    Flight::start();
  5. Запустіть вбудований сервер: Перевірте вашу установку за допомогою розробницького сервера PHP:

    php -S localhost:8000 -t public/

    Відвідайте http://localhost:8000, щоб побачити "Привіт, Flight!".

Крок 2: Організуйте структуру вашого проекту

Для чистого налаштування структуруйте ваш проект ось так:

flight-blog/
├── app/
│   ├── config/
│   └── views/
├── data/
├── public/
│   └── index.php
├── vendor/
└── composer.json

Крок 3: Встановіть та налаштуйте Latte

Latte - це легкий движок шаблонів, який добре інтегрується з Flight.

  1. Встановіть Latte:

    composer require latte/latte
  2. Налаштуйте Latte в Flight: Оновіть public/index.php, щоб зареєструвати Latte як двигун відображення:

    <?php
    require '../vendor/autoload.php';
    
    use Latte\Engine;
    
    Flight::register('view', Engine::class, [], function ($latte) {
       $latte->setTempDirectory(__DIR__ . '/../cache/');
       $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/'));
    });
    
    Flight::route('/', function () {
       Flight::view()->render('home.latte', ['title' => 'Мій блог']);
    });
    
    Flight::start();
  3. Створіть шаблон макету: У app/views/layout.latte:

    <!DOCTYPE html>
    <html>
    <head>
    <title>{$title}</title>
    </head>
    <body>
    <header>
        <h1>Мій блог</h1>
        <nav>
            <a href="/">Головна</a> | 
            <a href="/create">Створити допис</a>
        </nav>
    </header>
    <main>
        {block content}{/block}
    </main>
    <footer>
        <p>&copy; {date('Y')} Блог Flight</p>
    </footer>
    </body>
    </html>
  4. Створіть домашній шаблон: У app/views/home.latte:

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$title}</h2>
        <ul>
        {foreach $posts as $post}
            <li><a href="/post/{$post['slug']}">{$post['title']}</a></li>
        {/foreach}
        </ul>
    {/block}

    Перезапустіть сервер, якщо ви вийшли з нього, та відвідайте http://localhost:8000, щоб побачити відображену сторінку.

  5. Створіть файл даних:

    Використовуйте JSON-файл, щоб імітувати базу даних для спрощення.

    У data/posts.json:

    [
       {
           "slug": "first-post",
           "title": "Мій перший допис",
           "content": "Це мій перший допис у блозі з Flight PHP!"
       }
    ]

Крок 4: Визначте маршрути

Розділіть ваші маршрути на файл конфігурації для кращої організації.

  1. Створіть routes.php: У app/config/routes.php:

    <?php
    Flight::route('/', function () {
       Flight::view()->render('home.latte', ['title' => 'Мій блог']);
    });
    
    Flight::route('/post/@slug', function ($slug) {
       Flight::view()->render('post.latte', ['title' => 'Допис: ' . $slug, 'slug' => $slug]);
    });
    
    Flight::route('GET /create', function () {
       Flight::view()->render('create.latte', ['title' => 'Створити допис']);
    });
  2. Оновіть index.php: Додайте файл маршрутів:

    <?php
    require '../vendor/autoload.php';
    
    use Latte\Engine;
    
    Flight::register('view', Engine::class, [], function ($latte) {
       $latte->setTempDirectory(__DIR__ . '/../cache/');
       $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/'));
    });
    
    require '../app/config/routes.php';
    
    Flight::start();

Крок 5: Зберігайте та отримуйте дописи блогу

Додайте методи для завантаження і збереження дописів.

  1. Додайте методи дописів: У index.php додайте метод для завантаження дописів:

    Flight::map('posts', function () {
       $file = __DIR__ . '/../data/posts.json';
       return json_decode(file_get_contents($file), true);
    });
  2. Оновіть маршрути: Змініть app/config/routes.php, щоб використовувати дописи:

    <?php
    Flight::route('/', function () {
       $posts = Flight::posts();
       Flight::view()->render('home.latte', [
           'title' => 'Мій блог',
           'posts' => $posts
       ]);
    });
    
    Flight::route('/post/@slug', function ($slug) {
       $posts = Flight::posts();
       $post = array_filter($posts, fn($p) => $p['slug'] === $slug);
       $post = reset($post) ?: null;
       if (!$post) {
           Flight::notFound();
           return;
       }
       Flight::view()->render('post.latte', [
           'title' => $post['title'],
           'post' => $post
       ]);
    });
    
    Flight::route('GET /create', function () {
       Flight::view()->render('create.latte', ['title' => 'Створити допис']);
    });

Крок 6: Створіть шаблони

Оновіть ваші шаблони, щоб відображати дописи.

  1. Сторінка допису (app/views/post.latte):

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$post['title']}</h2>
        <div class="post-content">
            <p>{$post['content']}</p>
        </div>
    {/block}

Крок 7: Додайте створення дописів

Обробіть подання форми для додавання нових дописів.

  1. Створіть форму (app/views/create.latte):

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$title}</h2>
        <form method="POST" action="/create">
            <div class="form-group">
                <label for="title">Назва:</label>
                <input type="text" name="title" id="title" required>
            </div>
            <div class="form-group">
                <label for="content">Зміст:</label>
                <textarea name="content" id="content" required></textarea>
            </div>
            <button type="submit">Зберегти допис</button>
        </form>
    {/block}
  2. Додайте POST-маршрут: У app/config/routes.php:

    Flight::route('POST /create', function () {
       $request = Flight::request();
       $title = $request->data['title'];
       $content = $request->data['content'];
       $slug = strtolower(str_replace(' ', '-', $title));
    
       $posts = Flight::posts();
       $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content];
       file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT));
    
       Flight::redirect('/');
    });
  3. Перевірте це:

    • Відвідайте http://localhost:8000/create.
    • Надішліть новий допис (наприклад, "Другий допис" з деяким змістом).
    • Перевірте головну сторінку, щоб побачити його в списку.

Крок 8: Поліпшіть обробку помилок

Перевизначте метод notFound для покращеного досвіду 404.

У index.php:

Flight::map('notFound', function () {
    Flight::view()->render('404.latte', ['title' => 'Сторінка не знайдена']);
});

Створіть app/views/404.latte:

{extends 'layout.latte'}

{block content}
    <h2>404 - {$title}</h2>
    <p>Вибачте, така сторінка не існує!</p>
{/block}

Наступні кроки

Висновок

Ви побудували простий блог з Flight PHP! Цей посібник демонструє основні функції, такі як маршрутизація, шаблонізація з Latte та обробка подань форм, зберігаючи все легким. Досліджуйте документацію Flight для вивчення більш розвинених функцій, щоб підвищити ваш блог!

License

Ліцензія MIT (MIT)

Авторське право © 2024 @mikecao, @n0nag0n

Цим надається дозвіл, безкоштовно, будь-якій особі, яка отримала копію цього програмного забезпечення та супутньої документації файлів (далі — “Програмне забезпечення”), користуватися Програмним забезпеченням без обмежень, включаючи без обмежень права на використання, копіювання, модифікацію, об’єднання, публікацію, розповсюдження, підліцензування та/або продаж копій Програмного забезпечення, а також дозволяти особам, яким Програмне забезпечення надано, робити це, за умов дотримання наступних умов:

Вищезазначене повідомлення про авторські права та це повідомлення про дозвіл повинні бути включені в усі копії або значні частини Програмного забезпечення.

ПРОГРАМНЕ ЗАБЕЗПЕЧЕННЯ НАДАЄТЬСЯ “ЯК Є”, БЕЗ ГАРАНТІЙ БУДЬ-ЯКОГО РОДУ, ЯВНИХ АБО ПРИХОВАНИХ, ВКЛЮЧАЮЧИ, АЛЕ НЕ ОБМЕЖУЮЧИСЬ ГАРАНТІЯМИ КОМЕРЦІЙНОЇ РІЗНИЧКИ, ПРИДАТНОСТІ ДЛЯ ПЕВНОЇ МЕТИ ТА НЕПORУШЕННЯ. У ЖОДНОМУ ВИПАДКУ АВТОРИ АБО ВЛАСНИКИ АВТОРСЬКИХ ПРАВ НЕ НЕСУТЬ ВІДПОВІДАЛЬНОСТІ ЗА БУДЬ-ЯКІ ПРЕТЕНЗІЇ, ЗБИТКИ АБО ІНШІ ЗОБОВ'ЯЗАННЯ, ЧИ У ПРАВОВІЙ СПРАВІ, ДЕЛІКТІ АБО ІНШОМУ, ЩО ВИНИКЛО, ВИРІСШЕ З, АБО У ЗВ'ЯЗКУ З ПРОГРАМНИМ ЗАБЕЗПЕЧЕННЯМ АБО ВИКОРИСТАННЯМ АБО ІНШИМИ УГОДАМИ У ПРОГРАМНОМУ ЗАБЕЗПЕЧЕННІ.

About

Що таке Flight?

Flight – це швидкий, простий, розширювальний фреймворк для PHP. Він досить універсальний і може бути використаний для створення будь-якого виду веб-додатків. Він побудований з урахуванням простоти і написаний так, щоб його було легко зрозуміти та використовувати.

Flight – це чудовий фреймворк для початківців, які нові у PHP і хочуть навчитися створювати веб-додатки. Це також чудовий фреймворк для досвідчених розробників, які хочуть мати більше контролю над своїми веб-додатками. Він спроектований для легкого створення RESTful API, простого веб-додатка або складного веб-додатка.

Швидкий старт

Перш ніж його встановити, використовуючи Composer

composer require flightphp/core

або ви можете завантажити zip-архів репозиторію тут. Тоді у вас буде базовий файл index.php, схожий на наступний:

<?php

// якщо встановлено за допомогою composer
require 'vendor/autoload.php';
// або якщо встановлено вручну за допомогою zip-файлу
// require 'flight/Flight.php';

Flight::route('/', function() {
  echo 'привіт, світ!';
});

Flight::route('/json', function() {
  Flight::json(['привіт' => 'світ']);
});

Flight::start();

Ось і все! У вас є базовий додаток Flight. Тепер ви можете запустити цей файл за допомогою php -S localhost:8000 і відвідати http://localhost:8000 у вашому браузері, щоб побачити результат.

Чи швидкий він?

Так! Flight швидкий. Він є одним з найбільш швидких фреймворків PHP. Ви можете побачити всі бенчмарки на TechEmpower

Дивіться бенчмарк нижче з деякими іншими популярними фреймворками PHP.

Фреймворк Plaintext Reqs/sec JSON Reqs/sec
Flight 190,421 182,491
Yii 145,749 131,434
Fat-Free 139,238 133,952
Slim 89,588 87,348
Phalcon 95,911 87,675
Symfony 65,053 63,237
Lumen 40,572 39,700
Laravel 26,657 26,901
CodeIgniter 20,628 19,901

Скелет/Шаблон додатка

Існує приклад додатка, який може допомогти вам почати працювати з фреймворком Flight. Перейдіть на flightphp/skeleton для отримання інструкцій, як почати! Ви також можете відвідати сторінку прикладів для натхнення щодо деяких речей, які ви можете зробити з Flight.

Спільнота

Ми в Matrix Chat

Matrix

І в Discord

Участь

Є два способи, якими ви можете внести свій внесок у Flight:

  1. Ви можете внести свій внесок у основний фреймворк, відвідавши основний репозиторій.
  2. Ви можете внести свій внесок у документацію. Цей веб-сайт документації розміщено на Github. Якщо ви помітите помилку або хочете вдосконалити щось, не соромтеся виправити це та надіслати запит на злиття! Ми намагаємося стежити за речами, але оновлення та переклади мов бажані.

Вимоги

Flight вимагає PHP 7.4 або новішої версії.

Примітка: PHP 7.4 підтримується, оскільки на момент написання (2024) PHP 7.4 є версією за замовчуванням для деяких LTS-дистрибутивів Linux. Примусове переходження на PHP >8 викликало б багато труднощів для цих користувачів. Фреймворк також підтримує PHP >8.

Ліцензія

Flight випущений під ліцензією MIT.

Awesome-plugins/php_cookie

Cookies

overclokk/cookie — це проста бібліотека для керування куки у вашому додатку.

Installation

Встановлення є простим за допомогою composer.

composer require overclokk/cookie

Usage

Використання таке ж просте, як реєстрація нового методу в класі Flight.


use Overclokk\Cookie\Cookie;

/*
 * Встановіть у вашому bootstrap або public/index.php файлі
 */

Flight::register('cookie', Cookie::class);

/**
 * ExampleController.php
 */

class ExampleController {
    public function login() {
        // Встановіть куки

        // ви захочете, щоб це було false, щоб отримати новий екземпляр
        // використовуйте наведену нижче коментар, якщо хочете автозаповнення
        /** @var \Overclokk\Cookie\Cookie $cookie */
        $cookie = Flight::cookie(false);
        $cookie->set(
            'stay_logged_in', // назва куки
            '1', // значення, яке ви хочете встановити
            86400, // кількість секунд, протягом яких куки повинні існувати
            '/', // шлях, за яким куки будуть доступні
            'example.com', // домен, за яким куки будуть доступні
            true, // куки будуть передаватися лише через безпечне HTTPS з'єднання
            true // куки будуть доступні лише через HTTP протокол
        );

        // за бажанням, якщо ви хочете зберегти значення за замовчуванням
        // і мати швидкий спосіб встановити куки на тривалий час
        $cookie->forever('stay_logged_in', '1');
    }

    public function home() {
        // Перевірте, чи маєте ви куки
        if (Flight::cookie()->has('stay_logged_in')) {
            // помістіть їх у область інформаційної панелі, наприклад.
            Flight::redirect('/dashboard');
        }
    }
}

Awesome-plugins/php_encryption

PHP Шифрування

defuse/php-encryption — це бібліотека, яку можна використовувати для шифрування та дешифрування даних. Запуск і налаштування досить прості, щоб почати шифрування та дешифрування даних. У них є чудовий посібник, який допомагає пояснити основи використання бібліотеки, а також важливі питання безпеки, пов’язані із шифруванням.

Встановлення

Встановлення просте за допомогою composer.

composer require defuse/php-encryption

Налаштування

Потім вам потрібно згенерувати ключ шифрування.

vendor/bin/generate-defuse-key

Це видасть ключ, який вам потрібно зберегти в безпеці. Ви можете зберегти ключ у вашому app/config/config.php файлі в масиві внизу файлу. Хоча це не ідеальне місце, але принаймні щось.

Використання

Тепер, коли у вас є бібліотека та ключ шифрування, ви можете почати шифрування та дешифрування даних.


use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

/*
 * Встановіть у вашому bootstrap або public/index.php файлі
 */

// Метод шифрування
Flight::map('encrypt', function($raw_data) {
    $encryption_key = /* $config['encryption_key'] або file_get_contents з того, де ви помістили ключ */;
    return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});

// Метод дешифрування
Flight::map('decrypt', function($encrypted_data) {
    $encryption_key = /* $config['encryption_key'] або file_get_contents з того, де ви помістили ключ */;
    try {
        $raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
        // Атака! Або був завантажений неправильний ключ, або шифротекст було
        // змінено з моменту його створення — або пошкоджено в базі даних, або
        // навмисно змінено Евой, яка намагається здійснити атаку.

        // ... обробіть цей випадок таким чином, як це підходить для вашого застосунку ...
    }
    return $raw_data;
});

Flight::route('/encrypt', function() {
    $encrypted_data = Flight::encrypt('Це секрет');
    echo $encrypted_data;
});

Flight::route('/decrypt', function() {
    $encrypted_data = '...'; // Отримати зашифровані дані звідкись
    $decrypted_data = Flight::decrypt($encrypted_data);
    echo $decrypted_data;
});

Awesome-plugins/php_file_cache

flightphp/cache

Легка, проста та автономна клас кешування PHP в файлі

Переваги

Цей сайт документації використовує цю бібліотеку для кешування кожної зі сторінок!

Натисніть тут, щоб переглянути код.

Встановлення

Встановіть через composer:

composer require flightphp/cache

Використання

Використання досить просте. Це зберігає кеш-файл в каталозі кешу.

use flight\Cache;

$app = Flight::app();

// Ви передаєте каталог, в якому буде зберігатися кеш, у конструктор
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {

    // Це забезпечує використання кешу лише в режимі виробництва
    // ENVIRONMENT - це константа, яка встановлюється у вашому файлі завантаження або в іншому місці вашого додатку
    $cache->setDevMode(ENVIRONMENT === 'development');
});

Тоді ви можете використовувати його у своєму коді ось так:


// Отримати екземпляр кешу
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // повертає дані для кешування
}, 10); // 10 секунд

// або
$data = $cache->retrieve('simple-cache-test');
if(empty($data)) {
    $data = date("H:i:s");
    $cache->store('simple-cache-test', $data, 10); // 10 секунд
}

Документація

Відвідайте https://github.com/flightphp/cache, щоб отримати повну документацію, і обов'язково подивіться на папку прикладів.

Awesome-plugins/permissions

FlightPHP/Permissions

Це модуль дозволів, який можна використовувати у ваших проектах, якщо у вас є кілька ролей у вашому додатку, і кожна роль має трохи відмінну функціональність. Цей модуль дозволяє вам визначити дозволи для кожної ролі, а потім перевірити, чи має поточний користувач дозвіл на доступ до певної сторінки або виконання певної дії.

Клікніть тут, щоб перейти до репозиторію на GitHub.

Встановлення

Запустіть composer require flightphp/permissions, і ви на правильному шляху!

Використання

Спочатку вам потрібно налаштувати ваші дозволи, потім ви скажете вашому додатку, що означають ці дозволи. Врешті-решт, ви будете перевіряти ваші дозволи за допомогою $Permissions->has(), ->can(), або is(). has() і can() мають однакову функціональність, але називаються по-різному, щоб зробити ваш код читабельнішим.

Основний приклад

Припустимо, що у вас є функція у вашому додатку, яка перевіряє, чи ввійшов користувач. Ви можете створити об'єкт дозволу таким чином:

// index.php
require 'vendor/autoload.php';

// деякий код 

// потім, напевно, у вас є щось, що говорить вам, яка поточна роль особи
// ймовірно, у вас є щось, де ви витягуєте поточну роль
// з змінної сесії, яка це визначає
// після того, як хтось увійде, інакше у них буде роль 'гостя' або 'публічна'.
$current_role = 'admin';

// налаштування дозволів
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
    return $current_role !== 'guest';
});

// Ви, напевно, захотите зберегти цей об'єкт у Flight десь 
Flight::set('permission', $permission);

Потім у контролері десь ви можете мати щось на зразок цього.

<?php

// якийсь контролер
class SomeController {
    public function someAction() {
        $permission = Flight::get('permission');
        if ($permission->has('loggedIn')) {
            // зробити щось
        } else {
            // зробити щось інше
        }
    }
}

Ви також можете використовувати це, щоб відстежувати, чи мають вони дозвіл робити щось у вашому додатку. Наприклад, якщо у вас є спосіб, яким користувачі можуть взаємодіяти з публікацією у вашому програмному забезпеченні, ви можете перевірити, чи мають вони дозвіл на виконання певних дій.

$current_role = 'admin';

// налаштування дозволів
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
    if($current_role === 'admin') {
        $permissions = ['create', 'read', 'update', 'delete'];
    } else if($current_role === 'editor') {
        $permissions = ['create', 'read', 'update'];
    } else if($current_role === 'author') {
        $permissions = ['create', 'read'];
    } else if($current_role === 'contributor') {
        $permissions = ['create'];
    } else {
        $permissions = [];
    }
    return $permissions;
});
Flight::set('permission', $permission);

Потім у контролері десь...

class PostController {
    public function create() {
        $permission = Flight::get('permission');
        if ($permission->can('post.create')) {
            // зробити щось
        } else {
            // зробити щось інше
        }
    }
}

Впровадження залежностей

Ви можете впроваджувати залежності у замикання, яке визначає дозволи. Це корисно, якщо у вас є якийсь перемикач, id, або будь-яка інша точка даних, з якою ви хочете перевірити. Те ж саме стосується викликів типу Клас->Метод, за винятком того, що ви визначаєте аргументи в методі.

Замикання

$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
    // ... код
});

// у вашому файлі контролера
public function createOrder() {
    $MyDependency = Flight::myDependency();
    $permission = Flight::get('permission');
    if ($permission->can('order.create', $MyDependency)) {
        // зробити щось
    } else {
        // зробити щось інше
    }
}

Класи

namespace MyApp;

class Permissions {

    public function order(string $current_role, MyDependency $MyDependency = null) {
        // ... код
    }
}

Швидкий спосіб встановлення дозволів за допомогою класів

Ви також можете використовувати класи для визначення ваших дозволів. Це корисно, якщо у вас багато дозволів, і ви хочете зберегти ваш код чистим. Ви можете зробити щось на зразок цього:

<?php

// код завантаження
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');

// myapp/Permissions.php
namespace MyApp;

class Permissions {

    public function order(string $current_role, int $user_id) {
        // Припускаємо, що ви налаштували це раніше
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $allowed_permissions = [ 'read' ]; // всі можуть переглядати замовлення
        if($current_role === 'manager') {
            $allowed_permissions[] = 'create'; // менеджери можуть створювати замовлення
        }
        $some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
        if($some_special_toggle_from_db) {
            $allowed_permissions[] = 'update'; // якщо у користувача є спеціальний перемикач, вони можуть оновлювати замовлення
        }
        if($current_role === 'admin') {
            $allowed_permissions[] = 'delete'; // адміністратори можуть видаляти замовлення
        }
        return $allowed_permissions;
    }
}

Завдяки цьому є також швидкий спосіб, який ви можете використовувати (який також може бути кешованим!!!), де ви просто говорите класу дозволів відобразити всі методи в класі в дозволи. Отже, якщо у вас є метод з назвою order() і метод з назвою company(), ці методи будуть автоматично відображені, так що ви зможете просто запустити $Permissions->has('order.read') або $Permissions->has('company.read'), і це спрацює. Визначити це дуже складно, тому залишайтеся зі мною тут. Вам просто потрібно зробити це:

Створіть клас дозволів, які ви хочете об'єднати разом.

class MyPermissions {
    public function order(string $current_role, int $order_id = 0): array {
        // код для визначення дозволів
        return $permissions_array;
    }

    public function company(string $current_role, int $company_id): array {
        // код для визначення дозволів
        return $permissions_array;
    }
}

Потім зробіть дозволи видимими за допомогою цієї бібліотеки.

$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);

Нарешті, викликайте дозвіл у вашій кодовій базі, щоб перевірити, чи дозволено користувачу виконати даний дозвіл.

class SomeController {
    public function createOrder() {
        if(Flight::get('permissions')->can('order.create') === false) {
            die('Ви не можете створити замовлення. Вибачте!');
        }
    }
}

Кешування

Щоб увімкнути кешування, ознайомтеся з простим wruczak/phpfilecache бібліотекою. Приклад увімкнення цього наведено нижче.


// цей $app може бути частиною вашого коду, або
// ви можете просто передати null, і це
// витягне з Flight::app() у конструкторі
$app = Flight::app();

// Поки що він приймає це як кеш файлів. Інші можна легко
// додати в майбутньому. 
$Cache = new Wruczek\PhpFileCache\PhpFileCache;

$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 - це скільки секунд кешувати це. Залиште це, щоб не використовувати кешування

І вперед!

Awesome-plugins/simple_job_queue

Проста черга завдань

Проста черга завдань - це бібліотека, яка може використовуватися для асинхронної обробки завдань. Її можна використовувати з beanstalkd, MySQL/MariaDB, SQLite і PostgreSQL.

Встановлення

composer require n0nag0n/simple-job-queue

Використання

Щоб це працювало, вам потрібен спосіб додати завдання до черги та спосіб обробляти завдання (робітник). Нижче наведені приклади того, як додати завдання до черги та як обробити завдання.

Додавання до Flight

Додавання цього до Flight є простим і здійснюється за допомогою методу register(). Нижче наведено приклад того, як додати це до Flight.

<?php
require 'vendor/autoload.php';

// Змініть ['mysql'] на ['beanstalkd'], якщо ви хочете використовувати beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
    // якщо у вас вже є з'єднання PDO в Flight::db();
    $Job_Queue->addQueueConnection(Flight::db());

    // або якщо ви використовуєте beanstalkd/Pheanstalk
    $pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
    $Job_Queue->addQueueConnection($pheanstalk);
});

Додавання нового завдання

Коли ви додаєте завдання, вам потрібно вказати конвеєр (чергу). Це можна порівняти з каналом у RabbitMQ або трубою в beanstalkd.

<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));

Запуск робітника

Ось приклад файлу про те, як запустити робітника.

<?php

require 'vendor/autoload.php';

$Job_Queue = new n0nag0n\Job_Queue('mysql');
// З'єднання PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);

// або якщо ви використовуєте beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);

$Job_Queue->watchPipeline('send_important_emails');
while(true) {
    $job = $Job_Queue->getNextJobAndReserve();

    // налаштуйте так, як вам краще спати вночі (тільки для черг бази даних, beanstalkd не потребує цієї умови)
    if(empty($job)) {
        usleep(500000);
        continue;
    }

    echo "Обробка {$job['id']}\n";
    $payload = json_decode($job['payload'], true);

    try {
        $result = doSomethingThatDoesSomething($payload);

        if($result === true) {
            $Job_Queue->deleteJob($job);
        } else {
            // це забирає його з готової черги та ставить його в іншу чергу, яку можна буде забрати та "вибити" пізніше.
            $Job_Queue->buryJob($job);
        }
    } catch(Exception $e) {
        $Job_Queue->buryJob($job);
    }
}

Обробка тривалих процесів із Supervisord

Supervisord - це система контролю процесів, яка забезпечує безперервну роботу ваших робочих процесів. Ось більш детальний посібник із налаштування його з вашим робітником Simple Job Queue:

Встановлення Supervisord

# На Ubuntu/Debian
sudo apt-get install supervisor

# На CentOS/RHEL
sudo yum install supervisor

# На macOS з Homebrew
brew install supervisor

Створення скрипта робітника

Спочатку збережіть код вашого робітника в окремому файлі PHP:

<?php

require 'vendor/autoload.php';

$Job_Queue = new n0nag0n\Job_Queue('mysql');
// З'єднання PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);

// Встановіть конвеєр для моніторингу
$Job_Queue->watchPipeline('send_important_emails');

// Лог початку робітника
echo date('Y-m-d H:i:s') . " - Робітник запущений\n";

while(true) {
    $job = $Job_Queue->getNextJobAndReserve();

    if(empty($job)) {
        usleep(500000); // Спати 0,5 секунди
        continue;
    }

    echo date('Y-m-d H:i:s') . " - Обробка завдання {$job['id']}\n";
    $payload = json_decode($job['payload'], true);

    try {
        $result = doSomethingThatDoesSomething($payload);

        if($result === true) {
            $Job_Queue->deleteJob($job);
            echo date('Y-m-d H:i:s') . " - Завдання {$job['id']} успішно завершено\n";
        } else {
            $Job_Queue->buryJob($job);
            echo date('Y-m-d H:i:s') . " - Завдання {$job['id']} не вдалося, закопано\n";
        }
    } catch(Exception $e) {
        $Job_Queue->buryJob($job);
        echo date('Y-m-d H:i:s') . " - Виключення при обробці завдання {$job['id']}: {$e->getMessage()}\n";
    }
}

Налаштування Supervisord

Створіть файл конфігурації для вашого робітника:

[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d

Основні параметри конфігурації:

Управління робітниками за допомогою Supervisorctl

Після створення або модифікації конфігурації:

# Перезавантаження конфігурації супервайзера
sudo supervisorctl reread
sudo supervisorctl update

# Управління конкретними процесами робітників
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*

Запуск кількох конвеєрів

Для кількох конвеєрів створіть окремі файли робітників і конфігурації:

[program:email_worker]
command=php /path/to/email_worker.php
# ... інші конфігурації ...

[program:notification_worker]
command=php /path/to/notification_worker.php
# ... інші конфігурації ...

Моніторинг та журнали

Перевірте журнали, щоб контролювати активність робітника:

# Перегляд журналів
sudo tail -f /var/log/simple_job_queue.log

# Перевірка статусу
sudo supervisorctl status

Ця настройка забезпечує неперервну роботу ваших робочих процесів, навіть після аварій, перезавантажень сервера або інших проблем, що робить вашу систему черги надійною для виробничих середовищ.

Awesome-plugins/ghost_session

Ghostff/Session

Менеджер сесій PHP (без блокування, флеш, сегмент, шифрування сесій). Використовує PHP open_ssl для необов'язкового шифрування/дешифрування даних сесії. Підтримує File, MySQL, Redis та Memcached.

Натисніть тут, щоб переглянути код.

Встановлення

Встановіть за допомогою composer.

composer require ghostff/session

Базова конфігурація

Вам не потрібно передавати нічого, щоб використовувати налаштування за замовчуванням у вашій сесії. Ви можете прочитати про більше налаштувань у Github Readme.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

// єдине, що потрібно пам'ятати, це те, що ви повинні зафіксувати свою сесію при кожному завантаженні сторінки
// або вам потрібно буде запустити auto_commit у вашій конфігурації. 

Простой приклад

Ось простий приклад того, як ви можете це використовувати.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // виконайте свою логіку входу тут
    // перевірте пароль тощо.

    // якщо вхід успішний
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // щоразу, коли ви записуєте в сесію, ви повинні зафіксувати це навмисно.
    $session->commit();
});

// Ця перевірка може бути в обмеженій логіці сторінки або обгорнута в посередника.
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // виконайте свою логіку обмеженої сторінки тут
});

// версія посередника
Flight::route('/some-restricted-page', function() {
    // звичайна логіка сторінки
})->addMiddleware(function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }
});

Більш складний приклад

Ось більш складний приклад того, як ви можете це використовувати.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// вкажіть користувацький шлях до вашого файлу конфігурації сесії та надайте випадковий рядок для ідентифікатора сесії
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        // або ви можете вручну переоприділити параметри конфігурації
        $session->updateConfiguration([
            // якщо ви хочете зберігати свої дані сесії в базі даних (добре, якщо ви хочете таку функцію, як "вийти з усіх пристроїв")
            Session::CONFIG_DRIVER        => Ghostff\Session\Drivers\MySql::class,
            Session::CONFIG_ENCRYPT_DATA  => true,
            Session::CONFIG_SALT_KEY      => hash('sha256', 'my-super-S3CR3T-salt'), // будь ласка, змініть це на щось інше
            Session::CONFIG_AUTO_COMMIT   => true, // робіть це тільки якщо це потрібно і/або важко зафіксувати() вашу сесію.
                                                   // крім того, ви могли б зробити Flight::after('start', function() { Flight::session()->commit(); });
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # Драйвер бази даних для PDO dns eg(mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # Хост бази даних
                'db_name'   => 'my_app_database',   # Назва бази даних
                'db_table'  => 'sessions',          # Таблиця бази даних
                'db_user'   => 'root',              # Ім'я користувача бази даних
                'db_pass'   => '',                  # Пароль бази даних
                'persistent_conn'=> false,          # Уникнення витрат на встановлення нового з'єднання щоразу, коли скрипту потрібно взаємодіяти з базою даних, що призводить до швидшого веб-додатку. ЗНАЙДІТЬ ЗВОРОТНЮ СТОРОНУ САМОСТІЙНО
            ]
        ]);
    }
);

Допомога! Мої дані сесії не зберігаються!

Ви налаштовуєте свої дані сесії, і вони не зберігаються між запитами? Можливо, ви забули зафіксувати свої дані сесії. Ви можете зробити це, викликавши $session->commit(), після того як ви налаштували свої дані сесії.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // виконайте свою логіку входу тут
    // перевірте пароль тощо.

    // якщо вхід успішний
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // щоразу, коли ви записуєте в сесію, ви повинні зафіксувати це навмисно.
    $session->commit();
});

Інший спосіб обійти це, коли ви налаштовуєте свою службу сесії, вам потрібно встановити auto_commit на true у вашій конфігурації. Це автоматично зафіксує ваші дані сесії після кожного запиту.


$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        $session->updateConfiguration([
            Session::CONFIG_AUTO_COMMIT   => true,
        ]);
    }
);

Крім того, ви можете зробити Flight::after('start', function() { Flight::session()->commit(); });, щоб зафіксувати ваші дані сесії після кожного запиту.

Документація

Відвідайте Github Readme для повної документації. Параметри конфігурації добре задокументовані у файлі default_config.php. Код легко зрозуміти, якщо ви хочете ознайомитися з цим пакетом самостійно.

Awesome-plugins/pdo_wrapper

PdoWrapper PDO Helper Class

Flight постачається з допоміжним класом для PDO. Це дозволяє вам легко запитувати вашу базу даних з усією підготовкою/виконанням/fetchAll() заморочкою. Це значно спрощує, як ви можете запитувати вашу базу даних. Кожен рядок результату повертається як клас Colletion Flight, який дозволяє вам отримувати доступ до ваших даних через синтаксис масиву або синтаксис об'єкта.

Реєстрація класу допомоги PDO

// Зареєструйте клас допомоги PDO
Flight::register('db', \flight\database\PdoWrapper::class, ['mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
]);

Використання

Цей об'єкт розширює PDO, тому всі звичайні методи PDO доступні. Наступні методи додані, щоб спростити запити до бази даних:

runQuery(string $sql, array $params = []): PDOStatement

Використовуйте це для ВСТАВКИ, ОНОВЛЕННЯ або якщо ви плануєте використовувати SELECT в циклі while

$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]);
while($row = $statement->fetch()) {
    // ...
}

// Або запис до бази даних
$db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
$db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]);

fetchField(string $sql, array $params = []): mixed

Отримує перше поле з запиту

$db = Flight::db();
$count = $db->fetchField("SELECT COUNT(*) FROM table WHERE something = ?", [ $something ]);

fetchRow(string $sql, array $params = []): array

Отримує один рядок з запиту

$db = Flight::db();
$row = $db->fetchRow("SELECT id, name FROM table WHERE id = ?", [ $id ]);
echo $row['name'];
// або
echo $row->name;

fetchAll(string $sql, array $params = []): array

Отримує всі рядки з запиту

$db = Flight::db();
$rows = $db->fetchAll("SELECT id, name FROM table WHERE something = ?", [ $something ]);
foreach($rows as $row) {
    echo $row['name'];
    // або
    echo $row->name;
}

Примітка зі синтаксисом IN()

Це також має корисну обгортку для операторів IN(). Ви можете просто передати один знак питання як заповнювач для IN() а потім масив значень. Ось приклад того, як це може виглядати:

$db = Flight::db();
$name = 'Bob';
$company_ids = [1,2,3,4,5];
$rows = $db->fetchAll("SELECT id, name FROM table WHERE name = ? AND company_id IN (?)", [ $name, $company_ids ]);

Повний приклад

// Приклад маршруту і як ви б використовували цю обгортку
Flight::route('/users', function () {
    // Отримати всіх користувачів
    $users = Flight::db()->fetchAll('SELECT * FROM users');

    // Потік всіх користувачів
    $statement = Flight::db()->runQuery('SELECT * FROM users');
    while ($user = $statement->fetch()) {
        echo $user['name'];
        // або echo $user->name;
    }

    // Отримати одного користувача
    $user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);

    // Отримати одне значення
    $count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');

    // Спеціальний синтаксис IN() для допомоги (обов'язково, щоб IN було великими літерами)
    $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
    // ви також можете зробити так
    $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [ '1,2,3,4,5']);

    // Вставити нового користувача
    Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
    $insert_id = Flight::db()->lastInsertId();

    // Оновити користувача
    Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);

    // Видалити користувача
    Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);

    // Отримати кількість змінених рядків
    $statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
    $affected_rows = $statement->rowCount();

});

Awesome-plugins/migrations

Міграції

Міграція для вашого проєкту – це відстеження всіх змін бази даних, пов’язаних з вашим проєктом. byjg/php-migration – це справді корисна основна бібліотека, яка допоможе вам розпочати.

Встановлення

PHP Бібліотека

Якщо ви хочете використовувати тільки PHP бібліотеку у вашому проєкті:

composer require "byjg/migration"

Інтерфейс командного рядка

Інтерфейс командного рядка є самостійним і не вимагає, щоб ви встановлювали його разом із вашим проєктом.

Ви можете встановити його глобально і створити символічне посилання

composer require "byjg/migration-cli"

Будь ласка, відвідайте byjg/migration-cli, щоб отримати більше інформації про Migration CLI.

Підтримувані бази даних

База даних Драйвер Строка з’єднання
Sqlite pdo_sqlite sqlite:///path/to/file
MySql/MariaDb pdo_mysql mysql://username:password@hostname:port/database
Postgres pdo_pgsql pgsql://username:password@hostname:port/database
Sql Server pdo_dblib, pdo_sysbase Linux dblib://username:password@hostname:port/database
Sql Server pdo_sqlsrv Windows sqlsrv://username:password@hostname:port/database

Як це працює?

Міграція бази даних використовує ЧИСТИЙ SQL для управління версіонуванням бази даних. Щоб це працювало, вам потрібно:

SQL Скрипти

Скрипти поділені на три набори скриптів:

Директорія зі скриптами виглядає так:

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

Багаторазове середовище розробки

Якщо ви працюєте з кількома розробниками та кількома гілками, важко визначити, яке наступне число.

У цьому випадку ви можете додати суфікс "-dev" після номера версії.

Погляньте на сценарій:

У обох випадках розробники створять файл під назвою 43-dev.sql. Обидва розробники зможуть мігрувати UP і DOWN без проблем, а ваша локальна версія буде 43.

Але розробник 1 об'єднав свої зміни і створив фінальну версію 43.sql (git mv 43-dev.sql 43.sql). Якщо розробник 2 оновить вашу локальну гілку, він отримає файл 43.sql (від розробника 1) і ваш файл 43-dev.sql. Якщо він спробує мігрувати UP або DOWN, скрипт міграції повідомить про помилку і сповістить його про те, що існує ДВІ версії 43. У такому випадку розробник 2 повинен оновити свій файл на 44-dev.sql і продовжити працювати, поки не об’єднає свої зміни і не створить фінальну версію.

Використання PHP API та інтеграція його у ваші проєкти

Основне використання:

Дивіться приклад:

<?php
// Створіть URI з'єднання
// Дивіться більше: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');

// Зареєструйте базу даних або бази даних, які можуть обробляти цей URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);

// Створіть екземпляр Migration
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');

// Додайте функцію зворотного виклику для отримання інформації про виконання
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
    echo "$action, $currentVersion, ${fileInfo['description']}\n";
});

// Відновіть базу даних за допомогою скрипта "base.sql"
// і виконайте УСІ існуючі скрипти, щоб підняти версію бази даних до останньої
$migration->reset();

// Виконайте УСІ існуючі скрипти для підняття чи зниження версії бази даних
// з поточної версії до номера $version;
// Якщо номер версії не вказано, мігруйте до останньої версії бази даних
$migration->update($version = null);

Об'єкт Migration контролює версію бази даних.

Створення контролю версій у вашому проєкті

<?php
// Зареєструйте базу даних або бази даних, які можуть обробляти цей URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);

// Створіть екземпляр Migration
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');

// Ця команда створить таблицю версій у вашій базі даних
$migration->createVersion();

Отримання поточної версії

<?php
$migration->getCurrentVersion();

Додати зворотний виклик для контролю прогресу

<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
    echo "Виконання команди: $command на версії $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});

Отримання екземпляра драйвера бази даних

<?php
$migration->getDbDriver();

Щоб використовувати це, будь ласка, відвідайте: https://github.com/byjg/anydataset-db

Уникнення часткової міграції (не доступно для MySQL)

Часткова міграція – це коли скрипт міграції переривається посеред процесу через помилку або ручну переривання.

Таблиця міграції буде мати статус partial up або partial down, і її потрібно буде виправити вручну, перш ніж можна буде мігрувати знову.

Щоб уникнути цієї ситуації, ви можете вказати, що міграція буде виконуватись у транзакційному контексті. Якщо скрипт міграції не вдасться, транзакція буде скасована, а таблиця міграції буде позначена як complete, і версія буде одразу попередньою версією до скрипта, який спричинив помилку.

Щоб увімкнути цю функцію, вам потрібно викликати метод withTransactionEnabled, передавши true як параметр:

<?php
$migration->withTransactionEnabled(true);

ПРИМІТКА: Ця функція недоступна для MySQL, оскільки він не підтримує DDL команди всередині транзакції. Якщо ви використовуєте цей метод з MySQL, Migration проігнорує його тихо. Більше інформації: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html

Поради щодо написання SQL міграцій для Postgres

При створенні тригерів і SQL-функцій

-- DO
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Перевірте, що ім'я працівника та зарплата вказані
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname не може бути null'; -- не має значення, якщо ці коментарі пусті
        END IF; --
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% не може мати null зарплату', NEW.empname; --
        END IF; --

        -- Хто працює на нас, коли вони повинні це оплачувати?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% не може мати негативну зарплату', NEW.empname; --
        END IF; --

        -- Запам'ятайте, хто змінив платіж від коли
        NEW.last_date := current_timestamp; --
        NEW.last_user := current_user; --
        RETURN NEW; --
    END; --
$emp_stamp$ LANGUAGE plpgsql;

-- DON'T
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Перевірте, що ім'я працівника та зарплата вказані
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname не може бути null';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% не може мати null зарплату', NEW.empname;
        END IF;

        -- Хто працює на нас, коли вони повинні це оплачувати?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% не може мати негативну зарплату', NEW.empname;
        END IF;

        -- Запам'ятайте, хто змінив платіж від коли
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

Оскільки абстрактний рівень бази даних PDO не може виконувати партії SQL заявок, коли byjg/migration читає файл міграції, він повинен розділити весь вміст SQL файлу на частини за крапками з коми та виконати заяви одну за одною. Однак є один вид заяви, який може містити кілька крапок з коми між його тілом: функції.

Щоб мати можливість правильно парсити функції, byjg/migration 2.1.0 почав розділяти файли міграцій за послідовністю semicolon + EOL замість лише за крапкою з комою. Таким чином, якщо ви додасте пустий коментар після кожної внутрішньої крапки з комою у визначенні функції, byjg/migration зможе їх правильно розпізнати.

На жаль, якщо ви забудете додати будь-який з цих коментарів, бібліотека розділить заяву CREATE FUNCTION на кілька частин, і міграція завершиться невдачею.

Уникнення символа двокрапки (:)

-- DO
CREATE TABLE bookings (
  booking_id UUID PRIMARY KEY,
  booked_at  TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
  check_in   DATE NOT NULL
);

-- DON'T
CREATE TABLE bookings (
  booking_id UUID PRIMARY KEY,
  booked_at  TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
  check_in   DATE NOT NULL
);

Оскільки PDO використовує символ двокрапки для префікса названих параметрів у підготовлених запитах, його використання призведе до помилки в інших контекстах.

Наприклад, заяви PostgreSQL можуть використовувати :: для приведення значень між типами. З іншого боку, PDO прочитає це як недійсний названий параметр в недійсному контексті і завершить спробу його виконання з помилкою.

Єдиний спосіб виправити цю невідповідність – це уникати двокрапок взагалі (у цьому випадку PostgreSQL також має альтернативний синтаксис: CAST(value AS type)).

Використовуйте SQL редактор

Нарешті, написання ручних SQL міграцій може бути виснажливим, але це набагато простіше, якщо ви використовуєте редактор, здатний розуміти синтаксис SQL, надавати автозаповнення, досліджувати вашу поточну схему бази даних та/або автоматично форматувати ваш код.

Обробка різних міграцій всередині однієї схеми

Якщо вам потрібно створити різні скрипти міграцій та версії в одній схемі, це можливо, але надто ризиковано, і я не рекомендую цього зовсім.

Для цього вам потрібно створити різні "таблиці міграцій", передаючи параметр конструктору.

<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");

З міркувань безпеки ця функція недоступна з командного рядка, але ви можете використовувати змінну середовища MIGRATION_VERSION, щоб зберегти ім'я.

Ми справді рекомендуємо не використовувати цю функцію. Рекомендація – одна міграція для однієї схеми.

Запуск юніт-тестів

Основні юніт-тести можна запускати за допомогою:

vendor/bin/phpunit

Запуск тестів бази даних

Запуск інтеграційних тестів вимагає, щоб бази даних були запущені. Ми надали базовий docker-compose.yml, який ви можете використовувати для запуску баз даних для тестування.

Запуск баз даних

docker-compose up -d postgres mysql mssql

Запуск тестів

vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*

Опціонально, ви можете встановити хост і пароль, які використовуються юніт-тестами

export MYSQL_TEST_HOST=localhost     # за замовчуванням localhost
export MYSQL_PASSWORD=newpassword    # використовуйте '.' якщо хочете мати порожній пароль
export PSQL_TEST_HOST=localhost      # за замовчуванням localhost
export PSQL_PASSWORD=newpassword     # використовуйте '.' якщо хочете мати порожній пароль
export MSSQL_TEST_HOST=localhost     # за замовчуванням localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db      # за замовчуванням /tmp/test.db

Awesome-plugins/session

FlightPHP Сесія - Легкий обробник сесій на основі файлів

Це легкий плагін для обробки сесій на основі файлів для Flight PHP Framework. Він надає просте, але потужне рішення для управління сесіями, з такими функціями, як неблокуючі читання сесій, необов'язкова шифрування, функціональність автозбереження та режим тестування для розробки. Дані сесії зберігаються у файлах, що робить його ідеальним для додатків, які не потребують бази даних.

Якщо ви хочете використовувати базу даних, ознайомтеся з плагіном ghostff/session, який має багато з цих самих функцій, але з бекендом бази даних.

Відвідайте репозиторій Github для повного вихідного коду та деталей.

Встановлення

Встановіть плагін через Composer:

composer require flightphp/session

Основне використання

Ось простий приклад того, як використовувати плагін flightphp/session у вашому додатку Flight:

require 'vendor/autoload.php';

use flight\Session;

$app = Flight::app();

// Зареєструйте сервіс сесій
$app->register('session', Session::class);

// Приклад маршруту з використанням сесії
Flight::route('/login', function() {
    $session = Flight::session();
    $session->set('user_id', 123);
    $session->set('username', 'johndoe');
    $session->set('is_admin', false);

    echo $session->get('username'); // Виводить: johndoe
    echo $session->get('preferences', 'default_theme'); // Виводить: default_theme

    if ($session->get('user_id')) {
        Flight::json(['message' => 'Користувач увійшов в систему!', 'user_id' => $session->get('user_id')]);
    }
});

Flight::route('/logout', function() {
    $session = Flight::session();
    $session->clear(); // Очистити всі дані сесії
    Flight::json(['message' => 'Вихід виконано успішно']);
});

Flight::start();

Ключові моменти

Налаштування

Ви можете налаштувати обробник сесій, передаючи масив параметрів при реєстрації:

$app->register('session', Session::class, [
    'save_path' => '/custom/path/to/sessions',         // Директорія для файлів сесій
    'encryption_key' => 'a-secure-32-byte-key-here',   // Увімкнути шифрування (рекомендовано 32 байти для AES-256-CBC)
    'auto_commit' => false,                            // Вимкнути автозбереження для ручного контролю
    'start_session' => true,                           // Автоматично розпочати сесію (за замовчуванням: так)
    'test_mode' => false                               // Увімкнути тестовий режим для розробки
]);

Параметри конфігурації

Параметр Опис Значення за замовчуванням
save_path Директорія, де зберігаються файли сесій sys_get_temp_dir() . '/flight_sessions'
encryption_key Ключ для шифрування AES-256-CBC (необов'язковий) null (без шифрування)
auto_commit Авто-збереження даних сесії при завершенні роботи true
start_session Автоматично розпочати сесію true
test_mode Запустити в тестовому режимі, не впливаючи на PHP сесії false
test_session_id Користувацький ID сесії для тестового режиму (необов'язковий) Генерується випадково, якщо не задано

Розширене використання

Ручне збереження

Якщо ви вимкнули автозбереження, вам потрібно вручну зафіксувати зміни:

$app->register('session', Session::class, ['auto_commit' => false]);

Flight::route('/update', function() {
    $session = Flight::session();
    $session->set('key', 'value');
    $session->commit(); // Явно зберегти зміни
});

Безпека сесії з шифруванням

Увімкніть шифрування для чутливих даних:

$app->register('session', Session::class, [
    'encryption_key' => 'your-32-byte-secret-key-here'
]);

Flight::route('/secure', function() {
    $session = Flight::session();
    $session->set('credit_card', '4111-1111-1111-1111'); // Автоматично зашифровано
    echo $session->get('credit_card'); // Дешифровано під час отримання
});

Регенація сесії

Регеніруйте ID сесії для безпеки (наприклад, після входу в систему):

Flight::route('/post-login', function() {
    $session = Flight::session();
    $session->regenerate(); // Новий ID, зберегти дані
    // АБО
    $session->regenerate(true); // Новий ID, вилучити старі дані
});

Приклад Middleware

Захистіть маршрути за допомогою аутентифікації на основі сесій:

Flight::route('/admin', function() {
    Flight::json(['message' => 'Ласкаво просимо до панелі адміністрування']);
})->addMiddleware(function() {
    $session = Flight::session();
    if (!$session->get('is_admin')) {
        Flight::halt(403, 'Доступ заборонено');
    }
});

Це лише простий приклад того, як використовувати це в middleware. Для більш детального прикладу див. документацію middleware.

Методи

Клас Session надає ці методи:

Усі методи, крім get() і id(), повертають екземпляр Session для ланцюгового виклику.

Чому варто використовувати цей плагін?

Технічні деталі

Консультування

Внесення змін віталося! Форкніть репозиторій, внесіть свої зміни та надішліть запит на злиття. Повідомляйте про помилки або пропонуйте функції через трекер проблем Github.

Ліцензія

Цей плагін ліцензований під ліцензією MIT. Дивіться репозиторій Github для деталей.

Awesome-plugins/runway

Розгін

Розгін - це CLI-додаток, який допомагає вам керувати вашими застосунками Flight. Він може генерувати контролери, відображати всі маршрути та багато іншого. Він базується на відмінній бібліотеці adhocore/php-cli.

Натисніть тут, щоб переглянути код.

Встановлення

Встановіть за допомогою composer.

composer require flightphp/runway

Основна конфігурація

Перший раз, коли ви запустите Розгін, він проведе вас через процес налаштування і створить файл конфігурації .runway.json у корені вашого проєкту. Цей файл міститиме необхідні конфігурації для коректної роботи Розгону.

Використання

Розгін має кілька команд, які ви можете використовувати для керування вашим застосунком Flight. Є два простих способи використовувати Розгін.

  1. Якщо ви використовуєте скелетний проєкт, ви можете запустити php runway [command] з кореня вашого проєкту.
  2. Якщо ви використовуєте Розгін як пакет, встановлений через composer, ви можете запустити vendor/bin/runway [command] з кореня вашого проєкту.

Для будь-якої команди ви можете передати прапор --help, щоб отримати більше інформації про те, як використовувати команду.

php runway routes --help

Ось кілька прикладів:

Генерація контролера

На основі конфігурації у вашому файлі .runway.json, за замовчуванням буде згенеровано контролер у каталозі app/controllers/.

php runway make:controller MyController

Генерація моделі Active Record

На основі конфігурації у вашому файлі .runway.json, за замовчуванням буде згенеровано контролер у каталозі app/records/.

php runway make:record users

Якщо, наприклад, у вас є таблиця users з наступною схемою: id, name, email, created_at, updated_at, буде створено файл подібний до наступного у файлі app/records/UserRecord.php:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Клас ActiveRecord для таблиці користувачів.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 * 
 * @property int $id
 * @property string $name
 * @property string $email
 * @property string $created_at
 * @property string $updated_at
 * // ви також можете додати зв'язки тут, як тільки визначите їх у масиві $relations
 * @property CompanyRecord $company Приклад зв'язку
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Встановити зв'язки для моделі
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [];

    /**
     * Конструктор
     * @param mixed $databaseConnection З'єднання з базою даних
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

Відображення всіх маршрутів

Це відобразить всі маршрути, які наразі зареєстровані в Flight.

php runway routes

Якщо ви хочете переглянути лише конкретні маршрути, ви можете передати прапор для фільтрації маршрутів.

# Відображати лише маршрути GET
php runway routes --get

# Відображати лише маршрути POST
php runway routes --post

# тощо.

Налаштування Розгону

Якщо ви або створюєте пакет для Flight, або хочете додати свої власні команди у свій проєкт, ви можете це зробити, створивши каталог src/commands/, flight/commands/, app/commands/, або commands/ для вашого проєкту/пакету.

Щоб створити команду, вам просто потрібно розширити клас AbstractBaseCommand і реалізувати, принаймні, методи __construct та execute.

<?php

declare(strict_types=1);

namespace flight\commands;

class ExampleCommand extends AbstractBaseCommand
{
    /**
     * Конструктор
     *
     * @param array<string,mixed> $config JSON конфігурація з .runway-config.json
     */
    public function __construct(array $config)
    {
        parent::__construct('make:example', 'Створити приклад для документації', $config);
        $this->argument('<funny-gif>', 'Назва смішного гіфу');
    }

    /**
     * Виконує функцію
     *
     * @return void
     */
    public function execute(string $controller)
    {
        $io = $this->app()->io();

        $io->info('Створення прикладу...');

        // Зробіть щось тут

        $io->ok('Приклад створено!');
    }
}

Дивіться adhocore/php-cli Документацію для отримання додаткової інформації про те, як створити свої власні команди у вашому застосунку Flight!

Awesome-plugins/tracy_extensions

Tracy Flight Panel Extensions

Це набір розширень, щоб зробити роботу з Flight трохи більш насиченою.

Це панель

Flight Bar

І кожна панель показує дуже корисну інформацію про вашу програму!

Flight Data Flight Database Flight Request

Натисніть тут, щоб переглянути код.

Installation

Запустіть composer require flightphp/tracy-extensions --dev і ви на правильному шляху!

Configuration

Є дуже небагато конфігурацій, які вам потрібно зробити, щоб це запрацювало. Вам потрібно ініціювати відладчик Tracy перед використанням цього https://tracy.nette.org/en/guide:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// код завантаження
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// Можливо, вам потрібно вказати ваше середовище з Debugger::enable(Debugger::DEVELOPMENT)

// якщо ви використовуєте з'єднання з базою даних у вашій програмі, 
// необхідний обгортка PDO, щоб використовувати ТІЛЬКИ В РОЗРОБЦІ (не в продукції, будь ласка!)
// Він має ті ж параметри, що й звичайне з'єднання PDO
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// або, якщо ви прикріпите це до фреймворку Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// тепер щоразу, коли ви виконуєте запит, він буде реєструвати час, запит та параметри

// Це з'єднує всі точки
if(Debugger::$showBar === true) {
    // Це повинно бути false або Tracy не зможе відобразити :( 
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// більше коду

Flight::start();

Додаткове налаштування

Дані сесії

Якщо у вас є власний обробник сесій (такий як ghostff/session), ви можете передати будь-який масив даних сесії до Tracy, і вона автоматично виведе його для вас. Ви передаєте його з ключем session_data у другому параметрі конструктора TracyExtensionLoader.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

if(Debugger::$showBar === true) {
    // Це повинно бути false або Tracy не зможе відобразити :( 
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// маршрути та інші речі...

Flight::start();

Latte

Якщо у вас є Latte, встановлений у вашому проекті, ви можете використовувати панель Latte для аналізу ваших шаблонів. Ви можете передати екземпляр Latte до конструктора TracyExtensionLoader з ключем latte у другому параметрі.


use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    // це те місце, де ви додаєте панель Latte до Tracy
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // Це повинно бути false або Tracy не зможе відобразити :( 
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/tracy

Tracy

Tracy — це чудовий обробник помилок, який можна використовувати з Flight. Він має кілька панелей, які можуть допомогти вам налагодити вашу програму. Також його дуже легко розширити та додати свої панелі. Команда Flight створила кілька панелей спеціально для проектів Flight за допомогою плагіна flightphp/tracy-extensions.

Встановлення

Встановіть з Composer. І вам насправді захочеться встановити це без версії для розробників, оскільки Tracy постачається з компонентом обробки помилок для виробництва.

composer require tracy/tracy

Основна конфігурація

Є кілька основних параметрів конфігурації, щоб почати. Ви можете прочитати більше про них у Документації Tracy.


require 'vendor/autoload.php';

use Tracy\Debugger;

// Увімкніть Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // іноді вам потрібно бути явним (також Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // ви також можете надати масив IP-адрес

// Тут будуть записані помилки та виключення. Переконайтеся, що цей каталог існує і має право на запис.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // показувати всі помилки
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // всі помилки, крім застарілих повідомлень
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // якщо панель Debugger видима, то довжину вмісту не можна встановити Flight

    // Це специфічно для розширення Tracy для Flight, якщо ви це включили
    // інакше прокоментуйте це.
    new TracyExtensionLoader($app);
}

Корисні поради

Коли ви налагоджуєте свій код, є кілька дуже корисних функцій для виводу даних для вас.

Awesome-plugins/active_record

Flight Active Record

Активний запис - це відображення сутності бази даних на об'єкт PHP. Простими словами, якщо у вас є таблиця користувачів у вашій базі даних, ви можете "перекласти" рядок у цій таблиці в клас User і об'єкт $user у вашому коді. Дивіться основний приклад.

Натисніть тут для доступу до репозиторію на GitHub.

Основний приклад

Припустимо, у вас є наступна таблиця:

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

Тепер ви можете налаштувати новий клас для представлення цієї таблиці:

/**
 * Клас ActiveRecord зазвичай є одниною
 * 
 * Рекомендується додати властивості таблиці як коментарі тут
 * 
 * @property int    $id
 * @property string $name
 * @property string $password
 */ 
class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        // ви можете встановити таким чином
        parent::__construct($database_connection, 'users');
        // або таким чином
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Тепер спостерігайте, як відбувається магія!

// для sqlite
$database_connection = new PDO('sqlite:test.db'); // це лише для прикладу, ви, ймовірно, будете використовувати реальне з'єднання з базою даних

// для mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');

// або mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// або mysqli з не об'єктним створенням
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');

$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// або $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// не можна використовувати $user->save() тут, інакше воно вважатиме, що це оновлення!

echo $user->id; // 2

І це було так легко додати нового користувача! Тепер, коли в базі даних є рядок користувача, як витягти його?

$user->find(1); // знайти id = 1 у базі даних і повернути його.
echo $user->name; // 'Bobby Tables'

А що якщо ви хочете знайти всіх користувачів?

$users = $user->findAll();

Що щодо певної умови?

$users = $user->like('name', '%mamma%')->findAll();

Бачите, як це весело? Давайте встановимо це і почнемо!

Встановлення

Просто встановіть за допомогою Composer

composer require flightphp/active-record 

Використання

Це можна використовувати як окрему бібліотеку або з PHP Framework Flight. Цілком на ваш розсуд.

Окремо

Просто переконайтеся, що передали з'єднання PDO в конструктор.

$pdo_connection = new PDO('sqlite:test.db'); // це лише для прикладу, ви, ймовірно, будете використовувати реальне з'єднання з базою даних

$User = new User($pdo_connection);

Не хочете завжди встановлювати з'єднання з базою даних у конструкторі? Дивіться Управління з'єднаннями з базою даних для інших ідей!

Зареєструвати як метод у Flight

Якщо ви використовуєте PHP Framework Flight, ви можете зареєструвати клас ActiveRecord як сервіс, але насправді вам не потрібно.

Flight::register('user', 'User', [ $pdo_connection ]);

// тоді ви можете використовувати це так у контролері, функції тощо.

Flight::user()->find(1);

Методи runway

runway - це інструмент CLI для Flight, який має користувацьку команду для цієї бібліотеки.

# Використання
php runway make:record database_table_name [class_name]

# Приклад
php runway make:record users

Це створить новий клас у каталозі app/records/ під назвою UserRecord.php з наступним вмістом:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Клас ActiveRecord для таблиці користувачів.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 *
 * @property int $id
 * @property string $username
 * @property string $email
 * @property string $password_hash
 * @property string $created_dt
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Встановіть зв'язки для моделі
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [
        // 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
    ];

    /**
     * Конструктор
     * @param mixed $databaseConnection З'єднання з базою даних
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

CRUD функції

find($id = null) : boolean|ActiveRecord

Знайдіть один запис і присвойте його поточному об'єкту. Якщо ви передасте $id будь-якого типу, він виконає пошук за первинним ключем з цим значенням. Якщо нічого не передано, він просто знайде перший запис у таблиці.

Додатково ви можете передати йому інші допоміжні методи для запиту вашої таблиці.

// знайти запис з якимись умовами заздалегідь
$user->notNull('password')->orderBy('id DESC')->find();

// знайти запис за конкретним id
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

Знайдіть усі записи в зазначеній вами таблиці.

$user->findAll();

isHydrated(): boolean (v0.4.0)

Повертає true, якщо поточний запис був гідратований (отриманий з бази даних).

$user->find(1);
// якщо запис знайдено з даними...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

Вставляє поточний запис у базу даних.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Текстові первинні ключі

Якщо у вас є текстовий первинний ключ (такий як UUID), ви можете встановити значення первинного ключа перед вставкою одним з двох способів.

$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // або $user->save();

або ви можете мати первинний ключ, автоматично згенерований для вас через події.

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // ви можете також встановити primaryKey таким чином замість масиву вище.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // або як вам потрібно генерувати ваші унікальні id
    }
}

Якщо ви не встановите первинний ключ перед вставкою, він буде встановлений на rowid, і база даних згенерує його для вас, але не збережеться, оскільки цього поля може не бути в вашій таблиці. Це чому рекомендується використовувати подію, щоб автоматично обробити це для вас.

update(): boolean|ActiveRecord

Оновлює поточний запис у базі даних.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

Вставляє або оновлює поточний запис у базі даних. Якщо запис має id, він оновить, в іншому випадку вставить.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

Примітка: Якщо у вас є визначені в класі зв'язки, він рекурсивно збереже ці зв'язки також, якщо вони були визначені, ініційовані та мають "брудні" дані для оновлення. (v0.4.0 і вище)

delete(): boolean

Видаляє поточний запис з бази даних.

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

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

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

"Брудні" дані стосуються даних, які були змінені в запису.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// на даний момент нічого не є "брудним".

$user->email = 'test@example.com'; // тепер email вважається "брудним", оскільки він змінився.
$user->update();
// тепер немає даних, які є брудними, оскільки вони були оновлені та збережені в базі даних

$user->password = password_hash('newpassword'); // тепер це брудно
$user->dirty(); // передання нічого очистить усі брудні записи.
$user->update(); // нічого не буде оновлено, оскільки нічого не було захоплено як брудне.

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // і ім'я, і пароль оновлюються.

copyFrom(array $data): ActiveRecord (v0.4.0)

Це псевдонім для методу dirty(). Це трохи ясніше, що ви робите.

$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // і ім'я, і пароль оновлюються.

isDirty(): boolean (v0.4.0)

Повертає true, якщо поточний запис був змінений.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

Скидає поточний запис до його початкового стану. Це дійсно добре використовувати в циклі. Якщо ви передасте true, він також скине дані запиту, які були використані для знаходження поточного об'єкта (за замовчуванням).

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // почати з чистого аркуша
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

Після того, як ви запустите find(), findAll(), insert(), update(), або save() метод, ви можете отримати SQL, який був побудований, і використовувати його для налагодження.

Методи SQL Запитів

select(string $field1 [, string $field2 ... ])

Ви можете вибрати лише кілька стовпців у таблиці, якщо хочете (це ефективніше на справді широких таблицях з багатьма стовпцями)

$user->select('id', 'name')->find();

from(string $table)

Ви також можете вибрати іншу таблицю! Чому б і ні?!

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

Ви також можете виконати з'єднання з іншою таблицею в базі даних.

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

Ви можете встановити деякі власні аргументи where (ви не можете встановити параметри в цьому операторі where)

$user->where('id=1 AND name="demo"')->find();

Примітка безпеки - Ви можете бути спокушені зробити щось на зразок $user->where("id = '{$id}' AND name = '{$name}'")->find();. Будь ласка, НЕ РОБІТЬ ЦЬОГО!!! Це піддається атакам SQL-ін'єкцій. Є багато статей в Інтернеті, будь ласка, Google "sql injection attacks php", і ви знайдете багато статей на цю тему. Правильний спосіб обробити це з цією бібліотекою - замість цього методу where(), ви повинні зробити щось на зразок $user->eq('id', $id)->eq('name', $name)->find(); Якщо вам дійсно потрібно це зробити, бібліотека PDO має $pdo->quote($var), щоб екранізувати його для вас. Тільки після того, як ви використовуєте quote(), ви можете використовувати це в операторі where().

group(string $group_by_statement)/groupBy(string $group_by_statement)

Групуйте свої результати за певною умовою.

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

Сортуйте повернуті запити певним чином.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Обмежте кількість повернених записів. Якщо задано другий int, він буде зсувати, обмежуючи так, як у SQL.

$user->orderby('name DESC')->limit(0, 10)->findAll();

Умови WHERE

equal(string $field, mixed $value) / eq(string $field, mixed $value)

Де field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

Де field <> $value

$user->ne('id', 1)->find();

isNull(string $field)

Де field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

Де field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

Де field > $value

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

Де field < $value

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

Де field >= $value

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

Де field <= $value

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

Де field LIKE $value або field NOT LIKE $value

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

Де field IN($value) або field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

Де field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

Умови OR

Можливо обернути ваші умови в оператор OR. Це здійснюється або за допомогою методів startWrap() та endWrap(), або заповнюючи третій параметр умови після поля та значення.

// Метод 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Це буде оцінено як `id = 1 AND (name = 'demo' OR name = 'test')`

// Метод 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Це буде оцінено як `id = 1 OR name = 'demo'`

Взаємовідносини

Ви можете налаштувати декілька видів взаємовідносин, використовуючи цю бібліотеку. Ви можете налаштувати один->багато та один->один взаємовідносини між таблицями. Це потребує незначної додаткової підготовки у класі заздалегідь.

Налаштування масиву $relations не є важким, але вгадування правильного синтаксису може бути заплутаним.

protected array $relations = [
    // ви можете назвати ключ будь-як. Назва класу ActiveRecord, мабуть, підійде. Наприклад: user, contact, client
    'user' => [
        // обов'язково
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // це тип взаємовідносини

        // обов'язково
        'Some_Class', // це "інший" клас ActiveRecord, з яким це буде посилатися

        // обов'язково
        // в залежності від типу взаємовідносини
        // self::HAS_ONE = зовнішній ключ, що посилається на з'єднання
        // self::HAS_MANY = зовнішній ключ, що посилається на з'єднання
        // self::BELONGS_TO = локальний ключ, що посилається на з'єднання
        'local_or_foreign_key',
        // до вашого відома, це також лише приєднує до первинного ключа "іншої" моделі

        // необов'язково
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // додаткові умови, які ви хочете при приєднанні зв'язку
        // $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))

        // необов'язково
        'back_reference_name' // це якщо ви хочете повернути цю взаємовідносину назад до себе, наприклад: $user->contact->user;
    ];
]
class User extends ActiveRecord{
    protected array $relations = [
        'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
        'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
    ];

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }
}

class Contact extends ActiveRecord{
    protected array $relations = [
        'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
        'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
    ];
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'contacts');
    }
}

Тепер у нас налаштовані посилання, щоб ми могли використовувати їх дуже зручно!

$user = new User($pdo_connection);

// знайти найостаннішого користувача.
$user->notNull('id')->orderBy('id desc')->find();

// отримати контакти, використовуючи зв'язок:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// або ми можемо піти в іншу сторону.
$contact = new Contact();

// знайти один контакт
$contact->find();

// отримати користувача за допомогою зв'язку:
echo $contact->user->name; // це ім'я користувача

Досить круто, чи не так?

Налаштування нестандартних даних

Іноді вам може знадобитися прикріпити щось унікальне до вашого ActiveRecord, наприклад, нестандартне обчислення, яке може бути легше просто прикріпити до об'єкта, який потім буде переданий, скажімо, шаблону.

setCustomData(string $field, mixed $value)

Ви прикріплюєте нестандартні дані за допомогою методу setCustomData().

$user->setCustomData('page_view_count', $page_view_count);

І тоді ви просто посилаєтеся на це, як на звичайну властивість об'єкта.

echo $user->page_view_count;

Події

Ще одна надзвичайно класна функція цієї бібліотеки - це події. Події спрацьовують в певний час на основі певних методів, які ви викликаєте. Вони дуже корисні для автоматичного налаштування даних.

onConstruct(ActiveRecord $ActiveRecord, array &config)

Це дійсно корисно, якщо вам потрібно встановити з'єднання за замовчуванням або щось подібне.

// index.php або bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

//
//
//

// User.php
class User extends flight\ActiveRecord {

    protected function onConstruct(self $self, array &$config) { // не забувайте про посилання &
        // ви могли б зробити це, щоб автоматично налаштувати з'єднання
        $config['connection'] = Flight::db();
        // або так
        $self->transformAndPersistConnection(Flight::db());

        // Також ви можете встановити ім'я таблиці таким чином.
        $config['table'] = 'users';
    } 
}

beforeFind(ActiveRecord $ActiveRecord)

Це, ймовірно, лише корисно, якщо вам потрібно маніпулювати запитом щоразу.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFind(self $self) {
        // завжди виконувати id >= 0, якщо це ваше
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

Ця функція, ймовірно, корисніша, якщо ви завжди повинні виконувати якусь логіку кожного разу, коли цей запис отримується. Вам потрібно розшифрувати щось? Вам потрібно виконати налаштування запиту кожен раз (не продуктивно, але байдуже)?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFind(self $self) {
        // розшифрування чогось
        $self->secret = yourDecryptFunction($self->secret, $some_key);

        // можливо, зберігання чогось нестандартного, як запит???
        $self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']); 
    } 
}

beforeFindAll(ActiveRecord $ActiveRecord)

Це, ймовірно, лише корисно, якщо вам потрібно маніпулювати запитом щоразу.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFindAll(self $self) {
        // завжди виконувати id >= 0, якщо це ваше
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

Подібно до afterFind(), але ви можете зробити це для усіх записів!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFindAll(array $results) {

        foreach($results as $self) {
            // зробити щось класне, як і при afterFind()
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

Дійсно корисно, якщо вам потрібно встановити деякі значення за замовчуванням щоразу.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // встановити якісь правильні значення за замовчуванням
        if(!$self->created_date) {
            $self->created_date = gmdate('Y-m-d');
        }

        if(!$self->password) {
            $self->password = password_hash((string) microtime(true));
        }
    } 
}

afterInsert(ActiveRecord $ActiveRecord)

Можливо, у вас є випадок для зміни даних після їх вставки?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // робіть що хочете
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // або що завгодно....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

Дійсно корисно, якщо вам потрібно встановити деякі значення за замовчуванням щоразу на оновлення.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeUpdate(self $self) {
        // встановити якісь правильні значення за замовчуванням
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Можливо, у вас є випадок для зміни даних після їх оновлення?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterUpdate(self $self) {
        // робіть що хочете
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // або що завгодно....
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

Це корисно, якщо ви хочете, щоб події відбувалися як під час вставок, так і оновлень. Я вас не буде дратувати довгим поясненням, але ви, напевно, можете здогадатися, що це таке.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeSave(self $self) {
        $self->last_updated = gmdate('Y-m-d H:i:s');
    } 
}

beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)

Не впевнений, що ви хочете зробити тут, але без суджень! Робіть, як хочете!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo 'Він був сміливим солдатом... :cry-face:';
    } 
}

Управління з'єднаннями з базою даних

Коли ви користуєтеся цією бібліотекою, ви можете встановити з'єднання з базою даних кількома різними способами. Ви можете встановити з'єднання в конструкторі, ви можете встановити його через змінну конфігурації $config['connection'] або ви можете встановити його через setDatabaseConnection() (v0.4.1).

$pdo_connection = new PDO('sqlite:test.db'); // для прикладу
$user = new User($pdo_connection);
// або
$user = new User(null, [ 'connection' => $pdo_connection ]);
// або
$user = new User();
$user->setDatabaseConnection($pdo_connection);

Якщо ви хочете уникнути постійного встановлення $database_connection щоразу, коли ви викликаєте активний запис, існують варіанти!

// index.php або bootstrap.php
// Встановіть це як зареєстрований клас у Flight
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

// User.php
class User extends flight\ActiveRecord {

    public function __construct(array $config = [])
    {
        $database_connection = $config['connection'] ?? Flight::db();
        parent::__construct($database_connection, 'users', $config);
    }
}

// І тепер, аргументи не потрібні!
$user = new User();

Примітка: Якщо ви плануєте юніт-тестування, робити це може додати певні труднощі до юніт-тестування, але в цілому, оскільки ви можете впроваджувати ваше з'єднання за допомогою setDatabaseConnection() або $config['connection'], це не так вже й погано.

Якщо вам потрібно оновити з'єднання з базою даних, наприклад, якщо ви запускаєте тривале CLI-скрипт і вам потрібно періодично оновлювати з'єднання, ви можете заново встановити з'єднання за допомогою $your_record->setDatabaseConnection($pdo_connection).

Внески

Будь ласка, робіть це. :D

Налаштування

Коли ви будете брати участь, переконайтеся, що ви запустили composer test-coverage, щоб підтримувати 100% покриття тестами (це не 100% покриття юніт-тестами, а скоріше інтеграційне тестування).

Також переконайтеся, що ви запустили composer beautify та composer phpcs, щоб виправити будь-які помилки синтаксису.

Ліцензія

MIT

Awesome-plugins/latte

Latte

Latte — це повнофункціональний шаблонний引擎, який дуже простий у використанні і виглядає ближче до синтаксису PHP, ніж Twig або Smarty. Його також дуже легко розширити та додати власні фільтри та функції.

Встановлення

Встановіть за допомогою composer.

composer require latte/latte

Основна конфігурація

Існує кілька основних конфігураційних параметрів для початку роботи. Ви можете дізнатися про них більше в Latte Documentation.


use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {

    // Тут Latte буде кешувати ваші шаблони, щоб прискорити процес
    // Однією з чудових особливостей Latte є те, що він автоматично оновлює ваш
    // кеш, коли ви вносите зміни у ваші шаблони!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Скажіть Latte, де буде коренева директорія для ваших шаблонів.
    // $app->get('flight.views.path') встановлюється у файлі config.php
    //   Ви також могли б просто зробити щось на кшталт `__DIR__ . '/../views/'`
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

Приклад простого макету

Ось простий приклад файлу макету. Це файл, який буде використано для обгортання всіх інших ваших шаблонів.

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
    <head>
        <title>{$title ? $title . ' - '}Мій додаток</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- ваші елементи навігації тут -->
            </nav>
        </header>
        <div id="content">
            <!-- Тут і є ця магія -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; Авторські права
        </div>
    </body>
</html>

А тепер у нас є ваш файл, який буде відображатися всередині цього блоку контенту:

<!-- app/views/home.latte -->
<!-- Це говорить Latte, що цей файл "всередині" файлу layout.latte -->
{extends layout.latte}

<!-- Це контент, який буде відображатися всередині макету в блоці контенту -->
{block content}
    <h1>Головна сторінка</h1>
    <p>Ласкаво просимо до мого додатку!</p>
{/block}

Отже, коли ви будете відображати це у вашій функції або контролері, ви можете зробити щось на кшталт цього:

// простий маршрут
Flight::route('/', function () {
    Flight::latte()->render('home.latte', [
        'title' => 'Головна сторінка'
    ]);
});

// або якщо ви використовуєте контролер
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::latte()->render('home.latte', [
            'title' => 'Головна сторінка'
        ]);
    }
}

Дивіться Latte Documentation для отримання додаткової інформації про те, як максимально використовувати Latte!

Awesome-plugins/awesome_plugins

Чудові плагіни

Flight надзвичайно розширюваний. Існує кілька плагінів, які можна використовувати для додавання функціональності до вашого додатку Flight. Деякі з них офіційно підтримуються командою Flight, а інші є мікро/легкими бібліотеками, які допоможуть вам розпочати.

Документація API

Документація API має вирішальне значення для будь-якого API. Вона допомагає розробникам зрозуміти, як взаємодіяти з вашим API та чого очікувати у відповідь. Є кілька інструментів, доступних для генерації документації API для ваших проєктів Flight.

Аутентифікація/Авторизація

Аутентифікація та авторизація є критично важливими для будь-якої програми, яка вимагає контролю за тим, хто може отримувати доступ до чого.

Кешування

Кешування - це чудовий спосіб пришвидшити вашу програму. Існує кілька бібліотек кешування, які можна використовувати з Flight.

CLI

CLI-додатки - це чудовий спосіб взаємодіяти з вашим додатком. Ви можете використовувати їх для генерації контролерів, відображення всіх маршрутів тощо.

Cookies

Cookies - це чудовий спосіб зберігати невелику кількість даних на стороні клієнта. Їх можна використовувати для зберігання налаштувань користувача, налаштувань програми тощо.

Налагодження

Налагодження має вирішальне значення, коли ви розробляєте у своєму локальному середовищі. Є кілька плагінів, які можуть покращити ваш досвід налагодження.

Бази даних

Бази даних є основою більшості додатків. Ось як ви зберігаєте та отримуєте дані. Деякі бібліотеки бази даних є просто обгортками для написання запитів, а інші є повнофункціональними ORM.

Шифрування

Шифрування має критичне значення для будь-якої програми, яка зберігає чутливі дані. Шифрування та дешифрування даних не є надто складним, але правильне зберігання ключа шифрування може бути важким. Найважливіше - ніколи не зберігайте свій ключ шифрування у публічному каталозі або не комітуйте його в своєму кодовому репозиторії.

Черга завдань

Черги завдань дуже корисні для асинхронної обробки завдань. Це може бути відправка електронних листів, обробка зображень або що завгодно, що не потрібно виконувати в реальному часі.

Сесія

Сесії не є дуже корисними для API, але для створення веб-додатку сесії можуть бути критично важливими для підтримання стану та інформації для входу.

Шаблонізація

Шаблонізація є основою будь-якого веб-додатку з інтерфейсом. Існує кілька движків шаблонізації, які можна використовувати з Flight.

Участь

Є плагін, яким ви хочете поділитися? Надішліть запит на злиття, щоб додати його до списку!

Media

Медіа

Ми намагалися знайти все, що можемо, про різні види медіа в Інтернеті навколо Flight. Дивіться нижче різні ресурси, які ви можете використовувати, щоб дізнатися більше про Flight.

Статті та огляди

Відео та навчальні посібники

Examples

Потрібен швидкий старт?

У вас є два варіанти, щоб почати новий проект на Flight:

Приклади, надані спільнотою:

Потрібна трохи натхнення?

Хоча ці проекти офіційно не спонсоруються командою Flight, вони можуть дати вам ідеї про те, як структуризувати власні проекти, побудовані на Flight!

Хочете поділитися власним прикладом?

Якщо у вас є проект, яким ви хочете поділитися, будь ласка, подайте запит на злиття, щоб додати його до цього списку!

Install/install

Встановлення

Завантажте файли

Переконайтесь, що у вас на системі встановлено PHP. Якщо ні, натисніть тут для інструкцій щодо його встановлення.

Якщо ви використовуєте Composer, ви можете виконати наступну команду:

composer require flightphp/core

АБО ви можете завантажити файли безпосередньо та витягти їх у свій веб-директорію.

Налаштуйте свій веб-сервер

Вбудований сервер розробки PHP

Це, безумовно, найпростіший спосіб запустити свій проект. Ви можете використовувати вбудований сервер, щоб запустити своє застосування та навіть використовувати SQLite для бази даних (якщо sqlite3 встановлено на вашій системі) і не вимагати багато чого! Просто виконайте наступну команду після встановлення PHP:

php -S localhost:8000

Потім відкрийте свій браузер і перейдіть на http://localhost:8000.

Якщо ви хочете зробити корінь документа вашого проекту в іншому каталозі (Наприклад: ваш проект ~/myproject, але корінь документа ~/myproject/public/), ви можете виконати наступну команду, як тільки ви в директорії ~/myproject:

php -S localhost:8000 -t public/

Потім відкрийте свій браузер і перейдіть на http://localhost:8000.

Apache

Переконайтесь, що Apache вже встановлено на вашій системі. Якщо ні, знайдіть у Google, як встановити Apache на вашій системі.

Для Apache, відредагуйте свій файл .htaccess наступним чином:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Примітка: Якщо вам потрібно використовувати flight в підкаталозі, додайте рядок RewriteBase /subdir/ відразу після RewriteEngine On.

Примітка: Якщо ви хочете захистити всі серверні файли, такі як db чи env файл. Помістіть це у свій файл .htaccess:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Переконайтесь, що Nginx вже встановлено на вашій системі. Якщо ні, знайдіть у Google, як встановити Nginx на вашу систему.

Для Nginx, додайте наступне до вашої декларації сервера:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Створіть свій файл index.php

<?php

// Якщо ви використовуєте Composer, підключіть автозавантажувач.
require 'vendor/autoload.php';
// якщо ви не використовуєте Composer, підключіть фреймворк безпосередньо
// require 'flight/Flight.php';

// Потім визначте маршрут і призначте функцію для обробки запиту.
Flight::route('/', function () {
  echo 'hello world!';
});

// Нарешті, запустіть фреймворк.
Flight::start();

Встановлення PHP

Якщо у вас вже встановлено php на вашій системі, сміливо пропустіть ці інструкції та перейдіть до розділу завантаження

Звичайно! Ось інструкції для встановлення PHP на macOS, Windows 10/11, Ubuntu та Rocky Linux. Я також включу деталі про те, як встановити різні версії PHP.

macOS

Встановлення PHP за допомогою Homebrew

  1. Встановіть Homebrew (якщо ще не встановлено):

    • Відкрийте Terminal і виконайте:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Встановіть PHP:

    • Встановіть останню версію:
      brew install php
    • Щоб встановити конкретну версію, наприклад, PHP 8.1:
      brew tap shivammathur/php
      brew install shivammathur/php/php@8.1
  3. Перемикання між версіями PHP:

    • Відключіть поточну версію та підключіть бажану версію:
      brew unlink php
      brew link --overwrite --force php@8.1
    • Перевірте встановлену версію:
      php -v

Windows 10/11

Встановлення PHP вручну

  1. Завантажте PHP:

    • Відвідайте PHP для Windows і завантажте останню або конкретну версію (наприклад, 7.4, 8.0) у вигляді zip-файлу без підтримки потоків.
  2. Розпакуйте PHP:

    • Розпакуйте завантажений zip-файл до C:\php.
  3. Додайте PHP до системної PATH:

    • Перейдіть до Системні властивості > Змінні середовища.
    • У розділі Системні змінні знайдіть Path і натисніть Редагувати.
    • Додайте шлях C:\php (або куди ви розпакували PHP).
    • Натисніть ОК, щоб закрити всі вікна.
  4. Налаштуйте PHP:

    • Скопіюйте php.ini-development у php.ini.
    • Відредагуйте php.ini, щоб налаштувати PHP за потреби (наприклад, налаштування extension_dir, увімкнення розширень).
  5. Перевірте установку PHP:

    • Відкрийте командний рядок і виконайте:
      php -v

Встановлення кількох версій PHP

  1. Повторіть вищезазначені кроки для кожної версії, розміщуючи кожну в окремій директорії (наприклад, C:\php7, C:\php8).

  2. Перемикання між версіями шляхом коригування системної змінної PATH, щоб вказати на директорію бажаної версії.

Ubuntu (20.04, 22.04 тощо)

Встановлення PHP за допомогою apt

  1. Оновіть списки пакетів:

    • Відкрийте Terminal і виконайте:
      sudo apt update
  2. Встановіть PHP:

    • Встановіть останню версію PHP:
      sudo apt install php
    • Щоб встановити конкретну версію, наприклад, PHP 8.1:
      sudo apt install php8.1
  3. Встановлення додаткових модулів (за бажанням):

    • Наприклад, щоб встановити підтримку MySQL:
      sudo apt install php8.1-mysql
  4. Перемикання між версіями PHP:

    • Використовуйте update-alternatives:
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Перевірте встановлену версію:

    • Виконайте:
      php -v

Rocky Linux

Встановлення PHP за допомогою yum/dnf

  1. Увімкніть репозиторій EPEL:

    • Відкрийте Terminal і виконайте:
      sudo dnf install epel-release
  2. Встановіть репозиторій Remi:

    • Виконайте:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. Встановіть PHP:

    • Щоб встановити версію за замовчуванням:
      sudo dnf install php
    • Щоб встановити конкретну версію, наприклад, PHP 7.4:
      sudo dnf module install php:remi-7.4
  4. Перемикання між версіями PHP:

    • Використовуйте команду модуля dnf:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Перевірте встановлену версію:

    • Виконайте:
      php -v

Загальні нотатки

Guides

Посібники

Flight PHP створений, щоб бути простим, але потужним, а наші посібники допоможуть вам крок за кроком створити реальні додатки. Ці практичні навчальні посібники проведуть вас через повноцінні проекти, щоб продемонструвати, як Flight може бути використаний ефективно.

Офіційні посібники

Створення блогу

Дізнайтеся, як створити функціональний блог-додаток за допомогою Flight PHP. Цей посібник проводить вас через:

Цей навчальний посібник ідеально підходить для початківців, які хочуть побачити, як усі елементи складаються разом у реальному додатку.

Неофіційні посібники

Хоча ці посібники не підтримуються офіційно командою Flight, вони є цінними ресурсами, створеними спільнотою. Вони охоплюють різні теми та сценарії використання, надаючи додаткові відомості про використання Flight PHP.

Створення RESTful API за допомогою Flight Framework

Цей посібник проводить вас через створення RESTful API, використовуючи фреймворк Flight PHP. Він охоплює основи налаштування API, визначення маршрутів і повернення JSON-відповідей.

Створення простого блогу

Цей посібник проводить вас через створення базового блогу, використовуючи фреймворк Flight PHP. Він насправді має 2 частини: одна охоплює основи, а інша – більш складні теми та вдосконалення для готового до виробництва блогу.

Створення Pokémon API в PHP: Посібник для початківців

Цей веселий посібник проводить вас через створення простого Pokémon API за допомогою Flight PHP. Він охоплює основи налаштування API, визначення маршрутів і повернення JSON-відповідей.

Участь

Є ідея для посібника? Знайшли помилку? Ми вітаємо внески! Наші посібники підтримуються в репозиторії документації FlightPHP.

Якщо ви створили щось цікаве з Flight і хочете поділитися цим як посібником, будь ласка, надішліть запит на злиття. Обмін знаннями допомагає спільноті Flight зростати.

Шукаєте документацію API?

Якщо ви шукаєте конкретну інформацію про основні функції та методи Flight, зверніть увагу на розділ "Learn" у нашій документації.