Learn

Узнайте о Flight

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

Основные понятия платформы

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

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

Кроме того, отличное руководство было создано @lubiana. Хотя в нем не углубляется в 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/migrating_to_v3

Миграция на v3

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

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

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

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

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

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

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

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

function hello() {
    echo 'Hello World';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // это на самом деле будет нормально
    echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});

Flight::before('start', function(){
    // подобные вещи вызовут ошибку
    echo '<html><head><title>My Page</title></head><body>';
});

Flight::route('/', function(){
    // это на самом деле в порядке
    echo 'Hello World';

    // Это тоже должно быть в порядке
    Flight::hello();
});

Flight::after('start', function(){
    // это вызовет ошибку
    echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</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>My Page</title></head><body>';
});

// дополнительный код 

Изменения в диспетчере (3.7.0)

Если вы напрямую вызывали статические методы для Dispatcher, такие как Dispatcher::invokeMethod(), Dispatcher::execute(), и т. д., вам нужно будет обновить свой код, чтобы не вызывать эти методы напрямую. Dispatcher был преобразован в более объектно-ориентированный для упрощения использования контейнеров внедрения зависимостей. Если вам нужно вызвать метод аналогичный работе Dispatcher, вы можете вручную использовать что-то вроде $result = $class->$method(...$params); или call_user_func_array() вместо этого.

Learn/configuration

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

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

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

Доступные настройки конфигурации

Ниже приведен список всех доступных настроек конфигурации:

Переменные

Flight позволяет вам сохранять переменные, чтобы они могли использоваться в любом месте вашего приложения.

// Сохраните вашу переменную
Flight::set('id', 123);

// В другом месте вашего приложения
$id = Flight::get('id');

Чтобы узнать, установлена ли переменная, вы можете сделать следующее:

if (Flight::has('id')) {
  // Сделать что-то
}

Вы можете очистить переменную, выполнив:

// Очищает переменную id
Flight::clear('id');

// Очистить все переменные
Flight::clear();

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

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

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

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

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

Вы можете переопределить это поведение по своему усмотрению:

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

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

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

Не найдено

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

Вы можете переопределить это поведение по своему усмотрению:

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

Learn/security

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

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

Заголовки

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

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

Добавить вручную

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

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

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

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

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

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

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

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

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

Добавить как фильтр

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

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

Добавить как промежуточный слой

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

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

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

// index.php or wherever you have your routes
// FYI, this empty string group acts as a global middleware for
// all routes. Of course you could do the same thing and just add
// this only to specific routes.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // more routes
}, [ new SecurityHeadersMiddleware() ]);

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

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

Настройка

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

// Генерация токена CSRF и сохранение его в сессии пользователя
// (предполагая, что вы создали объект сессии и прикрепили его к Flight)
// Вам нужно сгенерировать только один токен на сессию (чтобы он работал 
// на разных вкладках и запросах для одного и того же пользователя)
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');
        }
    }
});

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

// 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 or wherever you have your routes
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // more routes
}, [ 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 or wherever you have your routes
$CorsUtil = new CorsUtil();
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Заключение

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

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

Внедрение зависимостей через 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
});

Примечание: Сопоставление групп регулярных выражений () с именованными параметрами не поддерживается. :'(

Опциональные параметры

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

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // Это совпадет с следующими URL:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

Любые несопоставленные необязательные параметры будут переданы как NULL.

Маски

Сопоставление происходит только на отдельных сегментах URL. Если вы хотите сопоставить несколько сегментов, вы можете использовать символ *.

Flight::route('/blog/*', function () {
  // Это совпадет с /blog/2000/02/01
});

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

Flight::route('*', function () {
  // Сделать что-то
});

Переход

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

Flight::route('/user/@name', function (string $name) {
  // Проверить какое-то условие
  if ($name !== "Иван") {
    // Продолжить к следующему маршруту
    return true;
  }
});

Flight::route('/user/*', function () {
  // Этот код будет выполнен
});

Псевдоним маршрута

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

Flight::route('/users/@id', function($id) { echo 'пользователь:'.$id; }, false, 'user_view');

// позже в коде где-то
Flight::getUrl('user_view', [ 'id' => 5 ]); // вернет '/users/5'

Это особенно полезно, если ваш URL случайно изменится. В приведенном выше примере, предположим, что пользователи были перенесены в /admin/users/@id вместо этого. С использованием псевдонимов вы не должны изменять все ссылки на псевдоним, потому что теперь псевдоним вернет /admin/users/5, как в приведенном выше примере.

Псевдоним маршрута все еще работает в группах:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'пользователь:'.$id; }, false, 'user_view');
});

// позже в коде где-то
Flight::getUrl('user_view', [ 'id' => 5 ]); // вернет '/users/5'

Информация о маршруте

Если вы хотите проверить информацию о сопоставленном маршруте, вы можете запросить объект маршрута быть переданным в вашу функцию обратного вызова, передав true как третий параметр в метод маршрута. Объект маршрута всегда будет последним параметром, переданным в вашу функцию обратного вызова.

Flight::route('/', function(\flight\net\Route $route) {
  // Массив сопоставленных HTTP методов
  $route->methods;

  // Массив именованных параметров
  $route->params;

  // Сопоставленное регулярное выражение
  $route->regex;

  // Содержит содержимое любых '*' использованных в шаблоне URL
  $route->splat;

  // Показывает путь URL, если вам действительно это нужно
  $route->pattern;

  // Показывает, какое промежуточное ПО назначено этому
  $route->middleware;

  // Показывает назначенное псевдоним маршруту
  $route->alias;
}, true);

Группировка маршрутов

Иногда вам может понадобиться группировать связанные маршруты вместе (например, /api/v1). Вы можете сделать это с помощью метода group:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Сопоставляется с /api/v1/users
  });

  Flight::route('/posts', function () {
    // Совпадает с /api/v1/posts
  });
});

Вы можете даже вкладывать группы групп:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get() получает переменные, это не метод для установки маршрута! См. объект контекста ниже
    Flight::route('GET /users', function () {
      // Совпадается с GET /api/v1/users
    });

    Flight::post('/posts', function () {
      // Совпадает с POST /api/v1/posts
    });

    Flight::put('/posts/1', function () {
      // Сопоставляется с PUT /api/v1/posts
    });
  });
  Flight::group('/v2', function () {

    // Flight::get() получает переменные, это не метод для установки маршрута! См. объект контекста ниже
    Flight::route('GET /users', function () {
      // Сопоставляется с GET /api/v2/users
    });
  });
});

Группировка с объектным контекстом

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

$app = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {

  // используйте переменную $router
  $router->get('/users', function () {
    // Сопоставляется с GET /api/v1/users
  });

  $router->post('/posts', function () {
    // Сопоставляется с POST /api/v1/posts
  });
});

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

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

Примечание: Передача ответов доступна только в том случае, если установлен параметр flight.v2.output_buffering в false.

Поток с ручными заголовками

Вы можете передавать ответ клиенту, используя метод stream() на маршруте. Если вы делаете это, вам придется установить все методы вручную, прежде чем выведете что-либо клиенту. Это делается с помощью функции header() языка PHP или метода Flight::response()->setRealHeader().

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

    // очевидно, вы бы очистили путь и т. д.
    $fileNameSafe = basename($filename);

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

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

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

    // вручную установить длину контента, если хотите
    header('Content-Length: '.filesize($filename));

    // Передаем данные клиенту
    echo $fileData;

// Это вол```markdown
## Маршрутизация

> **Примечание:** Хотите понять больше о маршрутизации? Посмотрите страницу ["почему фреймворк?"](/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 = 'John Doe';
    }

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

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

Flight::route('/', [ $greeting, 'hello' ]);
// Вы также можете сделать это без создания объекта сначала
// Примечание: Никакие аргументы не будут вставлены в конструктор
Flight::route('/', [ 'Greeting', 'hello' ]);

Внедрение зависимостей через DIC (Dependency Injection Container)

Если вы хотите использовать внедрение зависимостей через контейнер (PSR-11, PHP-DI, Dice и т. д.), единственный тип маршрутов, где это доступно, - это либо прямое создание объекта самостоятельно и использование контейнера для создания вашего объекта, либо вы можете использовать строки для определения класса и метода для вызова. Посетите страницу внедрения зависимостей для получения дополнительной информации.

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


use flight\database\PdoWrapper;

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

    public function hello(int $id) {
        // сделать что-то с $this->pdoWrapper
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "Привет, мир! Меня зовут {$name}!";
    }
}

// index.php

// Настройте контейнер с необходимыми параметрами
// См. страницу Внедрения зависимостей для получения дополнительной информации о PSR-11
$dice = new \Dice\Dice();

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

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

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

Flight::start();

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

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

Flight::route('GET /', function () {
  echo 'Я получил GET запрос.';
});

Flight::route('POST /', function () {
  echo 'Я получил POST запрос.';
});

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

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

Flight::route('GET|POST /', function () {
  echo 'Я получил GET или POST запрос.';
});

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


$router = 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
});

Примечание: Сопоставление групп регулярных выражений () с именованными параметрами не поддерживается. :'(

Опциональные параметры

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

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // Это будет соответствовать следующим URL:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

Любые несопоставленные необязательные параметры будут переданы как NULL.

Шаблоны

Сопоставление происходит только на отдельных сегментах URL. Если вы хотите сопоставить несколько сегментов, вы можете использовать шаблон *.

Flight::route('/blog/*', function () {
  // Это будет соответствовать /blog/2000/02/01
});

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

Flight::route('*', function () {
  // Сделать что-то
});

Передача

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

Flight::route('/user/@name', function (string $name) {
  // Проверить какое-то условие
  if ($name !== "Bob") {
    // Продолжить к следующему маршруту
    return true;
  }
});

Flight::route('/user/*', function () {
  // Это будет вызвано
});

Псевдоним маршрута

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

Flight::route('/users/@id', function($id) { echo '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('Content-Length: '.filesize($filename));

    // Передаем данные клиенту
    echo $fileData;

// Это волшебная строка
})->stream();

Поток с заголовками

Вы также можете использовать метод streamWithHeaders() для установановки заголовков перед началом передачи потока.

Flight::route('/stream-users', function() {

    // вы можете добавить любые дополнительные заголовки здесь
    // вы должны использовать функцию header() или метод Flight::response()->setRealHeader()

    // как бы вы добывали свои данные, просто для примера...
    $users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");

    echo '{';
    $user_count = count($users);
    while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
        echo json_encode($user);
        if(--$user_count > 0) {
            echo ',';
        }

        // Это необходимо для отправки данных клиенту
        ob_flush();
    }
    echo '}';

// Вот как вы установите заголовки перед началом передачи потока.
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // необязательный код состояния, по умолчанию 200
    'status' => 200
]);

Learn/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 МоеПромежуточное {
    public function before($params) {
        echo 'Промежуточное программное обеспечение вначале!';
    }

    public function after($params) {
        echo 'Промежуточное программное обеспечение вконце!';
    }
}

$MyMiddleware = new МоеПромежуточное();
Flight::route('/path', function() { echo 'Здесь я! '; })->addMiddleware($MyMiddleware); // также ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// Это отобразит "Промежуточное программное обеспечение вначале! Здесь я! Промежуточное программное обеспечение вконце!"

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

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

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

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

Вот простой пример с return false;:

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

        // поскольку это true, все продолжается
    }
}

Пример перенаправления

Вот пример перенаправления пользователя на страницу входа:

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::json(['error' => 'Для доступа к этой странице необходимо войти в систему.'], 403);
            exit;
            // или
            Flight::halt(403, json_encode(['error' => 'Для доступа к этой странице необходимо войти в систему.']);
        }
    }
}

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

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


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

    // Этот "пустой" маршрут на самом деле будет соответствовать /api
    Flight::route('', function() { echo 'api'; }, false, 'api');
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Если вы хотите применить глобальное промежуточное программное обеспечение ко всем своим маршрутам, вы можете добавить "пустую" группу:


// добавлено в конец метода группы
Flight::group('', function() {
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Learn/filtering

Фильтрация

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

Функция фильтра выглядит следующим образом:

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

Используя передаваемые переменные, вы можете изменять входные параметры и/или вывод.

Вы можете запустить фильтр перед вызовом метода следующим образом:

Flight::before('start', function (array &$params, string &$output): bool {
  // Сделать что-то
});

Вы можете запустить фильтр после вызова метода следующим образом:

Flight::after('start', function (array &$params, string &$output): bool {
  // Сделать что-то
});

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

Вот пример процесса фильтрации:

// Объявляем пользовательский метод
Flight::map('hello', function (string $name) {
  return "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();

Объект запроса предоставляет следующие свойства:

Вы можете получить доступ к свойствам 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;

Доступ к $_SERVER

Доступен быстрый способ получения к массиву $_SERVER через метод getVar():


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

Доступ к заголовкам запроса

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


// Возможно, вам понадобится заголовок Authorization
$host = Flight::request()->getHeader('Authorization');

// Если вам нужно получить все заголовки
$headers = Flight::request()->getHeaders();

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

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

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

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

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 "Запрещено";
    }
});

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

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

Запуск обратного вызова на теле ответа

Вы можете запустить обратный вызов на теле ответа, используя метод addResponseBodyCallback:

Flight::route('/пользователи', 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('/пользователи', 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() {
        Flight::response()->addResponseBodyCallback(function($body) {
            // Это 
            return $this->minify($body);
        });
    }

    protected function minify(string $body): string {
        // минимизировать тело
        return $body;
    }
}

// index.php
Flight::group('/пользователи', function() {
    Flight::route('', function() { /* ... */ });
    Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);

Установка заголовка ответа

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


// Это отправит "Привет, мир!" в браузер пользователя в виде обычного текста
Flight::route('/', function() {
    Flight::response()->header('Content-Type', 'text/plain');
    echo "Привет, мир!";
});

JSON

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

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

JSON с кодом состояния

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

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

JSON с красивым выводом

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

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

Если вы изменяете параметры, передаваемые в Flight::json() и хотите более простый синтаксис, вы можете просто переназначить метод JSON:

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

// Теперь его можно использовать так
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON и остановка выполнения

Если вы хотите отправить JSON-ответ и прекратить выполнение, вы можете использовать метод jsonHalt. Это полезно для случаев, когда вы проверяете, возможно, какой-то вид авторизации и если пользователь не авторизован, вы можете немедленно отправить JSON-ответ, очистить текущее тело содержимого и прекратить выполнение.

Flight::route('/пользователи', function() {
    $authorized = someAuthorizationCheck();
    // Проверяем, авторизован ли пользователь
    if($authorized === false) {
        Flight::jsonHalt(['error' => 'Недопустимо'], 401);
    }

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

JSONP

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

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

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

my_func({"id":123});

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

Перенаправление на другой URL

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

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

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

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

Остановка

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

Flight::halt();

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

Flight::halt(200, 'Скоро вернусь...');

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

Flight::stop();

HTTP-кэширование

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

Кэширование на уровне маршрута

Если вы хотите закэшировать весь ваш ответ, вы можете использовать метод cache() и передать время кэширования.


// Это закэширует ответ на 5 минут
Flight::route('/новости', function () {
  Flight::response()->cache(time() + 300);
  echo 'Это содержимое будет закэшировано.';
});

// Кроме того, вы можете использовать строку, которую вы бы передали
// методу strtotime()
Flight::route('/новости', function () {
  Flight::response()->cache('+5 минут');
  echo 'Это содержимое будет закэшировано.';
});

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

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

Flight::route('/новости', function () {
  Flight::lastModified(1234567890);
  echo 'Это содержание будет закэшировано.';
});

ETag

Кэширование ETag аналогично Last-Modified, за исключением того, что вы можете указать любой id для ресурса:

Flight::route('/новости', function () {
  Flight::etag('my-unique-id');
  echo 'Это содержание будет закэшировано.';
});

Имейте в виду, что вызов метода lastModified или etag и установка и проверка кэш-значения. Если кэш-значение совпадает между запросами, Flight немедленно отправит ответ HTTP 304 и прекратит обработку.

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

Представления

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

// Назначить данные шаблона
Flight::view()->assign('name', 'Bob');

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

Для завершенности следует также переопределить метод отображения по умолчанию в 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/extending

Расширение

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

Если вам нужен DIC (контейнер внедрения зависимостей), перейдите на страницу Контейнер внедрения зависимостей.

Отображение Методов

Для отображения вашего собственного простого пользовательского метода используйте функцию map:

// Отображение вашего метода
Flight::map('hello', function (string $name) {
  echo "hello $name!";
});

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

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

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

Для регистрации вашего собственного класса и его конфигурации используйте функцию register:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Например, когда Flight не может соответствовать URL маршруту, он вызывает метод notFound, который отправляет общий ответ с кодом HTTP 404. Вы можете переопределить это поведение, используя метод map:

Flight::map('notFound', function() {
  // Показать пользовательскую страницу 404
  include 'errors/404.html';
});

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

// Регистрация вашего пользовательского класса
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/autoloading

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

Автозагрузка - это концепция в PHP, где вы указываете каталог или каталоги для загрузки классов. Это намного более выгодно, чем использование require или include для загрузки классов. Это также требуется для использования пакетов Composer.

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

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

Допустим, у нас есть древо каталогов следующего вида:

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

Вы, возможно, заметили, что это та же структура файлов, что и у этого сайта документации.

Вы можете указать каждый каталог для загрузки так:


/**
 * public/index.php
 */

// Добавление пути к автозагрузчику
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

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

// нет необходимости в пространствах имен

// Рекомендуется, чтобы все автозагружаемые классы были в Pascal Case (каждое слово с заглавной буквы, без пробелов)
// Начиная с версии 3.7.2, вы можете использовать Pascal_Snake_Case для названий ваших классов, запустив Loader::setV2ClassLoading(false);
class MyController {

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

Пространства имен

Если у вас есть пространства имен, на самом деле становится очень легко это реализовать. Вы должны использовать метод Flight::path() для указания корневого каталога (не корневого документа или папки public/) вашего приложения.


/**
 * public/index.php
 */

// Добавление пути к автозагрузчику
Flight::path(__DIR__.'/../');

Теперь это может выглядеть ваш контроллер. Обратите внимание на пример ниже, но обратите внимание на комментарии для важной информации.

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

// пространства имен обязательны
// пространства имен совпадают со структурой каталогов
// пространства имен должны следовать тому же регистру, что и структура каталога
// пространства имен и каталоги не могут содержать нижние подчеркивания (если не установить Loader::setV2ClassLoading(false))
namespace app\controllers;

// Рекомендуется, чтобы все автозагружаемые классы были в Pascal Case (каждое слово с заглавной буквы, без пробелов)
// Начиная с версии 3.7.2, вы можете использовать Pascal_Snake_Case для названий ваших классов, запустив Loader::setV2ClassLoading(false);
class MyController {

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

И если вы хотите автоматически загрузить класс из вашего каталога 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 'Привет, Мир';
});

// Что вам скорее всего нужно
Flight::route('/hello', function(){
    echo 'Привет, Мир';
});

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

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

Содержание лицензии МТИ (МТИ)

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

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

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

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

About

Что такое Flight?

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

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

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

<?php

// если установлен с помощью composer
require 'vendor/autoload.php';
// или, если установлен вручную из zip-файла
// require 'flight/Flight.php';

Flight::route('/', function() {
  echo 'Привет, мир!';
});

Flight::route('/json', function() {
  Flight::json(['hello' => 'world']);
});

Flight::start();

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

Пример приложения "Skeleton/Boilerplate"

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

Сообщество

Мы находимся в Matrix! Общайтесь с нами в чате #flight-php-framework:matrix.org.

Вклад

Есть два способа внести свой вклад в Flight:

  1. Вы можете внести вклад в основной фреймворк, посетив core repository.
  2. Вы можете внести вклад в документацию. Этот веб-сайт документации размещен на Github. Если вы заметили ошибку или хотите улучшить что-то, не стесняйтесь исправить и отправить запрос на включение изменений! Мы стараемся быть в курсе происходящего, но обновления и переводы на другие языки приветствуются.

Требования

Flight требует PHP 7.4 или выше.

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

Лицензия

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

Awesome-plugins/php_cookie

Cookies

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

Установка

Установка проста с помощью 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/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, сегмент, шифрование сессий). Использует open_ssl PHP для необязательного шифрования/дешифрования данных сессии. Поддерживает File, MySQL, Redis и Memcached.

Установка

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

composer require ghostff/session

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

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


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

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

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

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

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

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

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

    // в любое время, когда вы пишете в сессию, вы должны явно фиксировать ее.
    $session->commit();
});

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

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

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

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

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

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

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


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// установите пользовательский путь к файлу конфигурации сеанса и дайте ему случайную строку для идентификатора сеанса
$app->register('session', Session::class, [ 'путь/к/файлу_конфигурации_сессии.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', 'моя-супер-секретная-соль'), // пожалуйста, поменяйте это на что-то другое
            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'   => 'моя_база_данных_приложения',   # Имя базы данных
                'db_table'  => 'сессии',          # Таблица базы данных
                '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, [ 'путь/к/файлу_конфигурации_сессии.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 модель Active Record будет сгенерирована для вас в каталоге 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
 */
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>', 'Имя смешного 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 немного более насыщенной.

Это Панель

Панель 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 framework
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 в вашем коде. Смотрите простой пример.

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

Допустим, у вас есть следующая таблица:

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', '%mamma%')->findAll();

Посмотрите, как это весело? Давайте установим его и начнем!

Установка

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

composer require flightphp/active-record 

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

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

Как самостоятельно

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

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

$User = new User($pdo_connection);

Фреймворк PHP Flight

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

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

// затем вы можете использовать это в контроллере, функции и т. д.
Flight::user()->find(1);

Функции 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 = 'демо';
$user->password = md5('демо');
$user->insert();
Первичные ключи на основе текста

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

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

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

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

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

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

update(): boolean|ActiveRecord

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

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

save(): boolean|ActiveRecord

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

$user = new User($pdo_connection);
$user->name = 'демо';
$user->password = md5('демо');
$user->save();

Примечание: Если в классе определены отношения, он рекурсивно сохранит эти отношения, если они были определены, созданы и есть данные для обновления. (v0.4.0 и выше)

delete(): boolean

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

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

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

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

Грязные данные относятся к данным, которые были изменены в записи.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// на данный момент нет "грязных" данных.

$user->email = 'test@example.com'; // теперь email считается "грязным", поскольку он изменился.
$user->update();
// теперь нет "грязных" данных, потому что они были обновлены и сохранены в базе данных

$user->password = password_hash()'newpassword'); // теперь это грязно
$user->dirty(); // передача ничего не очистит все грязные записи.
$user->update(); // ничего не обновит, потому что ни одна запись не была зафиксирована как грязная.

$user->dirty([ 'name' => 'нечто', 'password' => password_hash('другой пароль') ]);
$user->update(); // и имя, и пароль обновлены.

copyFrom(array $data): ActiveRecord (v0.4.0)

Это псевдоним для метода dirty(). Он немного яснее, что вы делаете.

$user->copyFrom([ 'name' => 'нечто', 'password' => password_hash('другой пароль') ]);
$user->update(); // и имя, и пароль обновлены.

isDirty(): boolean (v0.4.0)

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

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@mail.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

Сбрасывает текущую запись в начальное состояние. Это действительно хорошо использовать в циклических поведениях. Если вы передадите true, это также сбросит данные запроса, которые были использованы для нахождения текущего объекта (поведение по умолчанию).

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // начать с чистого листа
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

После выполнения метода find(), findAll(), insert(), update() или save() вы можете получить SQL, который был построен, и использовать его для отладки.

Методы запросов SQL

select(string $field1 [, string $field2 ... ])

Вы можете выбрать только несколько столбцов в таблице, если пожелаете (это более производительно на чрезмерно широких таблицах с многими столбцами).

$user->select('id', 'name')->find();

from(string $table)

Вы технически можете выбирать другую таблицу тоже! Почему бы и нет?!

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

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

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

Вы можете установить некоторые пользовательские условия where (в этом операторе where нельзя устанавливать параметры).

$user->where('id=1 AND name="demo"')->find();

Примечание по безопасности - Вас может подстерегать идея что-то типа $user->where("id = '{$id}' AND name = '{$name}'")->find();. ПОЖАЛУЙСТА, НЕ ДЕЛАЙТЕ ЭТО! Это подвержено так называемым атакам инъекций SQL. В Интернете есть много статей, пожалуйста, найдите в Google "атаки инъекций SQL php" и вы найдете много статей на эту тему. Правильный способ обработки этого с помощью этой библиотеки состоит в том, чтобы вместо метода where() вы делали что-то вроде $user->eq('id', $id)->eq('name', $name)->find(); Если вам действительно нужно это сделать, в библиотеке PDO есть $pdo->quote($var), чтобы заэкранировать его. Только после использования quote() можно использовать в выражении where().

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

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

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

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

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

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

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

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

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

Условия WHERE

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

Где field = $value

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

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

Где field <> $value

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

isNull(string $field)

Где field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

Где field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

Где field > $value

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

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

Где field < $value

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

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

Где field >= $value

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

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

Где field <= $value

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

like(string $field, mixed $value) / notLike(string $field, mixed $value)

Где field LIKE $value или field NOT LIKE $value

$user->like('name', 'де')->find();

in(string $field, array $values) / notIn(string $field, array $values)

Где field IN($value) или field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

Где field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

Отношения

Вы можете устанавливать несколько видов отношений с помощью этой библиотеки. Вы можете установить отношения один-ко-многим и один-к-одному между таблицами. Для этого немного предварительной настройки в классе.

Установка массива $relations не сложна, но догадаться о правильном синтаксисе может быть запутывающим.

protected array $relations = [
    // вы можете назвать ключ как угодно. Хорошее название ActiveRecord
    'user' => [
        // обязательно
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // это тип отношения

        // обязательно
        'Some_Class', // это класс другой ActiveRecord, на который будет ссылаться это

        // обязательно
        // в зависимости от типа отношения
        // self::HAS_ONE = внешний ключ, который ссылается на соединение
        // self::HAS_MANY = внешний ключ, который ссылается на соединение
        // self::BELONGS_TO = локальный ключ, который ссылается на соединение
        'local_or_foreign_key',
        // только FYI, это также присоединяется к первичному ключу "другой" модели

        // необязательно
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // дополнительные условия, которые вы хотите при присоединении отношения
        // $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))

        // необязательно
        'back_reference_name' // это, если вы хотите обратно ссылаться на это отношение к самому себе, например $user->contact->user;
    ];
]
class User extends ActiveRecord{
    protected array $relations = [
        'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
        'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
    ];

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }
}

class Contact extends ActiveRecord{
    protected array $relations = [
        'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
        'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
    ];
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'contacts');
    }
}

Теперь у нас настроены ссылки, поэтому мы можем использовать их очень легко!

$user = new User($pdo_connection);

// найти самого последнего пользователя.
$user->notNull('id')->orderBy('id desc')->find();

// получить контакты, используя отношение:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// или мы можем пойти в другую сторону.
$contact = new Contact();

// найти один контакт
$contact->find();

// получить пользователя, используя отношение:
echo $contact->user->name; // это имя пользователя

Довольно круто, да?

Установка пользовательских данных

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

setCustomData(string $field, mixed $value)

Вы прикрепляете пользовательские данные методом setCustomData().

$user->setCustomData('page_view_count', $page_view_count);

А затем вы просто обращаетесь к нему как к обычному свойству объекта.

echo $user->page_view_count;

События

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

onConstruct(ActiveRecord $ActiveRecord, array &config)

Это действительно полезно, если вам нужно установить стандартное соединение или что-то в этом роде.

// index.php или bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

//
//
//

// User.php
class User extends flight\ActiveRecord {

    protected function onConstruct(self $self, array &$config) { // не забудьте ссылку &
        // вы могли бы сделать это, чтобы автоматически установить соединение
        $config['connection'] = Flight::db();
        // или это
        $self->transformAndPersistConnection(Flight::db());

        // Вы также можете установить имя таблицы таким образом.
        $config['table'] = 'users';
    } 
}

beforeFind(ActiveRecord $ActiveRecord)

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

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFind(self $self) {
        // всегда выполнить id >= 0, если это ваше желание
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

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

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFind(self $self) {
        // расшифровка чего-то
        $self->secret = yourDecryptFunction($self->secret, $some_key);

        // возможно, сохранение чего-то пользовательского, например, запроса???
        $self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']; 
    } 
}

beforeFindAll(ActiveRecord $ActiveRecord)

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

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFindAll(self $self) {
        // всегда выполнить id >= 0, если это ваше желание
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

Похоже на afterFind(), но вы можете применить его ко всем записям!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFindAll(array $results) {

        foreach($results as $self) {
            // сделайте что-то крутое, как и послеFind()
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

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

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // установить какие-то стандартные значения
        if(!$self->created_date) {
            $self->created_date = gmdate('Y-m-d');
        }

        if(!$self->password) {
            $self->password = password_hash((string) microtime(true));
        }
    } 
}

afterInsert(ActiveRecord $ActiveRecord)

Возможно у вас есть случай использования для изменения данных после их вставки?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // сделайте что-то
        Flight::cache()->set('most_recent_insert_id', $self->id);

        // или что угодно еще....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

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

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // установить какие-то стандартные значения
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Возможно у вас есть случай использования для изменения данных после их обновления?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // сделайте что-то
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // или что угодно....
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

Это полезно, если вы хотите, чтобы события происходили как при вставке, так и при обновлении. Я не буду вдаваться в долгое объяснение, но уверен, вы можете догадаться, что это.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeSave(self $self) {
        $self->last_updated = gmdate('Y-m-d H:i:s');
    } 
}

beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)

Не уверен, что вы хотели бы здесь сделать, но здесь не будет суждений! Отправляйтесь!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo 'Он был храбрым солдатом... :cry-face:';
    } 
}

Управление подключением к базе данных

Когда вы используете эту библиотеку, вы можете установить подключение к базе данных несколькими способами. Вы можете установить подключение в конструкторе, вы можете установить его через переменную конфигурации $config['connection'] или вы можете установить его через setDatabaseConnection() (v0.4.1).

$pdo_connection = new PDO('sqlite:test.db'); // например
$user = new User($pdo_connection);
// или
$user = new User(null, [ 'connection' => $pdo_connection ]);
// или
$user = new User();
$user->setDatabaseConnection($pdo_connection);

Если вам нужно обновить подключение к базе данных, например, если вы запускаете долго работающий сценарий CLI и нужно обновлять соединение время от времени, вы можете переустановить подключение с помощью $your_record->setDatabaseConnection($pdo_connection).

Вклад

Пожалуйста, проходите. :D

Настройка

Когда вы вносите вклад, убедитесь, что вы запускаете composer test-coverage, чтобы поддерживать 100% покрытие тестов (это не истинное покрытие модульных тестов, а скорее тестирование интеграции).

Также убедитесь, что вы запускаете composer beautify и composer phpcs, чтобы исправить все ошибки линтинга.

Лицензия

MIT

Awesome-plugins/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->setTempDirectory(__DIR__ . '/../cache/');

    // Скажите Латте, где будет корневой каталог для ваших представлений.
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

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

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

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
    <head>
        <title>{$title ? $title . ' - '}My App</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- ваши элементы навигации здесь -->
            </nav>
        </header>
        <div id="content">
            <!-- Вот где волшебство -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; Copyright
        </div>
    </body>
</html>

И теперь у нас есть ваш файл, который будет отображаться внутри этого блока контента:

<!-- app/views/home.latte -->
<!-- Это говорит Латте, что этот файл "внутри" файла layout.latte -->
{extends layout.latte}

<!-- Это содержимое, которое будет отображаться внутри макета внутри блока content -->
{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.

CLI

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

Cookies

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

Отладка

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

Базы данных

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

Шифрование

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

Сеансы

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

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

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

Содействие

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

Examples

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

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

Нужна вдохновляющая идея?

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

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

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