Learn

Узнайте о Flight

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

Важные концепции фреймворка

Почему использовать фреймворк?

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

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

Flight по сравнению с другими фреймворками

Если вы переходите с другого фреймворка, такого как Laravel, Slim, Fat-Free или Symfony на Flight, эта страница поможет вам понять различия между ними.

Основные темы

Автозагрузка

Научитесь автоматически загружать ваши собственные классы в вашем приложении.

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

Научитесь управлять маршрутами для вашего веб-приложения. Это также включает группировку маршрутов, параметры маршрута и промежуточное ПО.

Промежуточное ПО

Научитесь использовать промежуточное ПО для фильтрации запросов и ответов в вашем приложении.

Запросы

Научитесь обрабатывать запросы и ответы в вашем приложении.

Ответы

Научитесь отправлять ответы вашим пользователям.

HTML шаблоны

Научитесь использовать встроенный движок для отображения ваших HTML шаблонов.

Безопасность

Научитесь обезопасить ваше приложение от распространенных угроз безопасности.

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

Научитесь настраивать фреймворк для вашего приложения.

Расширение Flight

Научитесь расширять фреймворк, добавляя собственные методы и классы.

События и Фильтрация

Научитесь использовать систему событий для добавления хуков к вашим методам и внутренним методам фреймворка.

Контейнер внедрения зависимостей

Научитесь использовать контейнеры внедрения зависимостей (DIC), чтобы управлять зависимостями вашего приложения.

API фреймворка

Узнайте о основных методах фреймворка.

Миграция на v3

Совместимость с предыдущими версиями в целом сохранена, но есть некоторые изменения, о которых вам следует знать, переходя с v2 на v3.

Устранение Неполадок

Есть некоторые общие проблемы, с которыми вы можете столкнуться при использовании Flight. Эта страница поможет вам устранить эти проблемы.

Learn/stopping

Остановка

Вы можете остановить фреймворк в любой момент, вызвав метод halt:

Flight::halt();

Вы также можете указать необязательный HTTP код состояния и сообщение:

Flight::halt(200, 'Сейчас вернусь...');

Вызов halt приведет к отмене любого содержимого ответа до этого момента. Если вы хотите остановить фреймворк и вывести текущий ответ, используйте метод stop:

Flight::stop();

Learn/errorhandling

Обработка Ошибок

Ошибки и Исключения

Все ошибки и исключения перехватываются Flight и передаются методу error. Поведение по умолчанию - отправлять общий ответ HTTP 500 Внутренняя Ошибка Сервера с некоторой информацией об ошибке.

Вы можете переопределить это поведение для ваших собственных потребностей:

Flight::map('error', function (Throwable $error) {
  // Обработать ошибку
  echo $error->getTraceAsString();
});

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

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

Не Найдено

Когда URL не может быть найден, Flight вызывает метод notFound. Поведение по умолчанию - отправить ответ HTTP 404 Не Найдено с простым сообщением.

Вы можете переопределить это поведение для ваших собственных потребностей:

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

Learn/flight_vs_laravel

Сравнение Flight и Laravel

Что такое Laravel?

Laravel - это полнофункциональный фреймворк с множеством функций и удивительной экосистемой, но это отражается на производительности и сложности. Цель Laravel - обеспечить разработчику наивысший уровень продуктивности и упростить выполнение общих задач. Laravel отличный выбор для разработчиков, которые стремятся создать полнофункциональное корпоративное веб-приложение. Однако это связано с определенными компромиссами, особенно в плане производительности и сложности. Начать работу с Laravel легко, но для освоения фреймворка потребуется некоторое время.

Часто разработчики чувствуют, что единственный способ решить проблемы в Laravel - использовать модули, в то время как на самом деле можно просто использовать другую библиотеку или написать свой собственный код.

Преимущества по сравнению с Flight

Недостатки по сравнению с Flight

Learn/migrating_to_v3

Миграция на v3

Обратная совместимость в целом была сохранена, но есть некоторые изменения, о которых вам следует знать при переходе с v2 на v3.

Поведение буферизации вывода (3.5.0)

Буферизация вывода - это процесс, при котором вывод, сгенерированный сценарием PHP, хранится в буфере (внутри PHP) перед отправкой клиенту. Это позволяет вам изменять вывод перед его отправкой клиенту.

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

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

Где у вас могут возникнуть проблемы

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

// просто пример
define('START_TIME', microtime(true));

function hello() {
    echo 'Привет, мир';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // это на самом деле будет хорошо
    echo '<p>Эта фраза Привет, мир была предоставлена буквой "П"</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 был преобразован в более объектно-ориентированный для удобства использования контейнеров внедрения зависимостей. Если вам нужно вызвать метод аналогично тому, как делал Диспетчер, вы можете вручную использовать что-то вроде $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 предоставляет ряд функций, чтобы помочь вам обеспечить безопасность ваших веб-приложений.

Заголовки (Headers)

HTTP заголовки - один из самых простых способов защитить ваши веб-приложения. Вы можете использовать заголовки, чтобы предотвратить clickjacking, XSS и другие атаки. Есть несколько способов, как можно добавить эти заголовки к вашему приложению.

Два отличных сайта, на которых можно проверить безопасность ваших заголовков, это securityheaders.com и observatory.mozilla.org.

Добавление вручную

Вы можете вручную добавить эти заголовки, используя метод 'header' объекта Flight\Response.

// Установить заголовок X-Frame-Options для предотвращения clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Установить заголовок Content-Security-Policy для предотвращения XSS
// Примечание: этот заголовок может быть очень сложным, так что вам понадобится
// обращаться к примерам в интернете для вашего приложения
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

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

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

// Установить заголовок Referrer-Policy для управления количеством отправляемой информации обратного адреса
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

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

// Установить заголовок Permissions-Policy для управления используемыми функциями и API
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Эти заголовки могут быть добавлены в начало ваших файлов bootstrap.php или index.php.

Добавление как Фильтр

Вы также можете добавить их в фильтр/хук, как показано ниже:

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

Добавление как Middleware

Вы также можете добавить их как класс Middleware. Это хороший способ держать свой код чистым и организованным.

// 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 или где у вас находятся ваши маршруты
//FYI, эта группа пустых строк действует как глобальное промежуточное ПО для всех маршрутов. Конечно же, вы могли бы сделать то же самое и просто добавить это только к определенным маршрутам.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers']);
    // более маршрутов
}, [ new SecurityHeadersMiddleware() ]);

Межсайтовая Подделка Запроса (CSRF)

Межсайтовая подделка запроса (CSRF) - это тип атаки, при которой злоумышленный сайт может заставить браузер пользователя отправить запрос на ваш сайт. Это может использоваться для выполнения действий на вашем сайте без ведома пользователя. Flight не предоставляет встроенного механизма защиты от CSRF, но вы легко можете реализовать свой собственный, используя промежуточное программное обеспечение.

Настройка

Сначала вам необходимо сгенерировать токен CSRF и сохранить его в сессии пользователя. Затем вы можете использовать этот токен в ваших формах и проверять его при отправке формы.

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

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

Использование Latte

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

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

И теперь в ваших шаблонах Latte вы можете использовать функцию csrf() для вывода токена CSRF.

<form method="post">
    {csrf()}
    <!-- другие поля формы -->
</form>

Коротко и просто, верно?

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

Вы можете проверить токен CSRF, используя фильтры событий:

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

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

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

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

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

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

Межсайтовая Вставка Скриптов (XSS)

Межсайтовая вставка скриптов (XSS) - это тип атаки, при которой злоумышленный сайт может внедрить код на вашем сайте. Большинство таких возможностей происходят из значений форм, которые вводят ваши конечные пользователи. Никогда не доверяйте выводу от ваших пользователей! Всегда предполагайте, что все они - лучшие хакеры в мире. Они могут внедрить вредоносный JavaScript или HTML на вашу страницу. Этот код может использоваться для кражи информации от ваших пользователей или выполнения действий на вашем сайте. Используя класс представлений Flight, вы легко можете экранировать вывод для предотвращения атак XSS.

// Допустим, пользователь умный и пытается использовать это в качестве своего имени
$name = '<script>alert("XSS")</script>';

// Это экранирует вывод
Flight::view()->set('name', $name);
// Это выведет: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Если вы используете что-то вроде Latte, зарегистрированного как ваш класс представлений, он также автоматически будет экранировать это.
Flight::view()->render('template', ['name' => $name]);

SQL Инъекция

SQL инъекция - это тип атаки, при которой злоумышленный пользователь может внедрить SQL-код в вашу базу данных. Это может быть использовано для кражи информации из вашей базы данных или выполнения действий над вашей базой данных. Опять же, никогда не доверяйте вводу от ваших пользователей! Всегда предполагайте, что они настроены враждебно. Вы можете использовать подготовленные операторы в ваших объектах PDO, чтобы предотвратить SQL инъекцию.

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

// Если вы используете класс PdoWrapper, это можно сделать легко в одной строке
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

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

// Просто пообещайте, что вы никогда, никогда НИКОГДА не сделаете что-то подобное...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// потому что что, если $username = "' OR 1=1; -- "; 
// После построения запроса он выглядит следующим образом
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Это выглядит странно, но это действующий запрос, который будет работать. Фактически,
// это очень распространенная атака методом SQL инъекции, которая вернет все пользователей.  

CORS

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

// app/utils/CorsUtil.php

namespace app\utils;

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

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

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

    private function allowOrigins(): void
    {
        // настройте разрешенные хосты здесь
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        $request = Flight::request();

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

// index.php или где у вас находятся ваши маршруты
$CorsUtil = new CorsUtil();

// Это должно быть выполнено до вызова start.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Заключение

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

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

Заголовки (Headers)

HTTP заголовки - один из самых простых способов защитить ваши веб-приложения. Вы можете использовать заголовки, чтобы предотвратить clickjacking, XSS и другие атаки. Есть несколько способов, как можно добавить эти заголовки к вашему приложению.

Два отличных сайта, на которых можно проверить безопасность ваших заголовков, это securityheaders.com и observatory.mozilla.org.

Добавление вручную

Вы можете вручную добавить эти заголовки, используя метод 'header' объекта Flight\Response.

// Установить заголовок X-Frame-Options для предотвращения clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Установить заголовок Content-Security-Policy для предотвращения XSS
// Примечание: этот заголовок может быть очень сложным, так что вам понадобится
// обращаться к примерам в интернете для вашего приложения
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

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

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

// Установить заголовок Referrer-Policy для управления количеством отправляемой информации обратного адреса
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

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

// Установить заголовок Permissions-Policy для управления используемыми функциями и API
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Эти заголовки могут быть добавлены в начало ваших файлов bootstrap.php или index.php.

Добавление как Фильтр

Вы также можете добавить их в фильтр/хук, как показано ниже:

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

Добавление как Middleware

Вы также можете добавить их как класс Middleware. Это хороший способ держать свой код чистым и организованным.

// 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 или где у вас находятся ваши маршруты
//FYI, эта группа пустых строк действует как глобальное промежуточное ПО для всех маршрутов. Конечно же, вы могли бы сделать то же самое и просто добавить это только к определенным маршрутам.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers']);
    // более маршрутов
}, [ new SecurityHeadersMiddleware() ]);

Межсайтовая Подделка Запроса (CSRF)

Межсайтовая подделка запроса (CSRF) - это тип атаки, при которой злоумышленный сайт может заставить браузер пользователя отправить запрос на ваш сайт. Это может использоваться для выполнения действий на вашем сайте без ведома пользователя. Flight не предоставляет встроенного механизма защиты от CSRF, но вы легко можете реализовать свой собственный, используя промежуточное программное обеспечение.

Настройка

Сначала вам необходимо сгенерировать токен CSRF и сохранить его в сессии пользователя. Затем вы можете использовать этот токен в ваших формах и проверять его при отправке формы.

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

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

Использование Latte

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

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

И теперь в ваших шаблонах Latte вы можете использовать функцию csrf() для вывода токена CSRF.

<form method="post">
    {csrf()}
    <!-- другие поля формы -->
</form>

Коротко и просто, верно?

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

Вы можете проверить токен CSRF, используя фильтры событий:

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

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

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

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

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

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

Межсайтовая Вставка Скриптов (XSS)

Межсайтовая вставка скриптов (XSS) - это тип атаки, при которой злоумышленный сайт может внедрить код на вашем сайте. Большинство таких возможностей происходят из значений форм, которые вводят ваши конечные пользователи. Никогда не доверяйте выводу от ваших пользователей! Всегда предполагайте, что все они - лучшие хакеры в мире. Они могут внедрить вредоносный JavaScript или HTML на вашу страницу. Этот код может быть использован для кражи информации от ваших пользователей или выполнения действий на вашем сайте. Используя класс представлений Flight, вы легко можете экранировать вывод для предотвращения атак XSS.

// Допустим, пользователь умный и пытается использовать это в качестве своего имени
$name = '<script>alert("XSS")</script>';

// Это экранирует вывод
Flight::view()->set('name', $name);
// Это выведет: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Если вы используете что-то вроде Latte, зарегистрированного как ваш класс представлений, он также автоматически будет экранировать это.
Flight::view()->render('template', ['name' => $name]);

SQL Инъекция

SQL инъекция - это тип атаки, при которой злоумышленный пользователь может внедрить SQL-код в вашу базу данных. Это может быть использовано для кражи информации из вашей базы данных или выполнения действий над вашей базой данных. Опять же, никогда не доверяйте вводу от ваших пользователей! Всегда предполагайте они нацелены на вас. Вы можете использовать подготовленные операторы в ваших объектах PDO, чтобы предотвратить SQL инъекцию.

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

// Если вы используете класс PdoWrapper, это можно сделать легко в одной строке
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

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

// Просто пообещайте, что вы никогда, никогда НИКОГДА не сделаете что-то подобное...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// потому что что, если $username = "' OR 1=1; -- "; 
// После построения запроса он выглядит следующим образом
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Это выглядит странно, но это действующий запрос, который будет работать. Фактически,
// это очень распространенная атака методом SQL инъекции, которая вернет все пользователей.  

CORS

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

// app/utils/CorsUtil.php

namespace app\utils;

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

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

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

    private function allowOrigins(): void
    {
        // настройте разрешенные хосты здесь
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        $request = Flight::request();

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

// index.php или где у вас находятся ваши маршруты
$CorsUtil = new CorsUtil();

// Это должно быть выполнено до вызова start.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Заключение

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

Learn/overriding

Переопределение

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/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 и т. д.), единственный тип маршрутов, где это доступно - это либо непосредственное создание объекта самостоятельно и использование контейнера для создания вашего объекта, или вы можете использовать строки для определения класса и метода для вызова. Загляните на страницу [Внедрение зависимостей] (/learn/extending) для более подробной информации.

Вот быстрый пример:


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 'user:'.$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 'user:'.$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() на маршруте. Если вы делаете так, вам необходимо установить все методы вручную перед тем, как что-то выводить в клиент. Это делается с помощью функции header() php или метода Flight::response()->setRealHeader().

Flight::route('/@filename', function($filename) {

    // очевидно, вы также будете очищать путь и прочее.
    $fileNameSafe = basename($filename);

    // Если у вас есть дополнительные заголовки для установки после выполнения маршрута
    // вы должны определить их, прежде чем что-то будет выведено.
    // Все они должны быть прямым вызовом функции header() или 
    // вызовом метода Flight::response()->setRealHeader()
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // или
    Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"');

    $fileData = file_get_contents('/some/path/to/files/'.$fileNameSafe);

    // Обработка ошибок и прочее
    if(empty($fileData)) {
        Flight::halt(404, 'Файл не найден');
    }

    // вручную установить длину контента, если хотите
    header# Маршрутизация

> **Примечание:** Хотите узнать больше о маршрутизации? Посмотрите страницу ["почему фреймворк?"](/learn/why-frameworks) для более подробного объяснения.

Основная маршрутизация в Flight выполняется путем сопоставления шаблона URL с функцией обратного вызова или массивом класса и метода.

```php
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');

Learn/flight_vs_symfony

Сравнение Flight и 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/variables

Переменные

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

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 означает, что каждый раз будет возвращаться один и тот же объект
    '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 в своих контроллерах/промежуточных уровнях, вот как вы можете его настроить:


// Где-то в вашем файле инициализации
$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 'thing';
    }
}

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

Промежуточное программное обеспечение маршрута

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 Forbidden, но без настроек.
  2. Вы можете перенаправить пользователя на страницу входа, используя Flight::redirect().
  3. Вы можете создать пользовательскую ошибку в промежуточном программном обеспечении и остановить выполнение маршрута.

Простой пример

Вот простой пример с возвратом 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' => 'Необходимо войти в систему, чтобы получить доступ к этой странице.']);
        }
    }
}

Группирование промежуточного программного обеспечения

Вы можете добавить группу маршрутов, и затем у каждого маршрута в этой группе будет также применяться то же промежуточное программное обеспечение. Это полезно, если вам нужно сгруппировать несколько маршрутов, например при помощи промежуточного программного обеспечения аутентификации для проверки ключа 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 'пользователь:'.$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 'пользователь:'.$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 "Hello, $name!";
});

// Добавляем фильтр до
Flight::before('hello', function (array &$params, string &$output): bool {
  // Изменяем параметр
  $params[0] = 'Fred';
  return true;
});

// Добавляем фильтр после
Flight::after('hello', function (array &$params, string &$output): bool {
  // Изменяем вывод
  $output .= " Have a nice day!";
  return true;
});

// Вызываем пользовательский метод
echo Flight::hello('Bob');

Это должно вывести:

Hello Fred! Have a nice day!

Если у вас определено несколько фильтров, вы можете прервать цепочку, вернув false в любой из ваших фильтров:

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

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

  // Это прервет цепочку
  return false;
});

// Этот фильтр не будет вызван
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'three';
  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'];

Обработка Загрузки Файлов

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

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


// Возможно, вам нужен заголовок авторизации
$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/frameworkmethods

Методы фреймворка

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

Основные Методы

Flight::map(строка $name, callable $callback, bool $pass_route = false) // Создает пользовательский метод фреймворка.
Flight::register(строка $name, строка $class, массив $params = [], ?callable $callback = null) // Регистрирует класс для метода фреймворка.
Flight::before(строка $name, callable $callback) // Добавляет фильтр перед методом фреймворка.
Flight::after(строка $name, callable $callback) // Добавляет фильтр после метода фреймворка.
Flight::path(строка $path) // Добавляет путь для автозагрузки классов.
Flight::get(строка $key) // Получает переменную.
Flight::set(строка $key, смешанный $value) // Устанавливает переменную.
Flight::has(строка $key) // Проверяет, установлена ли переменная.
Flight::clear(массив|строка $key = []) // Очищает переменную.
Flight::init() // Инициализирует фреймворк к его настройкам по умолчанию.
Flight::app() // Получает экземпляр объекта приложения.

Расширяемые Методы

Flight::start() // Запускает фреймворк.
Flight::stop() // Останавливает фреймворк и отправляет ответ.
Flight::halt(int $code = 200, строка $message = '') // Останавливает фреймворк с необязательным кодом состояния и сообщением.
Flight::route(строка $pattern, callable $callback, bool $pass_route = false) // Сопоставляет шаблон URL с обратным вызовом.
Flight::group(строка $pattern, callable $callback) // Создает группировку для URL, шаблон должен быть строкой.
Flight::redirect(строка $url, int $code) // Перенаправляет на другой URL.
Flight::render(строка $file, массив $data, ?строка $key = null) // Рендерит файл шаблона.
Flight::error(Throwable $error) // Отправляет ответ HTTP 500.
Flight::notFound() // Отправляет ответ HTTP 404.
Flight::etag(строка $id, строка $type = 'string') // Выполняет кэширование HTTP ETag.
Flight::lastModified(int $time) // Выполняет кэширование HTTP Last-Modified.
Flight::json(смешанный $data, int $code = 200, bool $encode = true, строка $charset = 'utf8', int $option) // Отправляет ответ JSON.
Flight::jsonp(смешанный $data, строка $param = 'jsonp', int $code = 200, bool $encode = true, строка $charset = 'utf8', int $option) // Отправляет ответ JSONP.

Любые пользовательские методы, добавленные с помощью map и register, также могут быть отфильтрованы.

Learn/api

Методы API фреймворка

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

Базовые методы

Эти методы являются базовыми для фреймворка и не могут быть переопределены.

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

Расширяемые методы

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

Кэширование HTTP

Flight обеспечивает встроенную поддержку кэширования на уровне HTTP. Если условие кэширования выполнено, Flight вернет ответ HTTP 304 Not Modified. В следующий раз когда клиент запросит тот же ресурс, их попросят использовать локально кэшированную версию.

Последнее изменение

Вы можете использовать метод 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 и прекратит обработку.

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

Если вы хотите получить текущий код статуса, вы можете использовать метод 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

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

// 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 с Pretty Print

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

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' => 'Unauthorized'], 401);
    }

    // Продолжайте с оставшейся частью маршрута
});

До v3.10.0 вам пришлось бы сделать что-то вроде этого:

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // Проверьте, авторизован ли пользователь
    if($authorized === false) {
        Flight::halt(401, json_encode(['error' => 'Unauthorized']));
    }

    // Продолжайте с оставшейся частью маршрута
});

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 и прекратит обработку.

Скачать файл

Есть вспомогательный метод для загрузки файла. Вы можете использовать метод download и передать путь.

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

Learn/frameworkinstance

Экземпляр фреймворка

Вместо того чтобы запускать Flight как глобальный статический класс, вы можете опционально запустить его как экземпляр объекта.

require 'flight/autoload.php';

$app = Flight::app();

$app->route('/', function () {
  echo 'hello world!';
});

$app->start();

Таким образом, вместо вызова статического метода, вы вызывали бы метод экземпляра с тем же именем на объекте Engine.

Learn/redirects

Редиректы

Можно перенаправить текущий запрос, используя метод redirect и передав новый URL:

Flight::redirect('/новое/местоположение');

По умолчанию Flight отправляет статусный код HTTP 303. Можно дополнительно установить пользовательский код:

Flight::redirect('/новое/местоположение', 401);

Learn/views

Виды

Flight по умолчанию предоставляет некоторые базовые функции шаблонизации. Чтобы отобразить вид шаблона, вызовите метод render с именем файла шаблона и необязательными данными шаблона:

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

Данные шаблона, которые вы передаете, автоматически встраиваются в шаблон и могут быть обращены как локальная переменная. Файлы шаблонов - это просто файлы PHP. Если содержимое файла шаблона hello.php выглядит так:

Привет, <?= $name ?>!

То вывод будет:

Привет, Bob!

Вы также можете вручную устанавливать переменные представления с помощью метода set:

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

Переменная name теперь доступна во всех ваших представлениях. Поэтому вы просто можете сделать:

Flight::render('hello');

Обратите внимание, что при указании имени шаблона в методе render вы можете пропустить расширение .php.

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

Flight::set('flight.views.path', '/путь/к/views');

Макеты

Часто веб-сайты имеют один файл шаблона макета с переменным содержимым. Чтобы отобразить содержимое для использования в макете, вы можете передать необязательный параметр в метод render.

Flight::render('header', ['heading' => 'Привет'], 'headerContent');
Flight::render('body', ['body' => 'Мир'], 'bodyContent');

Ваше представление затем будет иметь сохраненные переменные с именами headerContent и bodyContent. Затем вы можете отобразить ваш макет так:

Flight::render('layout', ['title' => 'Домашняя страница']);

Если файлы шаблонов выглядят так:

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>Домашняя страница</title>
  </head>
  <body>
    <h1>Привет</h1>
    <div>Мир</div>
  </body>
</html>

Пользовательские представления

Flight позволяет заменить стандартный движок представлений просто зарегистрировав свой собственный класс представлений. Вот как вы можете использовать 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);
});

Learn/templates

HTML Виды и Шаблоны

Flight предоставляет некоторые базовые возможности шаблонизации по умолчанию.

Если вам нужны более сложные потребности в шаблонизации, обратитесь к примерам Smarty и Latte в разделе Пользовательские Виды.

По умолчанию движок представления

Чтобы отобразить представление шаблона, вызовите метод render с именем файла шаблона и необязательными данными шаблона:

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

Данные шаблона, которые вы передаете, автоматически вставляются в шаблон и могут быть использованы как локальная переменная. Файлы шаблонов представляют собой просто файлы PHP. Если содержимое файла шаблона hello.php таково:

Привет, <?= $name ?>!

Вывод будет:

Привет, Bob!

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

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

Переменная name теперь доступна во всех ваших представлениях. Поэтому вы можете просто сделать:

Flight::render('hello');

Обратите внимание, что при указании имени шаблона в методе render вы можете опустить расширение .php.

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

Flight::set('flight.views.path', '/путь/к/шаблонам');

Макеты

Часто в веб-сайтах есть единственный файл шаблона макета с изменяющимися содержимым. Чтобы отобразить содержимое, которое будет использоваться в макете, вы можете передать необязательный параметр в метод render.

Flight::render('header', ['heading' => 'Привет'], 'headerContent');
Flight::render('body', ['body' => 'Мир'], 'bodyContent');

У вас в представлении будут сохранены переменные с именами headerContent и bodyContent. Затем вы можете отобразить свой макет, сделав так:

Flight::render('layout', ['title' => 'Домашняя страница']);

Если файлы шаблонов выглядят так:

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>Домашняя страница</title>
  </head>
  <body>
    <h1>Привет</h1>
    <div>Мир</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 и 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 - Dependency Injection Container), перейдите на страницу Контейнер внедрения зависимостей.

Сопоставление методов

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

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

// Вызов вашего пользовательского метода
Flight::hello('Боб');

Хотя возможно создавать простые пользовательские методы, рекомендуется просто создавать стандартные функции на PHP. Это обеспечивает автозаполнение в среде разработки и упрощает чтение. Эквивалент приведенного выше кода будет:

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

hello('Боб');

Это используется более часто, когда вам нужно передать переменные в ваш метод, чтобы получить ожидаемое значение. Использование метода register() вроде приведенного ниже больше подходит для передачи конфигурации, а затем вызова вашего предварительно настроенного класса.

Регистрация классов

Чтобы зарегистрировать свой собственный класс и настроить его, используйте функцию register:

// Зарегистрировать ваш класс
Flight::register('user', User::class);

// Получить экземпляр вашего класса
$user = Flight::user();

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

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

// Получить экземпляр вашего класса
// Это создаст объект с определенными параметрами
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();

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

Если вы передадите дополнительный параметр обратного вызова, он будет выполнен сразу после создания объекта класса. Это позволяет вам выполнить любые процедуры настройки для вашего нового объекта. Функция обратного вызова принимает один параметр, экземпляр нового объекта.

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

По умолчанию каждый раз, когда вы загружаете ваш класс, вы получите общий экземпляр. Чтобы получить новый экземпляр класса, просто передайте false в качестве параметра:

// Общий экземпляр класса
$shared = Flight::db();

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

Имейте в виду, что сопоставленные методы имеют приоритет над зарегистрированными классами. Если вы объявите оба с одинаковым именем, будет вызван только сопоставленный метод.

Переопределение методов фреймворка

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

Например, когда Flight не может соотнести 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/json

JSON

Flight предоставляет поддержку для отправки JSON и JSONP ответов. Чтобы отправить ответ в формате JSON, вы передаете данные, которые будут закодированы в JSON:

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

Для запросов JSONP вы можете, опционально, передать имя параметра запроса, которое вы используете для определения функции обратного вызова:

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

Таким образом, при выполнении GET-запроса с использованием ?q=my_func, вы должны получить следующий вывод:

my_func({"id":123});

Если вы не передаете имя параметра запроса, оно будет по умолчанию установлено на jsonp.

Learn/flight_vs_slim

Сравнение Flight и 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 (каждое слово с заглавной буквы, без пробелов)
// Начиная с версии 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 (каждое слово с заглавной буквы, без пробелов)
// Начиная с версии 3.7.2, вы можете использовать Pascal_Snake_Case для имен ваших классов, запустив Loader::setV2ClassLoading(false);
class MyController {

    public function index() {
        // сделать что-то
    }
}

И если вы хотите автоматически загрузить класс в каталоге utils, вы сделали бы практически то же самое:


/**
 * 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__.'/../');

Install

Установка

Загрузите файлы.

Если вы используете Composer, вы можете запустить следующую команду:

composer require flightphp/core

ИЛИ вы можете скачать файлы напрямую и извлечь их в ваш веб-каталог.

Настройка вашего веб-сервера.

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 добавьте следующее в ваше объявление сервера:

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

License

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

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

Настоящим предоставляется разрешение на бесплатное использование любому лицу, получившему копию данного программного обеспечения и сопроводительной документации (далее - "Программное обеспечение"), без ограничений, включая право использовать, копировать, изменять, объединять, публиковать, распространять, подлицензировать и/или продавать копии Программного обеспечения и разрешать лицам, которым предоставляется Программное обеспечение, сделать то же самое, при соблюдении следующих условий:

Вышеприведенное уведомление об авторском праве и это уведомление о разрешении должны быть включены во все копии или существенные части Программного обеспечения.

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ "КАК ЕСТЬ", БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОГО СОСТОЯНИЯ, ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ И НЕНАРУШЕНИЯ. НИ В КОЕМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО ОТВЕТСТВЕННОСТИ, ВЫТЕКАЮЩЕЙ ИЗ ДОГОВОРА, ДЕЛИКТА ИЛИ ИНАЧЕ, В СВЯЗИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ИЛИ ДРУГИМИ ОБРАЩЕНИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.

About

Что такое Flight?

Flight - быстрая, простая, расширяемая структура для PHP. Он довольно универсален и может использоваться для создания любого веб-приложения. Он создан с учетом простоты и написан таким образом, чтобы его легко понять и использовать.

Flight - отличная начальная структура для тех, кто нов в PHP и хочет научиться создавать веб-приложения. Это также отличная структура для опытных разработчиков, которые хотят больше контроля над своими веб-приложениями. Он разработан для легкого создания RESTful API, простого веб-приложения или сложного веб-приложения.

Быстрый старт

<?php

// если установлен с помощью композера
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 является версией по умолчанию для некоторых долгосрочных дистрибутивов Linux. Принудительное переход на PHP >8 вызвало бы много проблем для этих пользователей. Структура также поддерживает PHP >8.

Лицензия

Flight выпущен под лицензией MIT.

Awesome-plugins/php_cookie

Cookies

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

Установка

Установка проста с помощью composer.

composer require overclokk/cookie

Использование

Использование так же просто, как регистрация нового метода в классе 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 require defuse/php-encryption

Настройка

Затем вам нужно сгенерировать ключ шифрования.

vendor/bin/generate-defuse-key

Это выдаст ключ, который вам нужно будет хранить в надежном месте. Вы можете сохранить ключ в вашем файле app/config/config.php в массиве внизу файла. Хотя это не идеальное место, это хотя бы что-то.

Использование

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


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

/*
 * Set in your bootstrap or public/index.php file
 */

// Метод шифрования
Flight::map('encrypt', function($raw_data) {
    $encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
    return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});

// Метод дешифрования
Flight::map('decrypt', function($encrypted_data) {
    $encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
    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 - это константа, которая устанавливается в вашем файле запуска или в другом месте в вашем приложении
    $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 для полной документации и убедитесь, что вы посмотрите папку examples.

Awesome-plugins/permissions

FlightPHP/Права доступа

Это модуль разрешений, который можно использовать в ваших проектах, если у вас есть несколько ролей в вашем приложении, и каждая роль имеет немного разную функциональность. Этот модуль позволяет определить разрешения для каждой роли, а затем проверить, имеет ли текущий пользователь разрешение на доступ к определенной странице или выполнение определенного действия.

Нажмите сюда для репозитория на GitHub.

Установка

Запустите composer require flightphp/permissions и вы готовы к работе!

Использование

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

Базовый пример

Давайте предположим, что у вас есть функция в вашем приложении, которая проверяет, вошел ли пользователь в систему. Вы можете создать объект разрешений следующим образом:

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

// некоторый код

// затем у вас вероятно есть что-то, что говорит вам, какая текущая роль у человека
// скорее всего у вас есть что-то, откуда вы извлекаете текущую роль
// из переменной сеанса, которая определяет это
// после входа в систему у кого-то должна быть роль 'guest' или 'public'.
$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 {
            // сделать что-то еще
        }
    }
}

Внедрение зависимостей

Вы можете внедрять зависимости в замыкание, которое определяет разрешения. Это полезно, если у вас есть какой-то переключатель, идентификатор или любая другая точка данных, которую вы хотите проверить. То же самое работает для вызовов вида Class->Method, за исключением того, что аргументы определяются в методе.

Замыкания

$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/index

Удивительные плагины

Flight невероятно расширяем. Есть ряд плагинов, которые можно использовать для добавления функциональности в ваше приложение Flight. Некоторые официально поддерживаются командой Flight, а другие являются микро/лайт библиотеками, чтобы помочь вам начать.

Кэширование

Кэширование - отличный способ ускорить ваше приложение. Есть несколько библиотек кэширования, которые можно использовать с Flight.

Отладка

Отладка критически важна при разработке в локальной среде. Есть несколько плагинов, которые могут улучшить ваш опыт отладки.

Базы данных

Базы данных являются основой для большинства приложений. Это то, как вы сохраняете и извлекаете данные. Некоторые библиотеки баз данных просто обертки для написания запросов, а некоторые - полноценные ORM.

Сессии

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

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

Шаблонизация является основой любого веб-приложения с интерфейсом пользователя. Есть несколько шаблонизаторов, которые можно использовать с Flight.

Вклад

У вас есть плагин, который вы хотели бы поделиться? Отправьте запрос на добавление его в список!

Awesome-plugins/pdo_wrapper

PdoWrapper Класс помощника PDO

Flight поставляется с классом помощника для PDO. Он позволяет легко выполнять запросы к базе данных с использованием всех этих заморочек с подготовкой/выполнением/fetchAll(). Это значительно упрощает ваш способ выполнения запросов к базе данных. Каждая строка результата возвращается как класс Flight Collection, который позволяет вам получать доступ к данным с использованием синтаксиса массива или объекта.

Регистрация класса помощника 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

Используйте это для INSERTS, UPDATES или если вы планируете использовать 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 для необязательного шифрования/дешифрования данных сессии. Поддерживает File, MySQL, Redis и Memcached.

Нажмите сюда, чтобы просмотреть код.

Установка

Установите с помощью composer.

composer require ghostff/session

Базовая настройка

Вам не нужно передавать что-либо, чтобы использовать настройки по умолчанию для вашей сессии. Вы можете прочитать о других настройках в [Github Readme] (https://github.com/Ghostff/Session).


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

// важно помнить, что вам нужно подтвердить вашу сессию при каждой загрузке страницы
// или вам нужно запустить auto_commit в вашей конфигурации.

Простой пример

Вот простой пример того, как вы могли бы использовать это.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // сделайте здесь логику входа
    // проверка пароля и т. д.

    // если вход прошел успешно
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // каждый раз, когда вы записываете в сессию, вы должны подтвердить ее нарочно.
    $session->commit();
});

// Эта проверка может быть в логике ограниченной страницы или обернута с помощью промежуточного программного обеспечения.
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

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

    // сделайте здесь логику ограниченной страницы
});

// версия промежуточного программного обеспечения
Flight::route('/some-restricted-page', function() {
    // обычная логика страницы
})->addMiddleware(function() {
    $session = Flight::session();

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

Более сложный пример

Вот более сложный пример того, как вы могли бы использовать это.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// установите пользовательский путь к файлу конфигурации вашей сессии и укажите случайную строку для идентификатора сессии
$app->register('session', Session::class, [ 'путь/к/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, например (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, [ 'путь/к/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 [команда] из корня вашего проекта.
  2. Если вы используете Взлетную полосу как пакет, установленный через composer, вы можете запустить vendor/bin/runway [команда] из корня вашего проекта.

Для любой команды вы можете передать флаг --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;

/**
 * Класс Active Record для таблицы пользователей.
 * @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

И каждая панель отображает очень полезную информацию о вашем приложении!

Данные Flight База данных Flight Запрос Flight

Нажмите здесь, чтобы просмотреть код.

Установка

Запустите composer require flightphp/tracy-extensions --dev и вы на правильном пути!

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

Вам нужно сделать очень небольшую настройку, чтобы начать использовать это. Вам нужно инициировать отладчик 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

Трейси - удивительный обработчик ошибок, который можно использовать с Flight. У него есть ряд панелей, которые могут помочь вам отлаживать ваше приложение. Он также очень легок в расширении и добавлении собственных панелей. Команда Flight создала несколько панелей специально для проектов Flight с плагином flightphp/tracy-extensions.

Установка

Установите с помощью composer. И вам действительно захочется установить это без версии для разработчиков, так как у Трейси есть компонент обработки ошибок для продакшена.

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); // если панель отладки видима, тогда длина содержимого не может быть установлена Flight

    // Это специфично для Расширения Трейси для Flight, если вы его включили
    // в противном случае закомментируйте это.
    new TracyExtensionLoader($app);
}

Полезные советы

Когда вы отлаживаете свой код, есть несколько очень полезных функций для вывода данных для вас.

Awesome-plugins/active_record

Активная запись Flight

Активная запись - это отображение сущности базы данных на объект 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 = 'Бобби Тейблз';
$user->password = password_hash('какой-то крутой пароль');
$user->insert();
// или $user->save();

echo $user->id; // 1

$user->name = 'Джозеф Мамма';
$user->password = password_hash('крутой пароль снова!!!');
$user->insert();
// здесь нельзя использовать $user->save(), иначе он подумает, что это обновление!

echo $user->id; // 2

И это было настолько легко добавить нового пользователя! Теперь, когда есть строка пользователя в базе данных, как ее извлечь?

$user->find(1); // найдите id = 1 в базе данных и верните его.
echo $user->name; // 'Бобби Тейблз'

Что, если вы хотите найти всех пользователей?

$users = $user->findAll();

А что насчет определенного условия?

$users = $user->like('name', '%мамма%')->findAll();

Вот как это весело! Установим его и начнем!

Установка

Просто установите с помощью Composer

composer require flightphp/active-record 

Использование

Эту библиотеку можно использовать как автономно, так и с фреймворком Flight PHP. Полностью на ваше усмотрение.

Автономно

Убедитесь, что вы передаете соединение PDO в конструктор.

$pdo_connection = new PDO('sqlite:test.db'); // это просто пример, обычно вы бы использовали реальное соединение с базой данных

$User = new User($pdo_connection);

Вы не хотите всегда устанавливать соединение с базой данных в конструкторе? См. Управление соединением с базой данных для других идей!

Регистрация как метод в Flight

Если вы используете фреймворк Flight PHP, вы можете зарегистрировать класс ActiveRecord как службу, но честно говоря, это не обязательно.

Flight::register('user', 'User', [ $pdo_connection ]);

// затем вы можете использовать его так в контроллере, функции и т. д.

Flight::user()->find(1);

Методы runway

runway - это инструмент командной строки для Flight, который имеет пользовательскую команду для этой библиотеки.

# Использование
php runway make:record имя_таблицы_базы_данных [имя_класса]

# Пример
php runway make:record users

Это создаст новый класс в каталоге app/records/ под названием UserRecord.php со следующим содержанием:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Класс ActiveRecord для таблицы пользователей.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 *
 * @property int $id
 * @property string $username
 * @property string $email
 * @property string $password_hash
 * @property string $created_dt
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Установите отношения для модели
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [
        // 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
    ];

    /**
     * Конструктор
     * @param mixed $databaseConnection Соединение с базой данных
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

Функции CRUD

find($id = null) : boolean|ActiveRecord

Находит одну запись и присваивает ее текущему объекту. Если вы передаете $id, он выполнит поиск по первичному ключу с этим значением. Если ничего не передается, он просто найдет первую запись в таблице.

Кроме того, вы можете передавать другие вспомогательные методы для запроса вашей таблицы.

// найти запись с некоторыми условиями заранее
$user->notNull('password')->orderBy('id DESC')->find();

// найти запись по конкретному id
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

Находит все записи в указанной таблице.

$user->findAll();

isHydrated(): boolean (v0.4.0)

Возвращает true, если текущая запись была считана с базы данных.

$user->find(1);
// если запись найдена с данными...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

Вставляет текущую запись в базу данных.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Первичные ключи на основе текста

Если у вас есть первичный ключ на основе текста (например, UUID), вы можете установить значение первичного ключа перед вставкой одним из двух способов.

$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // или $user->save();

или вы можете автоматически сгенерировать первичный ключ через события.

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // вы также можете установить primaryKey таким образом, а не в массиве выше.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // или как иначе нужно создавать уникальные идентификаторы
    }
}

Если вы не установите первичный ключ перед вставкой, он будет установлен на rowid, и база данных сгенерирует его для вас, но он не сохранится, потому что это поле может отсутствовать в вашей таблице. Поэтому рекомендуется использовать событие для автоматической обработки этого для вас.

update(): boolean|ActiveRecord

Обновляет текущую запись в базе данных.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

Вставляет или обновляет текущую запись в базе данных. Если у записи есть идентификатор, он будет обновлен, в противном случае он будет вставлен.

$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-инъекций. В интернете есть много статей, пожалуйста, загуглите "sql injection attacks php", и вы найдете много статей по этой теме. Правильный способ обработки этого с использованием этой библиотеки заключается в том, что вместо этого метода where() вы бы сделали что-то вроде $user->eq('id', $id)->eq('name', $name)->find();. Если вам действительно нужно это сделать, библиотека PDO имеет $pdo->quote($var), чтобы экранировать его для вас. Только после использования quote() вы можете использовать его в операторе where().

group(string $group_by_statement)/groupBy(string $group_by_statement)

Группируйте ваши результаты по определенному условию.

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

Сортируйте возвращенный запрос определенным способом.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Ограничивают количество возвращаемых записей. Если передан второй int, это смещение, ограничение также как в SQL.

$user->orderby('name DESC')->limit(0, 10)->findAll();

Условия WHERE

equal(string $field, mixed $value) / eq(string $field, mixed $value)

Где field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

Где `self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO

CppI"родитель"hasMany`表示一对多关系。`self::HAS_ONE`表示一对一关系。`self::BELONGS_TO`表示从属关系。

Awesome-plugins/latte

Latte

Latte - это полнофункциональный движок шаблонов, который очень прост в использовании и ближе к синтаксису PHP, чем Twig или Smarty. Также очень легко расширяем и добавляем собственные фильтры и функции.

Установка

Установите с помощью composer.

composer require latte/latte

Основная конфигурация

Есть несколько основных опций конфигурации, с которых можно начать. Вы можете узнать больше о них в Документации по Latte.

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 для получения дополнительной информации о том, как использовать Latte в полной мере!

Awesome-plugins/awesome_plugins

Плагины Flight

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

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

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

Кэширование

Кэширование - отличный способ ускорить ваше приложение. Существует ряд библиотек кэширования, которые можно использовать с Flight.

CLI

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

Cookies

Cookies - отличный способ хранения небольших фрагментов данных на стороне клиента. Их можно использовать для хранения пользовательских предпочтений, настроек приложения и многое другое.

Отладка

Отладка крайне важна при разработке в вашей локальной среде. Существует несколько плагинов, которые могут улучшить ваш опыт отладки.

Базы данных

Базы данных - это основа большинства приложений. Это то, как вы храните и извлекаете данные. Некоторые библиотеки баз данных просто обертки для написания запросов, а некоторые - полноценные ORM.

Шифрование

Шифрование крайне важно для любого приложения, которое хранит конфиденциальные данные. Шифрование и дешифрование данных не так уж и сложно, но правильное хранение ключа шифрования может быть сложной задачей. Самое важное - никогда не храните ключ шифрования в общедоступном каталоге или не сохраняйте его в репозитории вашего кода.

Сессия

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

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

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

Вклад

Есть плагин, который вы хотели бы поделиться? Представьте запрос на включение его в список!

Media

Медиа

Мы постарались собрать все, что можем, о различных типах медиа в интернете, связанных с Flight. См. ниже различные ресурсы, которые вы можете использовать, чтобы узнать больше о Flight.

Статьи

Видео

Examples

Нужен быстрый старт?

У вас есть два варианта, чтобы начать работу с Flight:

Нужна вдохновение?

Хотя они не являются официально поддерживаемыми командой Flight, они могут дать вам идеи о том, как структурировать ваши собственные проекты, построенные с помощью Flight!

Хотите поделиться своим примером?

Если у вас есть проект, которым вы хотите поделиться, пожалуйста, отправьте запрос на добавление его в этот список!

Install/install

Установка

Загрузка файлов

Убедитесь, что у вас установлен PHP на вашей системе. Если нет, нажмите здесь для получения инструкций по установке на вашей системе.

Если вы используете Composer, вы можете выполнить следующую команду:

composer require flightphp/core

ИЛИ вы можете скачать файлы напрямую и извлечь их в ваш каталог веб-сайта.

Настройка вашего веб-сервера

Встроенный веб-сервер PHP

Это, безусловно, самый простой способ запустить приложение и использовать даже SQLite для базы данных. Просто выполните следующую команду после установки PHP:

php -S localhost:8000

Затем откройте свой браузер и перейдите по адресу http://localhost:8000.

Если вы хотите изменить корневой каталог вашего проекта на другой каталог (например, ваш проект - ~/myproject, а корневой каталог -~/myproject/public/), вы можете выполнить следующую команду после того, как вы находитесь в каталоге ~/myproject:

php -S localhost:8000 -t public/

Затем откройте свой браузер и перейдите по адресу http://localhost:8000.

Apache

Убедитесь, что Apache уже установлен на вашей системе. Если нет, найдите, как установить Apache на вашей системе через поиск в Google.

Для 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 уже установлен на вашей системе. Если нет, найдите, как установить Nginx на вашей системе через поиск в Google.

Для 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 (если его нет):

    • Откройте терминал и выполните:
      /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 и нажмите Изменить.
    • Добавьте путь C:\php (или куда бы вы не разархивировали PHP).
    • Нажмите OK, чтобы закрыть все окна.
  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. Обновите списки пакетов:

    • Откройте терминал и выполните:
      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:

    • Откройте терминал и выполните:
      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 module:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Проверьте установленную версию:

    • Выполните:
      php -v

Общие заметки