Learn

Узнайте о Flight

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

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

Почему фреймворк?

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

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

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

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

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

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

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

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

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

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

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

Запросы

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

Ответы

Узнайте, как отправлять ответы вашим пользователям.

События

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

HTML Шаблоны

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

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

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

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

Узнайте, как настроить фреймворк для вашего приложения.

Расширение Flight

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

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

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

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

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

API фреймворка

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

Переход на v3

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

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

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

Learn/stopping

Остановка

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

Flight::halt();

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

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

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

Flight::stop();

Learn/errorhandling

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

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

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

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

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

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

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

Не Найдено

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

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

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

Learn/flight_vs_laravel

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

Что такое Laravel?

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

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

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

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

Learn/migrating_to_v3

Миграция на v3

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

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

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

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

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

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

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

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

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

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // это на самом деле будет хорошо
    echo '<p>Эта фраза Привет, мир была предоставлена буквой "П"</p>';
});

Flight::before('start', function(){
    // такие вещи вызовут ошибку
    echo '<html><head><title>Моя страница</title></head><body>';
});

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

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

Flight::after('start', function(){
    // это вызовет ошибку
    echo '<div>Ваша страница загрузилась за '.(microtime(true) - START_TIME).' секунд</div></body></html>';
});

Включение поведения рендеринга v2

Можно ли сохранить ваш старый код без переписывания, чтобы он работал с v3? Да, вы можете! Вы можете включить поведение рендеринга v2, установив параметр конфигурации flight.v2.output_buffering в значение true. Это позволит вам продолжать использовать старое поведение рендеринга, но рекомендуется исправить его на будущее. В v4 фреймворка это будет удалено.

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

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

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

// еще код 

Изменения Диспетчера (3.7.0)

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

Изменения halt() stop() redirect() и error() (3.10.0)

Поведение по умолчанию до версии 3.10.0 заключалось в очистке как заголовков, так и тела ответа. Это было изменено на очистку только тела ответа. Если вам нужно очистить также заголовки, вы можете использовать Flight::response()->clear().

Learn/configuration

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

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

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

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

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

Конфигурация загрузчика

Существует еще один параметр конфигурации для загрузчика. Это позволит вам автоматически загружать классы с _ в имени класса.

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

Переменные

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Не найдено

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

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

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

Learn/security

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

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

Заголовки

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Добавить как промежуточное ПО

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

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

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

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

Подделка межсайтовых запросов (CSRF)

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

Настройка

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// app/middleware/CsrfMiddleware.php

namespace app\middleware;

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

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

Межсайтовый скриптинг (XSS)

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

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

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

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

SQL-инъекция

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

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

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

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

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

CORS

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

// app/utils/CorsUtil.php

namespace app\utils;

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

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

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

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

        $request = Flight::request();

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

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

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

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

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

// В вашем bootstrap.php или index.php

// в flightphp/skeleton это находится в app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
    ini_set('display_errors', 0); // Отключить отображение ошибок
    ini_set('log_errors', 1);     // Записывать ошибки
    ini_set('error_log', '/path/to/error.log');
}

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

Санитария ввода

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

// Предположим, что есть запрос $_POST с $_POST['input'] и $_POST['email']

// Санировать строковый ввод
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Санировать электронную почту
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);

Хэширование паролей

Храните пароли в безопасности и аккуратно их проверяйте, используя встроенные функции PHP.

$password = Flight::request()->data->password;
// Хэшировать пароль при хранении (например, во время регистрации)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

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

Ограничение частоты

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

// Предполагая, что у вас установлен и зарегистрирован flightphp/cache
// Использование flightphp/cache в промежуточном ПО
Flight::before('start', function() {
    $cache = Flight::cache();
    $ip = Flight::request()->ip;
    $key = "rate_limit_{$ip}";
    $attempts = (int) $cache->retrieve($key);

    if ($attempts >= 10) {
        Flight::halt(429, 'Слишком много запросов');
    }

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

Заключение

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

Learn/overriding

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

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

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

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

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

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

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

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

Learn/routing

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

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

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

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

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

Обратные вызовы/Функции

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

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

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

Классы

Вы также можете использовать статический метод класса:

class Greeting {
    public static function hello() {
        echo 'привет, мир!';
    }
}

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

Или создав объект сначала, а затем вызвав метод:


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

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

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

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

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

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

Вот краткий пример:


use flight\database\PdoWrapper;

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

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

// index.php

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

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

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

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

Flight::start();

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

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

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

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

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

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

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

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


$router = Flight::router();

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

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

Регулярные выражения

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

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

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

Именованные параметры

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

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

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

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // Это будет соответствовать /bob/123
  // Но не будет соответствовать /bob/12345
});

Примечание: Не поддерживается сопоставление групп regex () с позиционными параметрами. :'(

Важная оговорка

Хотя в приведенном выше примере кажется, что @name непосредственно связан с переменной $name, это не так. Порядок параметров в функции обратного вызова определяет, что будет передано ей. Так что если вы измените порядок параметров в функции обратного вызова, переменные также изменятся. Вот пример:

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

А если вы перейдете по следующему URL: /bob/123, вывод будет привет, 123 (bob)!. Пожалуйста, будьте осторожны, когда настраиваете свои маршруты и функции обратного вызова.

Необязательные параметры

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

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

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

Шаблоны

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

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

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

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

Передача

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ресурсная маршрутизация

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

Чтобы создать ресурс, сделайте следующее:

Flight::resource('/users', UsersController::class);

И что произойдет в фоновом режиме, так это то, что он создаст следующие маршруты:

[
      'index' => 'GET ',
      'create' => 'GET /create',
      'store' => 'POST ',
      'show' => 'GET /@id',
      'edit' => 'GET /@id/edit',
      'update' => 'PUT /@id',
      'destroy' => 'DELETE /@id'
]

И ваш контроллер будет выглядеть так:

class UsersController
{
    public function index(): void
    {
    }

    public function show(string $id): void
    {
    }

    public function create(): void
    {
    }

    public function store(): void
    {
    }

    public function edit(string $id): void
    {
    }

    public function update(string $id): void
    {
    }

    public function destroy(string $id): void
    {
    }
}

Примечание: Вы можете просмотреть вновь добавленные маршруты с помощью runway, запустив php runway routes.

Настройка ресурсных маршрутов

Существует несколько вариантов настройки ресурсных маршрутов.

Псевдоним базы

Вы можете настроить aliasBase. По умолчанию псевдоним является последней частью указанного URL. Например, /users/ приведет к aliasBase равному users. Когда эти маршруты созданы, псевдонимы будут users.index, users.create и т. д. Если вы хотите изменить псевдоним, установите aliasBase на желаемое значение.

Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);

Только и Исключено

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

Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);

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

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

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

Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);

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

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

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

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

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

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

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

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

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

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

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

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

// Это магическая строка
})->stream();

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

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

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

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

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

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

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

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

Learn/flight_vs_symfony

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

Что такое Symfony?

Symfony - набор многоразовых компонентов PHP и фреймворк PHP для веб-проектов.

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

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

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

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

Learn/flight_vs_another_framework

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

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

Laravel

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

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

Slim

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

Сравните Slim и Flight.

Fat-Free

Fat-Free - это полностековый фреймворк в намного меньшем объеме. Хотя в нем есть все необходимые инструменты, его архитектура данных может усложнить некоторые проекты более, чем это необходимо.

Сравните Fat-Free и Flight.

Symfony

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

Сравните Symfony и Flight.

Learn/variables

Переменные

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

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

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

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

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

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

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

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

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

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

Learn/dependency_injection_container

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

Вступление

Контейнер внедрения зависимостей (DIC) является мощным инструментом, который позволяет управлять зависимостями вашего приложения. Это ключевая концепция в современных PHP фреймворках и используется для управления созданием и настройкой объектов. Некоторые примеры DIC библиотек: Dice, Pimple, PHP-DI, и league/container.

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

Основной пример

В старом подходе это могло выглядеть примерно так:


require 'vendor/autoload.php';

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

    protected PDO $pdo;

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

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

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

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

Flight::start();

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

Вот тот же пример с использованием DIC (используя Dice):


require 'vendor/autoload.php';

// тот же класс, что и выше. Ничего не изменилось
class UserController {

    protected PDO $pdo;

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

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

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

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

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

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

Flight::start();

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


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

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

PSR-11

Flight также может использовать любой совместимый с PSR-11 контейнер. Это означает, что вы можете использовать любой контейнер, который реализует интерфейс PSR-11. Вот пример использования контейнера PSR-11 от League:


require 'vendor/autoload.php';

// тот же класс UserController, что и выше

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

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

Flight::start();

Хотя это может быть немного более многословным, чем предыдущий пример с Dice, все равно делает свою работу с теми же преимуществами!

Пользовательский обработчик DIC

Вы также можете создать свой собственный обработчик DIC. Это полезно, если у вас есть пользовательский контейнер, который вы хотите использовать и который не является PSR-11 (Dice). См. основной пример для того, как это сделать.

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

Экземпляр Engine

Если вы используете экземпляр Engine в своих контроллерах/промежуточных уровнях, вот как вы можете его настроить:


// Где-то в вашем файле инициализации
$engine = Flight::app();

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

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

// Теперь вы можете использовать экземпляр Engine в ваших контроллерах/промежуточных уровнях

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

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

Добавление других классов

Если у вас есть другие классы, которые вы хотите добавить в контейнер, с Dice это легко, так как они будут автоматически разрешены контейнером. Вот пример:


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

class MyCustomClass {
    public function parseThing() {
        return 'thing';
    }
}

class UserController {

    protected MyCustomClass $MyCustomClass;

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

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

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

Learn/middleware

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

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

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

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

// Если вы предоставляете только безымянную функцию, она будет выполнена перед обратным вызовом маршрута. 
// нет "после" промежуточных функций, за исключением классов (см. ниже)
Flight::route('/path', function() { echo ' Вот я!'; })->addMiddleware(function() {
    echo 'Сначала промежуточное программное обеспечение!';
});

Flight::start();

// Это выведет "Сначала промежуточное программное обеспечение! Вот я!"

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

Классы промежуточного программного обеспечения

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

class MyMiddleware {
    public function before($params) {
        echo 'Сначала промежуточное программное обеспечение!';
    }

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

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

Flight::start();

// Это выведет "Сначала промежуточное программное обеспечение! Вот я! Последнее промежуточное программное обеспечение!"

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

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

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

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

Вот простой пример с возвратом false:

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

        // поскольку это верно, всё продолжает идти
    }
}

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

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

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

Пример пользовательской ошибки

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

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

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

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


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

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

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


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

    // Это все еще /users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // А это все еще /users/1234
    Flight::route('/users/@id', function($id) { echo 'пользователь:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Learn/filtering

Фильтрация

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

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

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

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

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

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

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

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

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

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

// Объявляем пользовательский метод
Flight::map('hello', function (string $name) {
  return "Hello, $name!";
});

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

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

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

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

Hello Fred! Have a nice day!

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

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

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

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

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

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

Learn/requests

Запросы

Flight инкапсулирует HTTP-запрос в единый объект, к которому можно получить доступ следующим образом:

$request = Flight::request();

Типичные Сценарии Использования

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

Вот пример получения параметра строки запроса:

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

Вот пример формы с методом POST:

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

Свойства Объекта Запроса

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

Вы можете получить доступ к свойствам query, data, cookies и files как к массивам или объектам.

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

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

Или вы можете сделать:

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

СЫРОЕ Тело Запроса

Чтобы получить сырое тело HTTP-запроса, например, при работе с запросами PUT, вы можете сделать:

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

JSON Входные Данные

Если вы отправляете запрос с типом application/json и данными {"id": 123}, они будут доступны через свойство data:

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

$_GET

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

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

$_POST

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

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

$_COOKIE

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

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

$_SERVER

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


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

Доступ к Загруженным Файлам через $_FILES

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

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

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

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

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

Если вы загрузили несколько файлов, вы можете пройтись по ним в цикле:

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

Заметка по Безопасности: Всегда валидируйте и очищайте ввод пользователя, особенно при работе с загрузками файлов. Всегда валидируйте типы расширений, которые вы разрешите загружать, но вы также должны валидировать "магические байты" файла, чтобы убедиться, что это действительно тот тип файла, который пользователь заявляет, что это. Существуют статьи и библиотеки для помощи в этом.

Заголовки Запроса

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


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

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

Тело Запроса

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

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

Метод Запроса

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

$method = Flight::request()->method; // фактически вызывает getMethod()
$method = Flight::request()->getMethod();

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

URL Запросов

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

Полный URL

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

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

Базовый URL

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

$url = Flight::request()->getBaseUrl();
// Обратите внимание, нет конечного слэша.
// https://example.com

Парсинг Запросов

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

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

Learn/frameworkmethods

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

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

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

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

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

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

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

Learn/api

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

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

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

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

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

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

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

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

Learn/why_frameworks

Почему фреймворк?

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

Причины использования фреймворка

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

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

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

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

Это может работать так:

И зачем это важно?

Иметь правильный централизованный маршрутизатор может действительно сильно облегчить вашу жизнь! Просто сначала это может быть трудно увидеть. Вот несколько причин:

Я уверен, что вы знакомы со способом создания веб-сайта, описанным скрипт за скриптом. Может быть у вас есть файл под названием index.php, который содержит множество условных операторов if для проверки URL, а затем запуска определенной функции на основе URL. Это форма маршрутизации, но она не очень организована и может быстро выйти из-под контроля. Система маршрутизации Flight - это гораздо более организованный и мощный способ управления маршрутами.

Это?


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

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

// и так далее...

или это?


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

// Возможно, в вашем app/controllers/UserController.php
class UserController {
    public function viewUserProfile($id) {
        // сделать что-то
    }

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

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

Запросы и ответы

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

Запросы

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

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

Ответы

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

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

Learn/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 захватит его и отправит обратно пользователю с соответствующими заголовками.


// Это отправит "Hello, World!" в браузер пользователя
Flight::route('/', function() {
    echo "Hello, World!";
});

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

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


// Это отправит "Hello, World!" в браузер пользователя
Flight::route('/', function() {
    // многословно, но иногда работает, когда вам это нужно
    Flight::response()->write("Hello, World!");

    // если вы хотите получить тело, которое вы установили на этот момент
    // вы можете сделать это так
    $body = Flight::response()->getBody();
});

Код состояния

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

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "Hello, World!";
    } else {
        Flight::response()->status(403);
        echo "Forbidden";
    }
});

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

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

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

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

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

// то же самое что и

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

Очистка тела ответа

Если вы хотите очистить тело ответа, вы можете использовать метод clearBody:

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

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

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

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

// Это сожмет все ответы для любого маршрута
Flight::response()->addResponseBodyCallback(function($body) {
    return gzencode($body, 9);
});

Вы можете добавить несколько обратных вызовов, и они будут выполняться в порядке их добавления. Поскольку это может принимать любой callable, он может принимать массив классов [ $class, 'method' ], замыкание $strReplace = function($body) { str_replace('hi', 'there', $body); };, или имя функции 'minify', если у вас есть функция, чтобы минимизировать ваш html код, например.

Примечание: Обратные вызовы маршрутов не будут работать, если вы используете параметр конфигурации flight.v2.output_buffering.

Специфический обратный вызов маршрута

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

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

    // Это сожмет только ответ для этого маршрута
    Flight::response()->addResponseBodyCallback(function($body) {
        return gzencode($body, 9);
    });
});

Опция Middleware

Вы также можете использовать промежуточное ПО, чтобы применить обратный вызов ко всем маршрутам через промежуточное ПО:

// MinifyMiddleware.php
class MinifyMiddleware {
    public function before() {
        // Примените обратный вызов здесь к объекту response().
        Flight::response()->addResponseBodyCallback(function($body) {
            return $this->minify($body);
        });
    }

    protected function minify(string $body): string {
        // как-то уменьшить тело
        return $body;
    }
}

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

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

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


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

JSON

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

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

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

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

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

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

JSON с форматированием

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

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

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

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

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

JSON и остановка выполнения (v3.10.0)

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

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

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

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

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

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

JSONP

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

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

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

my_func({"id":123});

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

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

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

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

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

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

Остановка

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

Flight::halt();

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

Flight::halt(200, 'Be right back...');

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

Flight::stop();

Очистка данных ответа

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

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

Очистка только тела ответа

Если вы хотите очистить только тело ответа, вы можете использовать метод clearBody():

// Это всё равно сохранит любые заголовки, установленные на объекте response().
Flight::response()->clearBody();

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

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

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

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


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

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

Время последнего изменения

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

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

ETag

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

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

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

Загрузка файла (v3.12.0)

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

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

Learn/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/events

Система событий в Flight PHP (v3.15.0+)

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

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

Почему использовать события?

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

Представьте, что вы создаете приложение для блога:

Без событий вы бы напихали все это в одну функцию. С происшествиями вы можете разделить это: одна часть сохраняет комментарий, другая запускает событие типа 'comment.posted', а отдельные слушатели обрабатывают электронную почту и ведение журналов. Это делает ваш код более чистым и позволяет добавлять или удалять функции (например, уведомления), не трогая основную логику.

Обычные применения

Регистрация слушателей событий

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

Синтаксис

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

Как это работает

Вы "подписываетесь" на событие, сообщая Flight, что делать, когда оно происходит. Коллбек может принимать аргументы, переданные из триггера события.

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

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

Flight::onEvent('user.login', function ($username) {
    echo "Добро пожаловать обратно, $username!";
});

Здесь, когда событие 'user.login' инициируется, оно поприветствует пользователя по имени.

Ключевые моменты

Инициация событий

Чтобы событие произошло, используйте Flight::triggerEvent(). Это сообщает Flight выполнить всех слушателей, зарегистрированных для этого события, передавая любые данные, которые вы предоставляете.

Синтаксис

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

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

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

Это инициирует событие 'user.login' и отправляет 'alice' слушателю, который мы определили ранее, что приведет к выводу: Добро пожаловать обратно, alice!.

Ключевые моменты

Регистрация слушателей событий

...

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

Пример:

Flight::onEvent('user.login', function ($username) {
    if (isBanned($username)) {
        logoutUser($username);
        return false; // Останавливает последующих слушателей
    }
});
Flight::onEvent('user.login', function ($username) {
    sendWelcomeEmail($username); // это никогда не отправляется
});

Переопределение методов событий

Flight::onEvent() и Flight::triggerEvent() доступны для расширения, что означает, что вы можете перес définir, как они работают. Это прекрасно для продвинутых пользователей, которые хотят настроить систему событий, например, добавив ведение журнала или изменив, как события отправляются.

Пример: Настройка onEvent

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

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

Почему переопределять?

Куда поместить ваши события

Как новичок, вы можете задаться вопросом: где мне регистрировать все эти события в моем приложении? Простота Flight означает, что нет строгого правила — вы можете разместить их там, где это имеет смысл для вашего проекта. Тем не менее, поддержание их организованными помогает вам поддерживать код по мере роста вашего приложения. Вот некоторые практические варианты и лучшие практики, адаптированные к легковесной природе Flight:

Вариант 1: В вашем основном index.php

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

require 'vendor/autoload.php';

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

// Определение маршрутов
Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "Вошли в систему!";
});

Flight::start();

Вариант 2: Отдельный файл events.php

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

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

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

Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "Вошли в систему!";
});

Flight::start();

Вариант 3: Ближе к месту их триггера

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

Flight::route('/signup', function () {
    // Регистрация события здесь
    Flight::onEvent('user.registered', function ($email) {
        echo "Уведомление об приветствии отправлено на $email!";
    });

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

Лучшие практики для Flight

Советы: группировка по назначению

В events.php группируйте связанные события (например, все события, связанные с пользователем, вместе) с комментариями для ясности:

// app/config/events.php
// События пользователя
Flight::onEvent('user.login', function ($username) {
    error_log("$username вошел в систему");
});
Flight::onEvent('user.registered', function ($email) {
    echo "Добро пожаловать на $email!";
});

// События страницы
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]);
});

Эта структура хорошо масштабируется и остается удобной для новичков.

Примеры для новичков

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

Пример 1: Ведение журнала входа пользователя

// Шаг 1: Зарегистрируйте слушателя
Flight::onEvent('user.login', function ($username) {
    $time = date('Y-m-d H:i:s');
    error_log("$username вошел в систему в $time");
});

// Шаг 2: Запустить это в вашем приложении
Flight::route('/login', function () {
    $username = 'bob'; // Предположим, это из формы
    Flight::triggerEvent('user.login', $username);
    echo "Привет, $username!";
});

Почему это полезно: Код входа не должен знать о ведении журнала — он просто инициирует событие. Позже вы можете добавить больше слушателей (например, отправить приветственное письмо) без изменения маршрута.

Пример 2: Уведомление о новых пользователях

// Слушатель для новых регистраций
Flight::onEvent('user.registered', function ($email, $name) {
    // Симулировать отправку письма
    echo "Электронное письмо отправлено на $email: Добро пожаловать, $name!";
});

// Инициировать, когда кто-то подписывается
Flight::route('/signup', function () {
    $email = 'jane@example.com';
    $name = 'Jane';
    Flight::triggerEvent('user.registered', $email, $name);
    echo "Спасибо за регистрацию!";
});

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

Пример 3: Очистка кеша

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

// Инициировать, когда страница редактируется
Flight::route('/edit-page/(@id)', function ($pageId) {
    // Предположим, что мы обновили страницу
    Flight::triggerEvent('page.updated', $pageId);
    echo "Страница $pageId обновлена.";
});

Почему это полезно: Код редактирования не заботится о кешировании — он просто сигнализирует об обновлении. Другие части приложения могут реагировать по мере необходимости.

Лучшие практики

Система событий в Flight PHP с Flight::onEvent() и Flight::triggerEvent() предоставляет вам простой, но мощный способ создания гибких приложений. Позволяя различным частям вашего приложения взаимодействовать друг с другом через события, вы можете держать код организованным, повторно используемым и простым в расширении. Будь то ведение журнала действий, отправка уведомлений или управление обновлениями, события помогают делать это, не запутывая вашу логику. Кроме того, с возможностью переопределения этих методов у вас есть свобода настраивать систему под ваши нужды. Начните с небольшого события и посмотрите, как это преобразует структуру вашего приложения!

Встроенные события

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

Список встроенных событий

Learn/views

Виды

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

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

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

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

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

Привет, Bob!

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

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

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

Flight::render('hello');

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

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

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

Макеты

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

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

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

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

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

header.php:

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

body.php:

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

layout.php:

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

Вывод будет:

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

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

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

// Загрузка библиотеки Smarty
require './Smarty/libs/Smarty.class.php';

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

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

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

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

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

Learn/templates

HTML Views and Templates

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

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

Встроенный движок представлений

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

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

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

Hello, <?= $name ?>!

Вывод будет:

Hello, Bob!

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

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

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

Flight::render('hello');

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

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

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

Макеты

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

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

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

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

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

header.php:

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

body.php:

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

layout.php:

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

Вывод будет:

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

Smarty

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

// Загрузить библиотеку Smarty
require './Smarty/libs/Smarty.class.php';

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

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

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

Для полноты картины вы также должны переопределить метод render по умолчанию в Flight:

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

Latte

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

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

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

// И заверните, чтобы вы могли правильно использовать Flight::render()
Flight::map('render', function(string $template, array $data): void {
  // Это как $latte_engine->render($template, $data);
  echo Flight::view()->render($template, $data);
});

Blade

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

Сначала вам нужно установить библиотеку BladeOne через Composer:

composer require eftec/bladeone

Затем вы можете настроить BladeOne как класс представления в Flight:

<?php
// Загрузить библиотеку BladeOne
use eftec\bladeone\BladeOne;

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

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

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

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

Для полноты картины вы также должны переопределить метод render по умолчанию в Flight:

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

В этом примере файл шаблона hello.blade.php может выглядеть так:

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

Вывод будет:

Hello, Bob!

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

Learn/flight_vs_fat_free

Сравнение Flight и Fat-Free

Что такое Fat-Free?

Fat-Free (ласково известный как F3) - это мощный, но простой в использовании микро-фреймворк на PHP, разработанный для помощи в создании динамичных и надежных веб-приложений - быстро!

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

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

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

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

Learn/extending

Расширение

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

Если вы ищете DIC (Контейнер Для Внедрения Зависимостей), переходите на страницу Контейнер Для Внедрения Зависимостей.

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

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

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

// Вызовите свой собственный метод
Flight::hello('Bob');

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

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

hello('Bob');

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ведение журнала

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

// index.php или bootstrap.php

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

Теперь, когда он зарегистрирован, вы можете использовать его в своём приложении:

// В вашем контроллере или маршруте
Flight::log()->warning('Это сообщение предупреждения');

Это зафиксирует сообщение в файл журнала, который вы указали. А что, если вы хотите зафиксировать что-то, когда происходит ошибка? Вы можете использовать метод error:

// В вашем контроллере или маршруте

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

Вы также можете создать базовую систему APM (Мониторинг Производительности Приложения) используя методы before и after:

// В вашем bootstrap файле

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

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

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

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

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

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

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

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

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

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

Однако методы фреймворка, такие как map и register, не могут быть переопределены. Вы получите ошибку, если попытаетесь это сделать.

Learn/json

JSON

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

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

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

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

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

my_func({"id":123});

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

Learn/flight_vs_slim

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

Что такое Slim?

Slim - это PHP микрофреймворк, который помогает вам быстро писать простые, но мощные веб-приложения и API.

Многие идеи для некоторых функций v3 Flight фактически были взяты из Slim. Группировка маршрутов и выполнение промежуточного ПО в определенном порядке - это две функции, которые были вдохновлены Slim. Slim v3 был создан в стремлении к простоте, но есть смешанные отзывы относительно v4.

Достоинства по сравнению с Flight

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

Learn/autoloading

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

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

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

Основной пример

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

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

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

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


/**
 * public/index.php
 */

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

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

// не требуется использование пространства имен

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

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

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

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


/**
 * public/index.php
 */

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

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

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

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

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

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

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


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

// пространство имен должно соответствовать структуре каталога и регистру (обратите внимание на каталог UTILS в верхнем регистре
//     как в дереве файлов выше)
namespace app\UTILS;

class ArrayHelperUtil {

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

Подчеркивания в именах классов

Начиная с версии 3.7.2, вы можете использовать Pascal_Snake_Case для имен ваших классов, запустив Loader::setV2ClassLoading(false);. Это позволит вам использовать подчеркивания в именах ваших классов. Это не рекомендуется, но это доступно для тех, кто нуждается в этом.


/**
 * public/index.php
 */

// Добавить путь к автозагрузчику
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

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

// не требуется использование пространства имен

class My_Controller {

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

Learn/troubleshooting

Решение проблем

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

Общие проблемы

404 Страница не найдена или непредвиденное поведение маршрута

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


Flight::route('/hello', function(){
    // Это может вызвать ошибку 404 Страница не найдена
    return 'Hello World';
});

// Что вам, вероятно, нужно
Flight::route('/hello', function(){
    echo 'Hello World';
});

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

Класс не найден (автозагрузка не работает)

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

Некорректное имя файла

Самая распространенная причина в том, что имя класса не совпадает с именем файла.

Если у вас есть класс с именем MyClass, то файл должен иметь имя MyClass.php. Если у вас есть класс с именем MyClass и файл назван myclass.php, то автозагрузчик не сможет его найти.

Некорректное пространство имен

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

// код

// если ваш MyController находится в каталоге app/controllers и у него есть пространство имен
// это не сработает.
Flight::route('/hello', 'MyController->hello');

// вам нужно выбрать один из этих вариантов
Flight::route('/hello', 'app\controllers\MyController->hello');
// или если у вас есть оператор use вверху

use app\controllers\MyController;

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

path() не определен

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


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

Install

Установка

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

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

composer require flightphp/core

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

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

Apache

Для Apache отредактируйте ваш файл .htaccess следующим образом:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Примечание: Если вам нужно использовать flight в подкаталоге, добавьте строку RewriteBase /subdir/ сразу после RewriteEngine On.

Примечание: Если вы хотите защитить все файлы сервера, такие как файл db или env. Поместите это в свой файл .htaccess:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Для Nginx добавьте следующее в ваше объявление сервера:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Создайте файл index.php.

<?php

// Если вы используете Composer, требуется автозагрузчик.
require 'vendor/autoload.php';
// если вы не используете Composer, загружайте фреймворк напрямую
// require 'flight/Flight.php';

// Затем определите маршрут и назначьте функцию для обработки запроса.
Flight::route('/', function () {
  echo 'hello world!';
});

// Наконец, запустите фреймворк.
Flight::start();

Guides/blog

Создание простого блога с Flight PHP

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

Предварительные требования

Шаг 1: Настройте свой проект

Начните с создания новой директории проекта и установки Flight через Composer.

  1. Создайте директорию:

    mkdir flight-blog
    cd flight-blog
  2. Установите Flight:

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

    mkdir public
  4. Базовый index.php: Создайте public/index.php с простым маршрутом "hello world":

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

    php -S localhost:8000 -t public/

    Посетите http://localhost:8000, чтобы увидеть "Привет, Flight!".

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

Для чистой настройки структурируйте ваш проект следующим образом:

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

Шаг 3: Установите и настройте Latte

Latte — это легкий шаблонизатор, который хорошо интегрируется с Flight.

  1. Установите Latte:

    composer require latte/latte
  2. Настройте Latte в Flight: Обновите public/index.php, чтобы зарегистрировать Latte как движок представлений:

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

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

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

    Перезапустите сервер, если вы вышли из него, и посетите http://localhost:8000, чтобы увидеть отрендеренную страницу.

  5. Создайте файл данных:

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

    В data/posts.json:

    [
       {
           "slug": "first-post",
           "title": "Мой первый пост",
           "content": "Это мой самый первый пост в блоге с Flight PHP!"
       }
    ]

Шаг 4: Определите маршруты

Отделите ваши маршруты в файл конфигурации для лучшей организации.

  1. Создание routes.php: В app/config/routes.php:

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

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

Шаг 5: Хранение и получение постов блога

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

  1. Добавьте метод для постов: В index.php добавьте метод для загрузки постов:

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

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

Шаг 6: Создание шаблонов

Обновите ваши шаблоны для отображения постов.

  1. Страница поста (app/views/post.latte):

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

Шаг 7: Добавление создания постов

Обработайте отправку формы для добавления новых постов.

  1. Создайте форму (app/views/create.latte):

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

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

    • Посетите http://localhost:8000/create.
    • Отправьте новый пост (например, "Второй пост" с некоторым содержимым).
    • Проверьте главную страницу, чтобы увидеть его в списке.

Шаг 8: Улучшите обработку ошибок

Переопределите метод notFound для лучшего опыта 404.

В index.php:

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

Создайте app/views/404.latte:

{extends 'layout.latte'}

{block content}
    <h2>404 - {$title}</h2>
    <p>Извините, этой страницы не существует!</p>
{/block}

Следующие шаги

Заключение

Вы создали простой блог с Flight PHP! Этот гид демонстрирует основные функции, такие как маршрутизация, шаблонизация с помощью Latte и обработка отправок форм — при этом все оставаясь легковесным. Изучите документацию Flight для более сложных функций, чтобы развить ваш блог дальше!

License

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

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

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

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

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

About

Что такое Flight?

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

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

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

Сначала установите его с помощью Composer

composer require flightphp/core

или вы можете скачать zip-архив репозитория здесь. Затем у вас будет базовый файл index.php, похожий на следующий:

<?php

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

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

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

Flight::start();

Вот и все! У вас есть базовое приложение на Flight. Теперь вы можете запустить этот файл с помощью php -S localhost:8000 и посетить http://localhost:8000 в вашем браузере, чтобы увидеть вывод.

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

Это быстро?

Да! Flight быстрый. Это один из самых быстрых фреймворков PHP, доступных на данный момент. Вы можете увидеть все тесты на TechEmpower

Смотрите тест ниже с некоторыми другими популярными фреймворками PHP.

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

Skeleton/Boilerplate App

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

Сообщество

Мы в Matrix Chat

Matrix

И в Discord

Участие

Существуют два способа, которыми вы можете внести свой вклад в Flight:

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

Требования

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

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

Лицензия

Flight выпускается под лицензией MIT.

Awesome-plugins/php_cookie

Cookies

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

Установка

Установка проста с помощью 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

flightphp/cache

Лёгкий, простой и автономный класс кэширования PHP в файле

Преимущества

Этот сайт документации использует эту библиотеку для кэширования каждой из страниц!

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

Установка

Установите через composer:

composer require flightphp/cache

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

Использование довольно простое. Это сохраняет файл кэша в директории кэша.

use flight\Cache;

$app = Flight::app();

// Вы передаёте директорию, в которой будет храниться кэш, в конструктор
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {

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

Затем вы можете использовать его в своём коде так:


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

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

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

Посетите https://github.com/flightphp/cache для полной документации и убедитесь, что вы посмотрели папку примеры.

Awesome-plugins/permissions

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

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

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

Установка

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

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

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

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

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

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

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

// затем у вас вероятно есть что-то, что говорит вам, какая текущая роль у человека
// скорее всего у вас есть что-то, откуда вы извлекаете текущую роль
// из переменной сеанса, которая определяет это
// после входа в систему у кого-то должна быть роль 'guest' или 'public'.
$current_role = 'admin';

// настройка разрешений
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
    return $current_role !== 'guest';
});

// Вам вероятно захочется сохранить этот объект где-то в Flight
Flight::set('permission', $permission);

Затем в контроллере где-то вы можете иметь что-то вроде этого.

<?php

// некоторый контроллер
class SomeController {
    public function someAction() {
        $permission = Flight::get('permission');
        if ($permission->has('loggedIn')) {
            // сделать что-то
        } else {
            // сделать что-то другое
        }
    }
}

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

$current_role = 'admin';

// настройка разрешений
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
    if($current_role === 'admin') {
        $permissions = ['create', 'read', 'update', 'delete'];
    } else if($current_role === 'editor') {
        $permissions = ['create', 'read', 'update'];
    } else if($current_role === 'author') {
        $permissions = ['create', 'read'];
    } else if($current_role === 'contributor') {
        $permissions = ['create'];
    } else {
        $permissions = [];
    }
    return $permissions;
});
Flight::set('permission', $permission);

Затем где-то в контроллере...

class PostController {
    public function create() {
        $permission = Flight::get('permission');
        if ($permission->can('post.create')) {
            // сделать что-то
        } else {
            // сделать что-то еще
        }
    }
}

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

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

Замыкания

$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
    // ... код
});

// в вашем файле контроллера
public function createOrder() {
    $MyDependency = Flight::myDependency();
    $permission = Flight::get('permission');
    if ($permission->can('order.create', $MyDependency)) {
        // сделать что-то
    } else {
        // сделать что-то еще
    }
}

Классы

namespace MyApp;

class Permissions {

    public function order(string $current_role, MyDependency $MyDependency = null) {
        // ... код
    }
}

Сокращение для установки разрешений с использованием классов

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

<?php

// код инициализации
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');

// myapp/Permissions.php
namespace MyApp;

class Permissions {

    public function order(string $current_role, int $user_id) {
        // Предположим, что вы это настроили заранее
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $allowed_permissions = [ 'read' ]; // каждый может просматривать заказ
        if($current_role === 'manager') {
            $allowed_permissions[] = 'create'; // менеджеры могут создавать заказы
        }
        $some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
        if($some_special_toggle_from_db) {
            $allowed_permissions[] = 'update'; // если у пользователя есть особый переключатель, он может обновлять заказы
        }
        if($current_role === 'admin') {
            $allowed_permissions[] = 'delete'; // администраторы могут удалять заказы
        }
        return $allowed_permissions;
    }
}

Здесь примечательно то, что есть также сокращение, которое можно использовать (которое также может быть кешировано!!!), где вы просто говорите классу разрешений сопоставить все методы в классе в разрешения. Поэтому, если у вас есть метод с именем order() и метод с именем company(), они будут автоматически сопоставлены, и вы сможете просто выполнить $Permissions->has('order.read') или $Permissions->has('company.read'), и это сработает. Определение этого очень сложно, так что держитесь здесь со мной. Просто вам нужно сделать это:

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

class MyPermissions {
    public function order(string $current_role, int $order_id = 0): array {
        // код определения разрешений
        return $permissions_array;
    }

    public function company(string $current_role, int $company_id): array {
        // код определения разрешений
        return $permissions_array;
    }
}

Затем сделайте разрешения обнаруживаемыми с использованием этой библиотеки.

$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);

Наконец, вызовите разрешение в вашей кодовой базе, чтобы проверить, разрешено ли пользователю выполнение заданного разрешения.

class SomeController {
    public function createOrder() {
        if(Flight::get('permissions')->can('order.create') === false) {
            die('Вы не можете создать заказ. Извините!');
        }
    }
}

Кеширование

Для включения кэширования, см. простую библиотеку wruczak/phpfilecache. Пример включения приведен ниже.


// этот $app может быть частью вашего кода, или
// вы можете просто передать null, и он извлечет из Flight::app() в конструкторе
$app = Flight::app();

// Теперь для этого принимается файловое кэширование. Другие могут легко
// быть добавлены в будущем. 
$Cache = new Wruczek\PhpFileCache\PhpFileCache;

$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 - это сколько секунд кэшировать это. Оставьте это, чтобы не использовать кэширование

И впереди!

Awesome-plugins/simple_job_queue

Простой очередь задач

Простой очередь задач - это библиотека, которая может использоваться для обработки задач асинхронно. Она может быть использована с beanstalkd, MySQL/MariaDB, SQLite и PostgreSQL.

Установка

composer require n0nag0n/simple-job-queue

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

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

Добавление в Flight

Добавить это в Flight просто, и это делается с помощью метода register(). Ниже приведен пример того, как добавить это в Flight.

<?php
require 'vendor/autoload.php';

// Замените ['mysql'] на ['beanstalkd'], если хотите использовать beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
    // если у вас уже есть соединение PDO на Flight::db();
    $Job_Queue->addQueueConnection(Flight::db());

    // или если вы используете beanstalkd/Pheanstalk
    $pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
    $Job_Queue->addQueueConnection($pheanstalk);
});

Добавление новой задачи

Когда вы добавляете задачу, вам нужно указать конвейер (очередь). Это сравнимо с каналом в RabbitMQ или трубой в beanstalkd.

<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));

Запуск рабочего процесса

Вот пример файла того, как запустить рабочего процесса.

<?php

require 'vendor/autoload.php';

$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Соединение PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);

// или если вы используете beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);

$Job_Queue->watchPipeline('send_important_emails');
while(true) {
    $job = $Job_Queue->getNextJobAndReserve();

    // настройте это так, как вам будет спокойнее (только для очередей базы данных, beanstalkd не нуждается в этом условии)
    if(empty($job)) {
        usleep(500000);
        continue;
    }

    echo "Обработка {$job['id']}\n";
    $payload = json_decode($job['payload'], true);

    try {
        $result = doSomethingThatDoesSomething($payload);

        if($result === true) {
            $Job_Queue->deleteJob($job);
        } else {
            // это убирает его из очереди готовых и помещает в другую очередь, которую можно будет забрать и «ударить» позже.
            $Job_Queue->buryJob($job);
        }
    } catch(Exception $e) {
        $Job_Queue->buryJob($job);
    }
}

Обработка длительных процессов с помощью Supervisord

Supervisord - это система управления процессами, которая обеспечивает постоянную работу ваших процессов рабочих. Вот более полное руководство по настройке его с вашим рабочим процессом Простой очереди задач:

Установка Supervisord

# На Ubuntu/Debian
sudo apt-get install supervisor

# На CentOS/RHEL
sudo yum install supervisor

# На macOS с Homebrew
brew install supervisor

Создание скрипта рабочего процесса

Сначала сохраните ваш код рабочего процесса в отдельный файл PHP:

<?php

require 'vendor/autoload.php';

$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Соединение PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);

// Установите конвейер для наблюдения
$Job_Queue->watchPipeline('send_important_emails');

// Запись начала работы рабочего процесса
echo date('Y-m-d H:i:s') . " - Рабочий процесс запущен\n";

while(true) {
    $job = $Job_Queue->getNextJobAndReserve();

    if(empty($job)) {
        usleep(500000); // Спите 0,5 секунды
        continue;
    }

    echo date('Y-m-d H:i:s') . " - Обработка задачи {$job['id']}\n";
    $payload = json_decode($job['payload'], true);

    try {
        $result = doSomethingThatDoesSomething($payload);

        if($result === true) {
            $Job_Queue->deleteJob($job);
            echo date('Y-m-d H:i:s') . " - Задача {$job['id']} успешно завершена\n";
        } else {
            $Job_Queue->buryJob($job);
            echo date('Y-m-d H:i:s') . " - Задача {$job['id']} не удалась, похоронена\n";
        }
    } catch(Exception $e) {
        $Job_Queue->buryJob($job);
        echo date('Y-m-d H:i:s') . " - Исключение при обработке задачи {$job['id']}: {$e->getMessage()}\n";
    }
}

Настройка Supervisord

Создайте файл конфигурации для вашего рабочего процесса:

[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d

Основные параметры конфигурации:

Управление рабочими процессами с помощью Supervisorctl

После создания или изменения конфигурации:

# Перезагрузить конфигурацию супервайзера
sudo supervisorctl reread
sudo supervisorctl update

# Управление конкретными процессами рабочих
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*

Запуск нескольких конвейеров

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

[program:email_worker]
command=php /path/to/email_worker.php
# ... другие настройки ...

[program:notification_worker]
command=php /path/to/notification_worker.php
# ... другие настройки ...

Мониторинг и журналы

Проверьте журналы для мониторинга активности рабочих процессов:

# Просмотр журналов
sudo tail -f /var/log/simple_job_queue.log

# Проверка состояния
sudo supervisorctl status

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

Awesome-plugins/index

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

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

Кэширование

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

Отладка

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

Базы данных

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

Сессии

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

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

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

Вклад

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

Awesome-plugins/ghost_session

Ghostff/Session

Менеджер сессий PHP (неблокирующий, flash, сегмент, шифрование сессий). Использует PHP open_ssl для необязательной шифровки/расшифровки данных сессии. Поддерживает File, MySQL, Redis и Memcached.

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

Установка

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

composer require ghostff/session

Базовая конфигурация

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


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// установите пользовательский путь к вашему файлу конфигурации сессии и дайте ему случайную строку для идентификатора сессии
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        // или вы можете вручную переопределить параметры конфигурации
        $session->updateConfiguration([
            // если вы хотите хранить данные своей сессии в базе данных (хорошо, если вы хотите, чтобы что-то вроде "выйти из всех устройств")
            Session::CONFIG_DRIVER        => Ghostff\Session\Drivers\MySql::class,
            Session::CONFIG_ENCRYPT_DATA  => true,
            Session::CONFIG_SALT_KEY      => hash('sha256', 'my-super-S3CR3T-salt'), // пожалуйста, измените это на что-то другое
            Session::CONFIG_AUTO_COMMIT   => true, // делайте это только если это требуется и/или трудно вызвать commit() для вашей сессии.
                                                   // дополнительно вы можете сделать Flight::after('start', function() { Flight::session()->commit(); });
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # Драйвер базы данных для PDO dns, например (mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # Хост базы данных
                'db_name'   => 'my_app_database',   # Имя базы данных
                'db_table'  => 'sessions',          # Таблица базы данных
                'db_user'   => 'root',              # Имя пользователя базы данных
                'db_pass'   => '',                  # Пароль базы данных
                'persistent_conn'=> false,          # Избегайте накладных расходов на установление нового соединения каждый раз, когда скрипту необходимо взаимодействовать с базой данных, что приводит к более быстрому веб-приложению. НИЖНЯЯ СТОРОНА ВЫ БУДЕТЕ ИСКАТЬ САМИ
            ]
        ]);
    }
);

Помогите! Мои данные сессии не сохраняются!

Вы устанавливаете данные своей сессии, и они не сохраняются между запросами? Возможно, вы забыли зафиксировать данные вашей сессии. Вы можете сделать это, вызвав $session->commit() после того, как установили данные вашей сессии.

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

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

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

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

Другой способ обойти это - когда вы настраиваете свой сервис сессий, вы должны установить auto_commit в true в вашей конфигурации. Это автоматически зафиксирует данные вашей сессии после каждого запроса.


$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        $session->updateConfiguration([
            Session::CONFIG_AUTO_COMMIT   => true,
        ]);
    }
);

Кроме того, вы можете сделать Flight::after('start', function() { Flight::session()->commit(); });, чтобы фиксировать данные вашей сессии после каждого запроса.

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

Посетите Github Readme для полной документации. Параметры конфигурации хорошо задокументированы в default_config.php файле. Код прост для понимания, если вы хотите просмотреть этот пакет сами.

Awesome-plugins/pdo_wrapper

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

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

Миграции

Миграция для вашего проекта отслеживает все изменения базы данных, связанные с вашим проектом. byjg/php-migration — это действительно полезная основная библиотека, с которой вы можете начать.

Установка

PHP библиотека

Если вы хотите использовать только PHP библиотеку в вашем проекте:

composer require "byjg/migration"

Интерфейс командной строки

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

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

composer require "byjg/migration-cli"

Пожалуйста, посетите byjg/migration-cli, чтобы получить больше информации о Migration CLI.

Поддерживаемые базы данных

База данных Драйвер Строка соединения
Sqlite pdo_sqlite sqlite:///path/to/file
MySql/MariaDb pdo_mysql mysql://username:password@hostname:port/database
Postgres pdo_pgsql pgsql://username:password@hostname:port/database
Sql Server pdo_dblib, pdo_sysbase Linux dblib://username:password@hostname:port/database
Sql Server pdo_sqlsrv Windows sqlsrv://username:password@hostname:port/database

Как это работает?

Миграция базы данных использует ЧИСТЫЙ SQL для управления версионностью базы данных. Чтобы это заработало, вам необходимо:

SQL скрипты

Скрипты делятся на три группы:

Директория скриптов:

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

Многоразвивающая среда

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

В этом случае вы добавляете суффикс "-dev" после номера версии.

Смотрите сценарий:

В обоих случаях разработчики создадут файл под названием 43-dev.sql. Оба разработчика будут мигрировать UP и DOWN без проблем, и ваша локальная версия будет 43.

Но разработчик 1 объединил ваши изменения и создал окончательную версию 43.sql (git mv 43-dev.sql 43.sql). Если разработчик 2 обновит свою локальную ветку, он получит файл 43.sql (от dev 1) и ваш файл 43-dev.sql. Если он попытается мигрировать UP или DOWN, скрипт миграции упадет и предупредит его, что есть ДВЕ версии 43. В этом случае разработчик 2 должен будет обновить ваш файл до 44-dev.sql и продолжить работать, пока не объединит ваши изменения и не сгенерирует окончательную версию.

Использование PHP API и интеграция его в ваши проекты

Основное использование:

Смотрите пример:

<?php
// Создайте URI соединения
// Подробнее: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');

// Зарегистрируйте Базу данных или Базы данных, которые могут обрабатывать этот URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);

// Создайте экземпляр миграции
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');

// Добавьте функцию обратного вызова прогресса для получения информации о выполнении
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
    echo "$action, $currentVersion, ${fileInfo['description']}\n";
});

// Восстановите базу данных с использованием скрипта "base.sql"
// и выполните ВСЕ существующие скрипты для повышения версии базы данных до последней версии
$migration->reset();

// Выполните ВСЕ существующие скрипты для вверх или вниз версии базы данных
// от текущей версии до номера $version;
// Если номер версии не указан, мигрируйте до последней версии базы данных
$migration->update($version = null);

Объект миграции контролирует версию базы данных.

Создание контроля версий в вашем проекте

<?php
// Зарегистрируйте Базу данных или Базы данных, которые могут обрабатывать этот URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);

// Создайте экземпляр миграции
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');

// Эта команда создаст таблицу версий в вашей базе данных
$migration->createVersion();

Получение текущей версии

<?php
$migration->getCurrentVersion();

Добавить обратный вызов для контроля прогресса

<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
    echo "Выполняем команду: $command на версии $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});

Получение экземпляра драйвера Db

<?php
$migration->getDbDriver();

Чтобы использовать это, пожалуйста, посетите: https://github.com/byjg/anydataset-db

Избежание частичной миграции (недоступно для MySQL)

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

Таблица миграции будет иметь статус partial up или partial down, и ее необходимо исправить вручную перед тем, как снова мигрировать.

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

Чтобы включить эту функцию, вам необходимо вызвать метод withTransactionEnabled, передав true как параметр:

<?php
$migration->withTransactionEnabled(true);

ПРИМЕЧАНИЕ: Эта функция недоступна для MySQL, так как она не поддерживает DDL команды внутри транзакции. Если вы используете этот метод с MySQL, миграция проигнорирует его без уведомления. Дополнительная информация: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html

Советы по написанию SQL миграций для Postgres

О создании триггеров и SQL функций

-- ДЕЛАЙТЕ
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Проверьте, что empname и зарплата указаны
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname не может быть пустым'; -- не имеет значения, пустые ли эти комментарии
        END IF; --
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% не может иметь пустую зарплату', NEW.empname; --
        END IF; --

        -- Кто работает на нас, когда они должны за это платить?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% не может иметь отрицательную зарплату', NEW.empname; --
        END IF; --

        -- Запомните, кто изменял зарплату, когда
        NEW.last_date := current_timestamp; --
        NEW.last_user := current_user; --
        RETURN NEW; --
    END; --
$emp_stamp$ LANGUAGE plpgsql;

-- НЕ ДЕЛАЙТЕ
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Проверьте, что empname и зарплата указаны
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname не может быть пустым';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% не может иметь пустую зарплату', NEW.empname;
        END IF;

        -- Кто работает на нас, когда они должны за это платить?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% не может иметь отрицательную зарплату', NEW.empname;
        END IF;

        -- Запомните, кто изменял зарплату, когда
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

Поскольку уровень абстракции базы данных PDO не может выполнять пачки SQL операторов, когда byjg/migration читает файл миграции, он должен разбить все содержимое SQL файла по точкам с запятой и выполнять операторы один за другим. Однако существует один вид оператора, который может содержать несколько точек с запятой между его телом: функции.

Чтобы иметь возможность корректно парсить функции, byjg/migration 2.1.0 начал разбивать файлы миграции по последовательности точка с запятой + EOL вместо обычной точки с запятой. Таким образом, если вы добавите пустой комментарий после каждой внутренней точки с запятой в определении функции, byjg/migration сможет корректно его разобрать.

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

Избегайте символа двоеточия (:)

-- ДЕЛАЙТЕ
CREATE TABLE bookings (
  booking_id UUID PRIMARY KEY,
  booked_at  TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
  check_in   DATE NOT NULL
);

-- НЕ ДЕЛАЙТЕ
CREATE TABLE bookings (
  booking_id UUID PRIMARY KEY,
  booked_at  TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
  check_in   DATE NOT NULL
);

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

Например, операторы PostgreSQL могут использовать :: для приведения значений между типами. С другой стороны, PDO воспримет это как недопустимый именованный параметр в недопустимом контексте и выдаст ошибку, когда попытается его выполнить.

Единственный способ исправить это несоответствие — это полностью избегать двоеточий (в этом случае у PostgreSQL также есть альтернативный синтаксис: CAST(value AS type)).

Используйте SQL редактор

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

Обработка различных миграций внутри одной схемы

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

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

<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");

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

Мы действительно рекомендуем не использовать эту функцию. Рекомендация — одна миграция для одной схемы.

Запуск модульных тестов

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

vendor/bin/phpunit

Запуск тестов базы данных

Запуск интеграционных тестов требует, чтобы базы данных были запущены и работали. Мы предоставили базовый docker-compose.yml, и вы можете использовать его для запуска баз данных для тестов.

Запуск баз данных

docker-compose up -d postgres mysql mssql

Запуск тестов

vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*

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

export MYSQL_TEST_HOST=localhost     # по умолчанию localhost
export MYSQL_PASSWORD=newpassword    # используйте '.' если хотите иметь пустой пароль
export PSQL_TEST_HOST=localhost      # по умолчанию localhost
export PSQL_PASSWORD=newpassword     # используйте '.' если хотите иметь пустой пароль
export MSSQL_TEST_HOST=localhost     # по умолчанию localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db      # по умолчанию /tmp/test.db

Awesome-plugins/session

FlightPHP Сессия - Легковесный файловый обработчик сессий

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

Если вы хотите использовать базу данных, ознакомьтесь с плагином ghostff/session, который имеет многие из этих функций, но с базой данных.

Посетите Github репозиторий для полного исходного кода и деталей.

Установка

Установите плагин через Composer:

composer require flightphp/session

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

Вот простой пример того, как использовать плагин flightphp/session в вашем приложении Flight:

require 'vendor/autoload.php';

use flight\Session;

$app = Flight::app();

// Регистрация сервиса сессии
$app->register('session', Session::class);

// Пример маршрута с использованием сессии
Flight::route('/login', function() {
    $session = Flight::session();
    $session->set('user_id', 123);
    $session->set('username', 'johndoe');
    $session->set('is_admin', false);

    echo $session->get('username'); // Вывод: johndoe
    echo $session->get('preferences', 'default_theme'); // Вывод: default_theme

    if ($session->get('user_id')) {
        Flight::json(['message' => 'Пользователь вошел в систему!', 'user_id' => $session->get('user_id')]);
    }
});

Flight::route('/logout', function() {
    $session = Flight::session();
    $session->clear(); // Очистить все данные сессии
    Flight::json(['message' => 'Успешный выход']);
});

Flight::start();

Ключевые моменты

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

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

$app->register('session', Session::class, [
    'save_path' => '/custom/path/to/sessions',         // Каталог для файлов сессий
    'encryption_key' => 'a-secure-32-byte-key-here',   // Включить шифрование (рекомендуется 32 байта для AES-256-CBC)
    'auto_commit' => false,                            // Отключить автоматический коммит для ручного управления
    'start_session' => true,                           // Автоматически начинать сессию (по умолчанию: true)
    'test_mode' => false                               // Включить тестовый режим для разработки
]);

Параметры конфигурации

Параметр Описание Значение по умолчанию
save_path Каталог, где хранятся файлы сессий sys_get_temp_dir() . '/flight_sessions'
encryption_key Ключ для шифрования AES-256-CBC (опционально) null (без шифрования)
auto_commit Автоматически сохранять данные сессии при завершении работы true
start_session Автоматически начинать сессию true
test_mode Запуск в тестовом режиме без влияния на PHP сессии false
test_session_id Пользовательский идентификатор сессии для тестового режима (опционально) Генерируется случайно, если не установлен

Расширенное использование

Ручной коммит

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

$app->register('session', Session::class, ['auto_commit' => false]);

Flight::route('/update', function() {
    $session = Flight::session();
    $session->set('key', 'value');
    $session->commit(); // Явно сохранить изменения
});

Безопасность сессии с шифрованием

Включите шифрование для конфиденциальных данных:

$app->register('session', Session::class, [
    'encryption_key' => 'your-32-byte-secret-key-here'
]);

Flight::route('/secure', function() {
    $session = Flight::session();
    $session->set('credit_card', '4111-1111-1111-1111'); // Шифруется автоматически
    echo $session->get('credit_card'); // Дешифруется при получении
});

Регенация сессии

Регенерируйте идентификатор сессии для безопасности (например, после входа в систему):

Flight::route('/post-login', function() {
    $session = Flight::session();
    $session->regenerate(); // Новый ID, сохранить данные
    // ИЛИ
    $session->regenerate(true); // Новый ID, удалить старые данные
});

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

Защитите маршруты с помощью аутентификации на основе сессий:

Flight::route('/admin', function() {
    Flight::json(['message' => 'Добро пожаловать в панель управления']);
})->addMiddleware(function() {
    $session = Flight::session();
    if (!$session->get('is_admin')) {
        Flight::halt(403, 'Доступ запрещен');
    }
});

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

Методы

Класс Session предоставляет эти методы:

Все методы, кроме get() и id(), возвращают экземпляр Session для связывания.

Зачем использовать этот плагин?

Технические детали

Участие

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

Лицензия

Этот плагин лицензирован под MIT License. См. Github репозиторий для получения деталей.

Awesome-plugins/runway

Взлетная полоса

Взлетная полоса — это приложение CLI, которое помогает управлять приложениями Flight. Он может генерировать контроллеры, отображать все маршруты и многое другое. Он основан на отличной библиотеке adhocore/php-cli.

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

Установка

Установите через composer.

composer require flightphp/runway

Основная настройка

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

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

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

  1. Если вы используете каркас проекта, вы можете запустить php runway [команда] из корня вашего проекта.
  2. Если вы используете Взлетную полосу как пакет, установленный через composer, вы можете запустить vendor/bin/runway [команда] из корня вашего проекта.

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

php runway routes --help

Вот несколько примеров:

Создание контроллера

На основе конфигурации в вашем файле .runway.json по умолчанию контроллер будет создан для вас в каталоге app/controllers/.

php runway make:controller MyController

Создание модели Active Record

На основе конфигурации в вашем файле .runway.json по умолчанию модель будет создана для вас в каталоге app/records/.

php runway make:record users

Если у вас, например, есть таблица users с такой схемой: id, name, email, created_at, updated_at, будет создан файл, подобный следующему, в файле app/records/UserRecord.php:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Класс Active Record для таблицы пользователей.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 * 
 * @property int $id
 * @property string $name
 * @property string $email
 * @property string $created_at
 * @property string $updated_at
 * // здесь вы также можете добавить отношения после их определения в массиве $relations
 * @property CompanyRecord $company Пример отношения
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Установите отношения для модели
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [];

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

Отображение всех маршрутов

Это отобразит все маршруты, которые в настоящее время зарегистрированы в Flight.

php runway routes

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

# Отобразить только GET маршруты
php runway routes --get

# Отобразить только POST маршруты
php runway routes --post

# и т.д.

Настройка Взлетной полосы

Если вы создаете пакет для Flight или хотите добавить свои собственные команды в свой проект, вы можете сделать это, создав каталог src/commands/, flight/commands/, app/commands/ или commands/ для вашего проекта/пакета.

Для создания команды просто расширьте класс AbstractBaseCommand и реализуйте, как минимум, метод __construct и метод execute.

<?php

declare(strict_types=1);

namespace flight\commands;

class ExampleCommand extends AbstractBaseCommand
{
    /**
     * Конструктор
     *
     * @param array<string,mixed> $config JSON конфигурация из .runway-config.json
     */
    public function __construct(array $config)
    {
        parent::__construct('make:example', 'Создать пример для документации', $config);
        $this->argument('<funny-gif>', 'Имя смешной гифки');
    }

    /**
     * Выполняет функцию
     *
     * @return void
     */
    public function execute(string $controller)
    {
        $io = $this->app()->io();

        $io->info('Создание примера...');

        // Сделайте здесь что-то

        $io->ok('Пример создан!');
    }
}

Смотрите Документация adhocore/php-cli для получения дополнительной информации о том, как создавать свои собственные команды в вашем приложении Flight!

Awesome-plugins/tracy_extensions

Tracy Flight Panel Extensions

Это набор расширений, чтобы работа с Flight была немного более обширной.

Это Панель

Панель Flight

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

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

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

Установка

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

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

Вам нужно сделать очень небольшую настройку, чтобы начать использовать это. Вам нужно инициировать отладчик Tracy перед использованием этого https://tracy.nette.org/en/guide:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// код инициализации
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// Возможно, вам нужно указать ваше окружение с помощью Debugger::enable(Debugger::DEVELOPMENT)

// если вы используете подключения к базе данных в вашем приложении, есть 
// обязательная оболочка PDO для использования ТОЛЬКО В РАЗРАБОТКЕ (пожалуйста, не в продакшн!)
// У него те же параметры, что и обычное соединение PDO
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// или если вы присоединяете это к фреймворку Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// теперь при каждом запросе он будет записывать время, запрос и параметры

// Это соединяет точки
if(Debugger::$showBar === true) {
    // Это должно быть false, иначе Tracy не сможет отображаться :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// еще код

Flight::start();

Дополнительная Конфигурация

Данные сеанса

Если у вас есть пользовательский обработчик сеансов (например, ghostff/session), вы можете передать любой массив данных сеанса в Tracy, и он автоматически выведет его для вас. Вы передаете это с ключом session_data вторым параметром конструктора TracyExtensionLoader.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

if(Debugger::$showBar === true) {
    // Это должно быть false, иначе Tracy не сможет отображаться :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// маршруты и другие вещи...

Flight::start();

Latte

Если у вас установлен Latte в вашем проекте, вы можете использовать панель Latte для анализа ваших шаблонов. Вы можете передать экземпляр Latte в конструктор TracyExtensionLoader с ключом latte вторым параметром.



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    // здесь вы добавляете Панель Latte в Tracy
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // Это должно быть false, иначе Tracy не сможет отображаться :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/tracy

Tracy

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

Установка

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

composer require tracy/tracy

Базовая конфигурация

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


require 'vendor/autoload.php';

use Tracy\Debugger;

// Включение Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // иногда вам придется быть явным (также Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // также можно предоставить массив IP-адресов
// Здесь будут регистрироваться ошибки и исключения. Убедитесь, что этот каталог существует и доступен для записи.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // показывать все ошибки
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // все ошибки, кроме устаревших уведомлений
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // если панель отладки видима, тогда длина содержимого не может быть установлена Flight

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

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

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

Awesome-plugins/active_record

Flight Active Record

Активная запись – это отображение сущности базы данных на объект PHP. Проще говоря, если у вас есть таблица пользователей в вашей базе данных, вы можете "перевести" строку в этой таблице на класс User и объект $user в вашем коде. См. основной пример.

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

Основной пример

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

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

Теперь вы можете настроить новый класс для представления этой таблицы:

/**
 * Класс ActiveRecord обычно единственное число
 * 
 * Настоятельно рекомендуется добавлять свойства таблицы в виде комментариев здесь
 * 
 * @property int    $id
 * @property string $name
 * @property string $password
 */ 
class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        // вы можете установить это таким образом
        parent::__construct($database_connection, 'users');
        // или таким образом
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Теперь смотрите, как происходит магия!

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

// для mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');

// или mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// или mysqli с созданием без объекта
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');

$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('некоторый классный пароль');
$user->insert();
// или $user->save();

echo $user->id; // 1

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

echo $user->id; // 2

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

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

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

$users = $user->findAll();

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

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

Видите, как это весело? Давайте установим это и начнем!

Установка

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

composer require flightphp/active-record 

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

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

Отдельно

Просто убедитесь, что вы передаете подключение PDO в конструктор.

$pdo_connection = new PDO('sqlite:test.db'); // это просто для примера, вы, вероятно, используете реальное подключение к базе данных

$User = new User($pdo_connection);

Не хотите всегда устанавливать ваше подключение к базе данных в конструкторе? См. Управление подключением к базе данных для других идей!

Зарегистрировать как метод в Flight

Если вы используете PHP-фреймворк Flight, вы можете зарегистрировать класс ActiveRecord как сервис, но вам честно не обязательно это делать.

Flight::register('user', 'User', [ $pdo_connection ]);

// тогда вы можете использовать это так в контроллере, функции и т.д.

Flight::user()->find(1);

Методы runway

runway – это CLI инструмент для Flight, который имеет специальную команду для этой библиотеки.

# Использование
php runway make:record database_table_name [class_name]

# Пример
php runway make:record users

Это создаст новый класс в директории app/records/ как UserRecord.php со следующим содержимым:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Класс ActiveRecord для таблицы пользователей.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 *
 * @property int $id
 * @property string $username
 * @property string $email
 * @property string $password_hash
 * @property string $created_dt
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Установите отношения для модели
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [
        // 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
    ];

    /**
     * Конструктор
     * @param mixed $databaseConnection Подключение к базе данных
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

Функции CRUD

find($id = null) : boolean|ActiveRecord

Находит одну запись и присваивает ее текущему объекту. Если вы передаете $id какого-либо рода, это будет выполнять поиск по первичному ключу с этим значением. Если ничего не передается, это просто найдет первую запись в таблице.

Кроме того, вы можете передать ему другие вспомогательные методы для запроса к вашей таблице.

// найти запись с некоторыми условиями заранее
$user->notNull('password')->orderBy('id DESC')->find();

// найти запись по конкретному id
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

Находит все записи в таблице, которую вы указываете.

$user->findAll();

isHydrated(): boolean (v0.4.0)

Возвращает true, если текущая запись была гидратирована (извлечена из базы данных).

$user->find(1);
// если запись найдена с данными...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

Вставляет текущую запись в базу данных.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Первичные ключи на текстовой основе

Если у вас есть первичный ключ на текстовой основе (например, UUID), вы можете установить значение первичного ключа перед вставкой одним из двух способов.

$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // или $user->save();

или вы можете позволить первичному ключу автоматически генерироваться для вас через события.

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // вы также можете установить первичный ключ таким образом вместо массива выше.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // или как вам нужно сгенерировать ваши уникальные идентификаторы
    }
}

Если вы не установите первичный ключ перед вставкой, он будет установлен на rowid, и база данных сгенерирует его для вас, но он не будет храниться, потому что это поле может не существовать в вашей таблице. Вот почему рекомендуется использовать событие для автоматического управления этим.

update(): boolean|ActiveRecord

Обновляет текущую запись в базе данных.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

Вставляет или обновляет текущую запись в базе данных. Если у записи есть id, она будет обновлена, в противном случае будет вставлена.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

Примечание: Если у вас есть определенные отношения в классе, они также будут рекурсивно сохранены, если они были определены, созданы и имеют измененные данные для обновления. (v0.4.0 и выше)

delete(): boolean

Удаляет текущую запись из базы данных.

$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();

Вы также можете удалить несколько записей, исполняя поиск заранее.

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

Грязные данные относятся к данным, которые были изменены в записи.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// на данный момент ничего не "грязное".

$user->email = 'test@example.com'; // теперь 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@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

Сбрасывает текущую запись в ее начальное состояние. Это действительно полезно использовать в поведениях типа цикла. Если вы передадите true, он также сбросит данные запроса, которые использовались для поиска текущего объекта (поведение по умолчанию).

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // начинаем с чистого листа
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

После выполнения метода find(), findAll(), insert(), update() или save() вы можете получить SQL, который был построен, и использовать его для отладки.

Методы SQL Запросов

select(string $field1 [, string $field2 ... ])

Вы можете выбрать только несколько столбцов в таблице, если хотите (это более производительно на действительно широких таблицах с большим количеством столбцов)

$user->select('id', 'name')->find();

from(string $table)

Вы вполне можете выбрать другую таблицу! Почему бы и нет?!

$user->select('id', 'name')->from('user')->find();

join(string $table_name, string $join_condition)

Вы даже можете присоединиться к другой таблице в базе данных.

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

Вы можете установить некоторые пользовательские аргументы where (вы не можете устанавливать параметры в этом условии where)

$user->where('id=1 AND name="demo"')->find();

Примечание по безопасности - Вам может возникнуть желание сделать что-то вроде $user->where("id = '{$id}' AND name = '{$name}'")->find();. Пожалуйста, НЕ ДЕЛАЙТЕ ЭТОГО!!! Это подвержено тому, что называется атаками SQL-инъекций. В Интернете есть много статей, пожалуйста, поищите "sql injection attacks php", и вы найдёте много статей на эту тему. Правильный способ обработки этого с помощью этой библиотеки вместо этого метода where(), вы бы сделали что-то более вроде $user->eq('id', $id)->eq('name', $name)->find(); Если вам абсолютно необходимо это делать, библиотека PDO имеет $pdo->quote($var), чтобы экранировать это для вас. Только после того, как вы используете quote(), вы можете использовать это в операторе where().

group(string $group_by_statement)/groupBy(string $group_by_statement)

Группируйте ваши результаты по определенному условию.

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

Сортируйте возвращаемый запрос определённым образом.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Ограничьте количество возвращаемых записей. Если задано второе целое число, оно будет сдвинуто, ограничение также как в SQL.

$user->orderby('name DESC')->limit(0, 10)->findAll();

Условия WHERE

equal(string $field, mixed $value) / eq(string $field, mixed $value)

Где field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

Где field <> $value

$user->ne('id', 1)->find();

isNull(string $field)

Где field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

Где field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

Где field > $value

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

Где field < $value

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

Где field >= $value

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

Где field <= $value

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

Где field LIKE $value или field NOT LIKE $value

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

Где field IN($value) или field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

Где field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

Условия ИЛИ

Возможно, вы захотите обернуть ваши условия в оператор ИЛИ. Это делается либо с помощью методов startWrap() и endWrap(), либо путем заполнения 3-го параметра условия после поля и значения.

// Метод 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Это будет эквивалентно `id = 1 AND (name = 'demo' OR name = 'test')`

// Метод 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Это будет эквивалентно `id = 1 OR name = 'demo'`

Отношения

Вы можете установить несколько видов отношений, используя эту библиотеку. Вы можете установить отношения один->много и один->один между таблицами. Это требует небольшой дополнительной настройки в классе заранее.

Установка массива $relations не сложна, но угадать правильный синтаксис может быть сложно.

protected array $relations = [
    // вы можете назвать ключ как угодно. Имя ActiveRecord, вероятно, хорошее. Например: user, contact, client
    'user' => [
        // обязательно
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // это тип отношения

        // обязательно
        'Some_Class', // это "другой" ActiveRecord, на который это будет ссылаться

        // обязательно
        // в зависимости от типа отношения
        // self::HAS_ONE = внешний ключ, который ссылается на соединение
        // self::HAS_MANY = внешний ключ, который ссылается на соединение
        // self::BELONGS_TO = локальный ключ, который ссылается на соединение
        'local_or_foreign_key',
        // просто 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) {
            // сделайте что-то классное, как и в afterFind()
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

Действительно полезно, если вам нужно установить некоторые значения по умолчанию каждый раз.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // задайте некоторые надежные значения по умолчанию
        if(!$self->created_date) {
            $self->created_date = gmdate('Y-m-d');
        }

        if(!$self->password) {
            $self->password = password_hash((string) microtime(true));
        }
    } 
}

afterInsert(ActiveRecord $ActiveRecord)

Возможно, у вас есть случай использования для изменения данных послеINSERT?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // делайте что хотите
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // или что-то еще...
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

Действительно полезно, если вам нужно установить некоторые значения по умолчанию каждый раз при обновлении.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeUpdate(self $self) {
        // задайте некоторые надежные значения по умолчанию
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Возможно, у вас есть случай использования для изменения данных после его обновления?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterUpdate(self $self) {
        // делайте что хотите
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // или что-то еще...
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

Это полезно, если вы хотите, чтобы события происходили как при вставках, так и при обновлениях. Я сэкономлю вам длинное объяснение, но я уверен, что вы можете догадаться, что это такое.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeSave(self $self) {
        $self->last_updated = gmdate('Y-m-d H:i:s');
    } 
}

beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)

Не уверен, что вы хотите сделать здесь, но никаких предвзятостей! Дерзайте!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo 'Он был смелым солдатом... :cry-face:';
    } 
}

Управление подключением к базе данных

Когда вы используете эту библиотеку, вы можете установить подключение к базе данных несколькими способами. Вы можете установить подключение в конструкторе, вы можете установить его через переменную конфигурации $config['connection'] или вы можете установить его через setDatabaseConnection() (v0.4.1).

$pdo_connection = new PDO('sqlite:test.db'); // например
$user = new User($pdo_connection);
// или
$user = new User(null, [ 'connection' => $pdo_connection ]);
// или
$user = new User();
$user->setDatabaseConnection($pdo_connection);

Если вы хотите избежать постоянного указания $database_connection каждый раз, когда вы вызываете активную запись, есть способы обойти это!

// index.php или bootstrap.php
// Установите это как зарегистрированный класс в Flight
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

// User.php
class User extends flight\ActiveRecord {

    public function __construct(array $config = [])
    {
        $database_connection = $config['connection'] ?? Flight::db();
        parent::__construct($database_connection, 'users', $config);
    }
}

// И теперь, без аргументов не требуется!
$user = new User();

Примечание: Если вы планируете unit-тестирование, делать это может создать некоторые проблемы с unit-тестами, но в целом, потому что вы можете инъектировать ваше подключение с помощью setDatabaseConnection() или $config['connection'], это не слишком плохо.

Если вам нужно обновить подключение к базе данных, например, если вы запускаете долгое CLI-скрипт и вам нужно периодически обновлять подключение, вы можете переустановить соединение с помощью $your_record->setDatabaseConnection($pdo_connection).

Участие

Пожалуйста, сделайте это. :D

Настройка

Когда вы участвуете, убедитесь, что вы выполняете команду composer test-coverage, чтобы поддерживать 100% покрытие тестами (это не истинное покрытие юнит-тестами, больше похоже на интеграционное тестирование).

Также убедитесь, что вы выполняете composer beautify и composer phpcs, чтобы исправить любые ошибки линтинга.

Лицензия

MIT

Awesome-plugins/latte

Latte

Latte - это полнофункциональный движок шаблонов, который очень прост в использовании и ближе к синтаксису PHP, чем Twig или Smarty. Также очень легко расширяем и добавляем собственные фильтры и функции.

Установка

Установите с помощью composer.

composer require latte/latte

Основная конфигурация

Есть несколько основных опций конфигурации, с которых можно начать. Вы можете узнать больше о них в Документации по Latte.

use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {

    // Здесь Latte будет кэшировать ваши шаблоны для ускорения работы
    // Одна из хороших вещей в Latte заключается в том, что он автоматически обновляет ваш
    // кэш при внесении изменений в ваши шаблоны!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    // Скажите Latte, где будет базовый каталог для ваших представлений.
    // $app->get('flight.views.path') устанавливается в файле config.php
    //   Вы также можете просто сделать что-то вроде `__DIR__ . '/../views/'`
    $latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});

Простой пример макета

Вот простой пример файла макета. Этот файл будет использоваться для оформления всех ваших других представлений.

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
    <head>
        <title>{$title ? $title . ' - '}Мое приложение</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- ваши элементы навигации здесь -->
            </nav>
        </header>
        <div id="content">
            <!-- Вот где волшебство -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; Авторские права
        </div>
    </body>
</html>

И теперь у нас есть ваш файл, который будет отображаться внутри этого блока контента:

<!-- app/views/home.latte -->
<!-- Это говорит Latte, что этот файл "внутри" файла layout.latte -->
{extends layout.latte}

<!-- Это содержимое, которое будет отображаться в макете внутри блока контента -->
{block content}
    <h1>Домашняя страница</h1>
    <p>Добро пожаловать в мое приложение!</p>
{/block}

Затем, когда вы будете рендерить это внутри вашей функции или контроллера, вы сделаете что-то вроде этого:

// простой маршрут
Flight::route('/', function () {
    Flight::latte()->render('home.latte', [
        'title' => 'Домашняя страница'
    ]);
});

// или если вы используете контроллер
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::latte()->render('home.latte', [
            'title' => 'Домашняя страница'
        ]);
    }
}

Смотрите Документацию по Latte для получения дополнительной информации о том, как использовать Latte в полной мере!

Awesome-plugins/awesome_plugins

Потрясающие Плагины

Flight невероятно расширяемый. Есть множество плагинов, которые могут быть использованы для добавления функциональности в ваше приложение Flight. Некоторые официально поддерживаются командой Flight, а другие являются микро/легкими библиотеками, чтобы помочь вам начать.

Документация API

Документация API имеет решающее значение для любого API. Она помогает разработчикам понять, как взаимодействовать с вашим API и чего ожидать в ответ. Существуют несколько инструментов, доступных для помощи в генерации документации API для ваших проектов Flight.

Аутентификация/Авторизация

Аутентификация и Авторизация имеют решающее значение для любого приложения, которое требует контроля доступа.

Кэширование

Кэширование - отличный способ ускорить ваше приложение. Существует несколько библиотек кэширования, которые могут быть использованы с Flight.

CLI

CLI-приложения - отличный способ взаимодействовать с вашим приложением. Вы можете использовать их для генерации контроллеров, отображения всех маршрутов и многого другого.

Куки

Куки - отличный способ хранения небольших данных на стороне клиента. Их можно использовать для хранения пользовательских предпочтений, настроек приложения и многого другого.

Отладка

Отладка имеет решающее значение при разработке в вашей локальной среде. Существует несколько плагинов, которые могут улучшить ваш опыт отладки.

Базы данных

Базы данных являются основой большинства приложений. Это то, как вы храните и извлекаете данные. Некоторые библиотека баз данных просто обертки для написания запросов, а некоторые являются полноценными ORM.

Шифрование

Шифрование имеет решающее значение для любого приложения, которое хранит конфиденциальные данные. Шифрование и дешифрование данных не так уж сложно, но правильное хранение ключа шифрования может быть сложным. Самое важное - никогда не хранить ваш ключ шифрования в публичном каталоге или не коммитить его в ваше репозиторий кода.

Очередь заданий

Очереди заданий очень полезны для асинхронной обработки задач. Это может быть отправка электронных писем, обработка изображений или что угодно, что не требует выполнения в реальном времени.

Сессия

Сессии не очень полезны для API, но для создания веб-приложения сессии могут быть решающими для поддержания состояния и информации о входе.

Шаблонизация

Шаблонизация является основой любого веб-приложения с пользовательским интерфейсом. Существует несколько движков шаблонизации, которые могут быть использованы с Flight.

Участие

Есть плагин, которым вы хотели бы поделиться? Отправьте запрос на внесение изменений, чтобы добавить его в список!

Media

Медиа

Мы постарались собрать все, что можем, о различных типах медиа в интернете, связанных с Flight. Ниже приведены различные ресурсы, которые вы можете использовать, чтобы узнать больше о Flight.

Статьи и материалы

Видеоролики и руководства

Examples

Нужен быстрый старт?

У вас есть два варианта, чтобы начать новый проект на Flight:

Примеры, предложенные сообществом:

Нужна немного вдохновения?

Хотя эти проекты не официально спонсированы командой Flight, они могут дать вам идеи о том, как структурировать собственные проекты, построенные с использованием Flight!

Хотите поделиться своим примером?

Если у вас есть проект, которым вы хотите поделиться, пожалуйста, отправьте запрос на слияние, чтобы добавить его в этот список!

Install/install

Установка

Загрузка файлов

Убедитесь, что у вас установлен PHP на вашей системе. Если нет, нажмите здесь для получения инструкций по установке на вашей системе.

Если вы используете Composer, вы можете выполнить следующую команду:

composer require flightphp/core

ИЛИ вы можете скачать файлы напрямую и извлечь их в ваш каталог веб-сайта.

Настройка вашего веб-сервера

Встроенный веб-сервер PHP

Это, безусловно, самый простой способ запустить приложение и использовать даже SQLite для базы данных. Просто выполните следующую команду после установки PHP:

php -S localhost:8000

Затем откройте свой браузер и перейдите по адресу http://localhost:8000.

Если вы хотите изменить корневой каталог вашего проекта на другой каталог (например, ваш проект - ~/myproject, а корневой каталог -~/myproject/public/), вы можете выполнить следующую команду после того, как вы находитесь в каталоге ~/myproject:

php -S localhost:8000 -t public/

Затем откройте свой браузер и перейдите по адресу http://localhost:8000.

Apache

Убедитесь, что Apache уже установлен на вашей системе. Если нет, найдите, как установить Apache на вашей системе через поиск в Google.

Для Apache измените ваш файл .htaccess следующим образом:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Примечание: Если вам нужно использовать flight в подкаталоге, добавьте строку RewriteBase /subdir/ сразу после RewriteEngine On.

Примечание: Если вы хотите защитить все файлы сервера, такие как файл db или env. Поместите это в ваш файл .htaccess:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Убедитесь, что Nginx уже установлен на вашей системе. Если нет, найдите, как установить Nginx на вашей системе через поиск в Google.

Для Nginx добавьте следующее в ваше объявление сервера:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Создание вашего файла index.php

<?php

// Если вы используете Composer, подключите автозагрузчик.
require 'vendor/autoload.php';
// если вы не используете Composer, загрузите фреймворк непосредственно
// require 'flight/Flight.php';

// Затем определите маршрут и назначьте функцию для обработки запроса.
Flight::route('/', function () {
  echo 'hello world!';
});

// Наконец, запустите фреймворк.
Flight::start();

Установка PHP

Если у вас уже установлен php на вашей системе, переходите к разделу загрузки файлов.

Успехов! Вот инструкции по установке PHP на macOS, Windows 10/11, Ubuntu и Rocky Linux. Я также предоставлю информацию о том, как установить различные версии PHP.

macOS

Установка PHP с помощью Homebrew

  1. Установите Homebrew (если его нет):

    • Откройте терминал и выполните:
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Установите PHP:

    • Установите последнюю версию:
      brew install php
    • Чтобы установить конкретную версию, например, PHP 8.1:
      brew tap shivammathur/php
      brew install shivammathur/php/php@8.1
  3. Переключение между версиями PHP:

    • Отсоедините текущую версию и привяжите желаемую версию:
      brew unlink php
      brew link --overwrite --force php@8.1
    • Проверьте установленную версию:
      php -v

Windows 10/11

Установка PHP вручную

  1. Скачайте PHP:

    • Перейдите на PHP для Windows и загрузите последнюю или конкретную версию (например, 7.4, 8.0) как zip-архив без поддержки потоков.
  2. Разархивируйте PHP:

    • Разархивируйте загруженный zip-файл в C:\php.
  3. Добавьте PHP в системный путь:

    • Перейдите в Свойства системы > Переменные среды.
    • В разделе Системные переменные найдите Path и нажмите Изменить.
    • Добавьте путь C:\php (или куда бы вы не разархивировали PHP).
    • Нажмите OK, чтобы закрыть все окна.
  4. Настройте PHP:

    • Скопируйте php.ini-development в php.ini.
    • Отредактируйте php.ini для настройки PHP по необходимости (например, установка extension_dir, включение расширений).
  5. Проверьте установку PHP:

    • Откройте командную строку и выполните:
      php -v

Установка нескольких версий PHP

  1. Повторите вышеперечисленные шаги для каждой версии, помещая каждую в отдельный каталог (например, C:\php7, C:\php8).

  2. Переключайтесь между версиями, изменяя системную переменную PATH, указывающую на каталог нужной версии.

Ubuntu (20.04, 22.04 и др.)

Установка PHP с помощью apt

  1. Обновите списки пакетов:

    • Откройте терминал и выполните:
      sudo apt update
  2. Установите PHP:

    • Установите последнюю версию PHP:
      sudo apt install php
    • Чтобы установить конкретную версию, например, PHP 8.1:
      sudo apt install php8.1
  3. Установите дополнительные модули (по желанию):

    • Например, для установки поддержки MySQL:
      sudo apt install php8.1-mysql
  4. Переключение между версиями PHP:

    • Используйте update-alternatives:
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Проверьте установленную версию:

    • Выполните:
      php -v

Rocky Linux

Установка PHP с помощью yum/dnf

  1. Включите репозиторий EPEL:

    • Откройте терминал и выполните:
      sudo dnf install epel-release
  2. Установите репозиторий Remi:

    • Выполните:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. Установите PHP:

    • Чтобы установить версию по умолчанию:
      sudo dnf install php
    • Чтобы установить конкретную версию, например, PHP 7.4:
      sudo dnf module install php:remi-7.4
  4. Переключение между версиями PHP:

    • Используйте команду dnf module:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Проверьте установленную версию:

    • Выполните:
      php -v

Общие заметки

Guides

Гиды

Flight PHP разработан, чтобы быть простым, но мощным, а наши гайды помогут вам шаг за шагом создавать реальные приложения. Эти практические учебники проведут вас через полные проекты, чтобы продемонстрировать, как можно эффективно использовать Flight.

Официальные Гайды

Создание блога

Узнайте, как создать функциональное блог-приложение с помощью Flight PHP. Этот гайд проведет вас через:

Этот учебник идеально подходит для начинающих, которые хотят увидеть, как все элементы сочетаются в реальном приложении.

Неофициальные Гайды

Хотя эти гайды не поддерживаются официально командой Flight, они являются ценными ресурсами, созданными сообществом. Они охватывают различные темы и сценарии использования, предоставляя дополнительные сведения о использовании Flight PHP.

Создание RESTful API с помощью Flight Framework

Этот гайд проводит вас через создание RESTful API с использованием фреймворка Flight PHP. Он охватывает основы настройки API, определения маршрутов и возврата JSON-ответов.

Создание простого блога

Этот гайд проводит вас через создание базового блога с использованием фреймворка Flight PHP. Он фактически состоит из 2 частей: одна охватывает основы, а другая более продвинутые темы и доработки для блога, готового к производству.

Создание Pokémon API на PHP: Руководство для начинающих

Этот интересный гайд проводит вас через создание простого Pokémon API с использованием Flight PHP. Он охватывает основы настройки API, определения маршрутов и возврата JSON-ответов.

Вклад

Есть идея для гайда? Нашли ошибку? Мы приветствуем вклад! Наши гайды поддерживаются в репозитории документации FlightPHP.

Если вы создали что-то интересное с помощью Flight и хотите поделиться этим в качестве гайда, пожалуйста, отправьте запрос на внесение изменений. Деление своими знаниями способствует росту сообщества Flight.

Ищете документацию по API?

Если вы ищете специфическую информацию о ключевых функциях и методах Flight, ознакомьтесь с разделом "Обучение" нашей документации.