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
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();

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

Висновок

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

Learn/routing

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

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

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

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

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

Функції/Колбеки

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

function hello() {
    echo 'привіт світ!';
}

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

Класи

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

class Greeting {
    public static function hello() {
        echo 'привіт світ!';
    }
}

Flight::route('/', [ 'Greeting', 'hello' ]);

Або, створивши об'єкт спочатку, а потім викликавши метод:


// Greeting.php
class Greeting
{
    public function __construct() {
        $this->name = 'Джон Доу';
    }

    public function hello() {
        echo "Привіт, {$this->name}!";
    }
}

// index.php
$greeting = new Greeting();

Flight::route('/', [ $greeting, 'hello' ]);
// Ви також можете зробити це без попереднього створення об'єкта
// Примітка: Аргументи не будуть впроваджені в конструктор
Flight::route('/', [ 'Greeting', 'hello' ]);
// Крім того, ви можете використовувати цей коротший синтаксис
Flight::route('/', 'Greeting->hello');
// або
Flight::route('/', Greeting::class.'->hello');

Впровадження залежностей через DIC (Контейнер для впровадження залежностей)

Якщо ви хочете використовувати впровадження залежностей через контейнер (PSR-11, PHP-DI, Dice тощо), єдиний тип маршрутів, де це доступно, - це або безпосереднє створення об'єкта самостійно та використання контейнера для створення вашого об'єкта, або ви можете використовувати рядки для визначення класу та методу для виклику. Ви можете перейти на сторінку Впровадження залежностей для отримання додаткової інформації.

Ось швидкий приклад:


use flight\database\PdoWrapper;

// Greeting.php
class Greeting
{
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    }

    public function hello(int $id) {
        // зробіть щось з $this->pdoWrapper
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "Привіт, світ! Моє ім'я {$name}!";
    }
}

// index.php

// Налаштуйте контейнер з будь-якими параметрами, які вам потрібні
// Див. сторінку Впровадження залежностей для отримання додаткової інформації про PSR-11
$dice = new \Dice\Dice();

// Не забудьте переназначити змінну '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// Зареєструйте обробник контейнера
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// Маршрути, як зазвичай
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// або
Flight::route('/hello/@id', 'Greeting->hello');
// або
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

Метод маршрутизації

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

Flight::route('GET /', function () {
  echo 'Я отримав GET запит.';
});

Flight::route('POST /', function () {
  echo 'Я отримав POST запит.';
});

// Ви не можете використовувати Flight::get() для маршрутів, оскільки це метод 
//    для отримання змінних, а не для створення маршруту.
// Flight::post('/', function() { /* код */ });
// Flight::patch('/', function() { /* код */ });
// Flight::put('/', function() { /* код */ });
// Flight::delete('/', function() { /* код */ });

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

Flight::route('GET|POST /', function () {
  echo 'Я отримав або GET, або POST запит.';
});

Крім того, ви можете отримати об’єкт Router, в якому є допоміжні методи для використання:


$router = Flight::router();

// мапує всі методи
$router->map('/', function() {
    echo 'привіт світ!';
});

// GET запит
$router->get('/users', function() {
    echo 'користувачі';
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();

Регулярні вирази

Ви можете використовувати регулярні вирази у ваших маршрутах:

Flight::route('/user/[0-9]+', function () {
  // Це буде відповідати /user/1234
});

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

Названі параметри

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

Flight::route('/@name/@id', function (string $name, string $id) {
  echo "привіт, $name ($id)!";
});

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

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // Це буде відповідати /bob/123
  // Але не відповідатиме /bob/12345
});

Примітка: Із групами відповідності regex () з названими параметрами не підтримується. :'(

Необов'язкові параметри

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

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 !== "Боб") {
    // Продовжте до наступного маршруту
    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
  });
});

Потокова передача

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

Примітка: Потокові відповіді доступні лише в тому випадку, якщо ви маєте flight.v2.output_buffering встановлений на false.

Потокова передача з ручними заголовками

Ви можете потоково передавати відповідь клієнту, використовуючи метод stream() на маршруті. Якщо ви це робите, ви повинні вручну встановити всі методи, перш ніж щось виводити клієнту. Це робиться за допомогою функції php header() або методу 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') // Виконує кешування HTTP ETag.
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-відповідь і зупиняє фреймворк.

Будь-які користувацькі методи, додані за допомогою 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 захопить це та надішле назад користувачу з відповідними заголовками.


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

// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Привіт, Світ!

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


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

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

Код статусу

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

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

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

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

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

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

Flight::route('/', function() {
    Flight::response()->write("Привіт, Світ!");
});

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

Flight::route('/', function() {
    echo "Привіт, Світ!";
});

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

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

Flight::route('/', function() {
    if($someCondition) {
        Flight::response()->write("Привіт, Світ!");
    } 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:


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

JSON

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

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

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

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

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

JSON з красивим форматом

Ви також можете передати аргумент до останньої позиції для увімкнення красивого форматування:

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

Якщо ви змінюєте параметри, передані в Flight::json(), і хочете спростити синтаксис, ви можете просто переназначити метод 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 = someAuthorizationCheck();
    // Перевірте, чи користувач авторизований
    if($authorized === false) {
        Flight::jsonHalt(['error' => 'Неавторизований'], 401);
    }

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

Перед v3.10.0, вам доводилося робити щось подібне:

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // Перевірте, чи користувач авторизований
    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 ("Перейти за іншим"). Ви можете додатково встановити кастомний код:

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/templates

HTML Views and Templates

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

Якщо вам потрібні більш складні потреби в шаблонізації, подивіться приклади Smarty і Latte в розділі Custom Views.

Дефолтний движок перегляду

Щоб відобразити шаблон перегляду, викликайте метод 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>

Користувацькі движки перегляду

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

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);
});

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

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

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

function hello(string $name) {
  echo "hello $name!";
}

hello('Bob');

Це використовується більше, коли вам потрібно передати змінні у ваш метод для отримання очікуваного значення. Використання методу 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 не може співвіднести 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__.'/../');

License

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

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

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

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

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

About

Що таке Flight?

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

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

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

<?php

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

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

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

Flight::start();

Досить просто, правда? Дізнайтеся більше про Flight у документації!

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

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

Спільнота

Ми в Matrix Чаті разом з нами за адресою #flight-php-framework:matrix.org.

Як допомогти

Існує два способи, як ви можете внести свій внесок у 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

Wruczek/PHP-File-Cache

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

Переваги

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

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

Встановіть за допомогою composer:

composer require wruczek/php-file-cache

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

Використання досить просте.

use Wruczek\PhpFileCache\PhpFileCache;

$app = Flight::app();

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

    // Це забезпечує, що кеш використовується лише в продуктивному режимі
    // ENVIRONMENT - це константа, яка встановлюється у вашому bootstrap файлі або в іншому місці вашого додатку
    $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/Wruczek/PHP-File-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/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/session

Ghostff/Session

PHP Менеджер Сесій (нема блокувань, flash, сегмент, шифрування сесій). Використовує PHP open_ssl для необов’язкового шифрування/дешифрування даних сесії. Підтримує файли, 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();
});

// Ця перевірка могла б бути в логіці обмеженої сторінки або обернута з middleware.
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // виконайте вашу логіку обмеженої сторінки тут
});

// версія middleware
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/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 фреймворком Flight. Повністю на ваш розсуд.

Незалежно

Просто переконайтеся, що ви передаєте з'єднання PDO до конструктора.

$pdo_connection = new PDO('sqlite:test.db'); // це просто для прикладу, ви, напевно, використовуєте реальне з’єднання з базою даних

$User = new User($pdo_connection);

Не хочете завжди налаштовувати з'єднання з базою даних у конструкторі? Дивіться Управління з'єднанням з базою даних для інших ідей!

Реєстрація як метод у Flight

Якщо ви використовуєте PHP фреймворк 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' ]);
        // ви також можете встановити первинний ключ таким чином замість масиву вище.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // або як вам потрібно згенерувати ваші унікальні ідентифікатори
    }
}

Якщо ви не встановите первинний ключ перед вставкою, він буде встановлено на 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'; // тепер електронна пошта вважається "брудною", оскільки була змінена.
$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 Injection. Є багато статей в Інтернеті, будь ласка, 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)

Обмежте кількість повернених записів. Якщо передано друге ціле число, він буде зсувом, обмеженням, як у 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();

Відносини

Ви можете встановити кілька видів зв’язків, використовуючи цю бібліотеку. Ви можете встановити один->багато та один->один зв'язки між таблицями. Це вимагає деякого додаткового налаштування в класі заздалегідь.

Встановлення масиву $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% покриття тестами (це не справжнє покриття одиничних тестів, швидше інтеграційне тестування).

Також не забудьте запустити 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, а інші є мікро/легкими бібліотеками, щоб допомогти вам почати.

Аутентифікація/Авторизація

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

Кешування

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

CLI

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

Cookies

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

Налагодження

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

Бази даних

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

Шифрування

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

Сесія

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

Шаблонізація

Шаблонізація є основою будь-якого веб-застосунку з UI. Є кілька шаблонних рушіїв, які можна використовувати з 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

Загальні нотатки