Learn

Узнайте о Flight

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

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

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

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

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

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

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

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

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

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

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

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

Запросы

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

Ответы

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

HTML-шаблоны

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

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

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

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

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

Расширение Flight

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

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

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

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

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

API фреймворка

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

Переход на v3

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

Learn/stopping

Остановка

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

Flight::halt();

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

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

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

Flight::stop();

Learn/errorhandling

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

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

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

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

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

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

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

Не Найдено

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

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

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

Learn/migrating_to_v3

Миграция на v3

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

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

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

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

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

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

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

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

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

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

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

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

    // Это тоже должно быть хорошо
    Flight::hello();
});

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

Включение поведения визуализации v2

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

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

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

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

// больше кода 

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

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

Learn/configuration

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

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

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

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

Следующий список содержит все доступные настройки конфигурации:

Переменные

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Не найдено

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

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

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

Learn/security

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

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

Заголовки

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

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

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

Межсайтовая фальсификация запросов (CSRF)

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

Настройка

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

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

Или вы можете использовать класс Middleware:

// 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 не имеет встроенной функциональности, но это легко решается с помощью промежуточного программного обеспечения или фильтров событий, аналогичных CSRF.

// app/middleware/CorsMiddleware.php

namespace app\middleware;

class CorsMiddleware
{
    public function before(array $params): void
    {
        $response = Flight::response();
        if (isset($_SERVER['HTTP_ORIGIN'])) {
            $this->allowOrigins();
            $response->header('Access-Control-Allow-Credentials: true');
            $response->header('Access-Control-Max-Age: 86400');
        }

        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
                $response->header(
                    'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS'
                );
            }
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
                $response->header(
                    "Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
                );
            }
            $response->send();
            exit(0);
        }
    }

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

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

// index.php или где у вас находятся ваши маршруты
Flight::route('/users', function() {
    $users = Flight::db()->fetchAll('SELECT * FROM users');
    Flight::json($users);
})->addMiddleware(new CorsMiddleware());

Заключение

Безопасность - это важно, и важно убедиться, что ваши веб-приложения безопасны. 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 = 'John Doe';
    }

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

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

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

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

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

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


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

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

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

Введение

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

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

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

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


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 в нескольких местах. Именно здесь пригодится DI контейнер.

Вот тот же пример с использованием DI контейнера (используя 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');

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

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('/путь', function() { echo ' Вот я!'; })->addMiddleware(function() {
    echo 'Промежуточное программное обеспечение в первую очередь!';
});

Flight::start();

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

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

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

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

class МоеПромежуточное {
    public function before($params) {
        echo 'Промежуточное программное обеспечение в первую очередь!';
    }

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

$МоеПромежуточное = new МоеПромежуточное();
Flight::route('/путь', function() { echo ' Вот я! '; })->addMiddleware($МоеПромежуточное); // также ->addMiddleware([ $МоеПромежуточное, $MоеПромежуточное2 ]);

Flight::start();

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

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

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


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

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

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


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

Learn/filtering

Фильтрация

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Hello Fred! Have a nice day!

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

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

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

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

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

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

Learn/requests

Запросы

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

$request = Flight::request();

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

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

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

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

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

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

Необработанное тело запроса

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

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

JSON-данные

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

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

Доступ к $_SERVER

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


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

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

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


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

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

Learn/frameworkmethods

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

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

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

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

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

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

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

Learn/api

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

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

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

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

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

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

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

Learn/why_frameworks

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

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

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

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

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

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

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

Это может выглядеть примерно так:

И Зачем это Важно?

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

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

Вот так?


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

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

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

Или так?


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

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

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

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

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

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

Запросы

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

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

Ответы

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

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

Learn/httpcaching

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

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

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

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

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

ETag

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

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

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

Learn/responses

Ответы

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

Отправка базового ответа

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


// Это отправит "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

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

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


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

JSON

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

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

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

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

Learn/frameworkinstance

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

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

require 'flight/autoload.php';

$app = Flight::app();

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

$app->start();

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

Learn/redirects

Редиректы

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

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

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

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

Learn/views

Виды

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

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

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

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

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

Привет, Bob!

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

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

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

Flight::render('hello');

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

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

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

Макеты

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

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

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

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

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

header.php:

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

body.php:

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

layout.php:

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

Вывод будет:

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

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

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

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

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

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

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

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

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

Learn/templates

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

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

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

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

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

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

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

Вывод будет:

Привет, Bob!

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

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

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

Flight::render('hello');

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

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

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

Макеты

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

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

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

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

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

header.php:

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

body.php:

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

layout.php:

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

Вывод будет:

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

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

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

Smarty

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

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

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

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

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

Для завершенности следует также переопределить метод отображения по умолчанию в Flight:

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

Latte

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


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

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

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

Learn/extending

Расширение

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

Если вам нужен DIC (Dependency Injection Container), перейдите на страницу Dependency Injection Container.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Зарегистрируйте ваш пользовательский класс
Flight::register('router', MyRouter::class);

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

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

Learn/json

JSON

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

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

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

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

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

my_func({"id":123});

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

Learn/autoloading

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

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

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

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

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

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

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

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


/**
 * public/index.php
 */

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

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

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

// Все автозагружаемые классы рекомендуется писать в формате Pascal Case (каждое слово с заглавной буквы, без пробелов)
// Требование - нельзя использовать подчеркивание в имени класса
class MyController {

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

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

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


/**
 * public/index.php
 */

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

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

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

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

// Все автозагружаемые классы рекомендуется писать в формате Pascal Case (каждое слово с заглавной буквы, без пробелов)
// Требование – нельзя использовать подчеркивание в имени класса
class MyController {

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

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


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

// пространство имен должно соответствовать структуре и регистру каталога (обратите внимание, что каталог UTILS состоит из заглавных букв
//     как в дереве файлов выше)
namespace app\UTILS;

class ArrayHelperUtil {

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

Install

Установка

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

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

composer require flightphp/core

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

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

Apache

Для Apache отредактируйте ваш файл .htaccess следующим образом:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Примечание: Если вам нужно использовать flight в подкаталоге, добавьте строку RewriteBase /subdir/ сразу после RewriteEngine On.

Примечание: Если вы хотите защитить все файлы сервера, такие как файл db или env. Поместите это в свой файл .htaccess:

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Для Nginx добавьте следующее в ваше объявление сервера:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Создайте файл index.php.

<?php

// Если вы используете Composer, требуется автозагрузчик.
require 'vendor/autoload.php';
// если вы не используете Composer, загружайте фреймворк напрямую
// require 'flight/Flight.php';

// Затем определите маршрут и назначьте функцию для обработки запроса.
Flight::route('/', function () {
  echo 'hello world!';
});

// Наконец, запустите фреймворк.
Flight::start();

License

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

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

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

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

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

About

Что такое Flight?

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

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

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

<?php

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

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

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

Flight::start();

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

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

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

Сообщество

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

Вклад

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

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

Требования

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

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

Лицензия

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

Awesome-plugins/php_cookie

Cookies

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

Установка

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

composer require overclokk/cookie

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

Использование так же просто, как регистрация нового метода в классе Flight.


use Overclokk\Cookie\Cookie;

/*
 * Установите в вашем файле bootstrap или public/index.php
 */

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

/**
 * ExampleController.php
 */

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

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

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

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

Awesome-plugins/php_encryption

Шифрование PHP

defuse/php-encryption - это библиотека, которая может быть использована для шифрования и дешифрования данных. Начать использование довольно просто для начала шифрования и дешифрования данных. У них есть отличное руководство, которое помогает объяснить основы использования библиотеки, а также важные аспекты безопасности, касающиеся шифрования.

Установка

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

composer require defuse/php-encryption

Настройка

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

vendor/bin/generate-defuse-key

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

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

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


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

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

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

// Метод дешифрования
Flight::map('decrypt', function($encrypted_data) {
    $encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
    try {
        $raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
        // Атака! Загружен неверный ключ или зашифрованный текст был изменен с момента его создания -- либо поврежден в базе данных, либо намеренно изменен Злодеем, пытающимся провести атаку.

        // ... обработайте этот случай так, чтобы он подходил для вашего приложения ...
    }
    return $raw_data;
});

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

Flight::route('/decrypt', function() {
    $encrypted_data = '...'; // Получите зашифрованные данные откуда-нибудь
    $decrypted_data = Flight::decrypt($encrypted_data);
    echo $decrypted_data;
});

Awesome-plugins/php_file_cache

Wruczek/PHP-File-Cache

Простой, легкий и автономный класс PHP для кэширования в файлах

Преимущества

Установка

Установите через composer:

composer require wruczek/php-file-cache

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

Использование довольно просто.

use Wruczek\PhpFileCache\PhpFileCache;

$app = Flight::app();

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

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

Затем вы можете использовать его в своем коде так:


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

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

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

Посетите https://github.com/Wruczek/PHP-File-Cache для полной документации и убедитесь, что вы посмотрите папку examples.

Awesome-plugins/index

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

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

Кэширование

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

Отладка

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

Базы данных

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

Сессии

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

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

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

Вклад

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

Awesome-plugins/pdo_wrapper

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

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

Регистрация класса-помощника PDO

// Регистрация класса-помощника PDO
Flight::зарегистрировать('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 = 'Боб';
$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@example.com']);
    $insert_id = Flight::db()->lastInsertId();

    // Обновить пользователя
    Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Боб', 123]);

    // Удалить пользователя
    Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);

    // Получить количество затронутых строк
    $statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Боб', 'Салли']);
    $affected_rows = $statement->rowCount();

});

Awesome-plugins/session

Ghostff/Session

Менеджер сеансов PHP (неблокирующий, flash, сегмент, шифрование сеанса). Использует open_ssl PHP для необязательного шифрования/дешифрования данных сеанса. Поддерживает File, MySQL, Redis и Memcached.

Установка

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

composer require ghostff/session

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

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


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// установите пользовательский путь к вашему файлу конфигурации сеансов и дайте ему случайную строку для идентификатора сеанса
$app->register('session', Session::class, [ 'путь/к/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', 'моя-супер-С3КР3ТНАЯ-соль'), // измените это на что-то другое
            Session::CONFIG_AUTO_COMMIT   => true, // делайте это только если это требуется и/или сложно фиксировать() ваш сеанс.
                                                // кроме того, вы можете сделать Flight::after('start', function() { Flight::session()->commit(); });
            Session::CONFIG_MYSQL_DS         => [
                'driver'    => 'mysql',             # Драйвер базы данных для DNS PDO, например (mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # Хост базы данных
                'db_name'   => 'my_app_database',   # Имя базы данных
                'db_table'  => 'sessions',          # Таблица базы данных
                'db_user'   => 'root',              # Имя пользователя базы данных
                'db_pass'   => '',                  # Пароль базы данных
                'persistent_conn'=> false,          # Избегайте накладных расходов на установку нового подключения каждый раз, когда скрипт должен общаться с базой данных, что приводит к более быстрому веб-приложению. НАЙДИТЕ ЗАДНЮЮ ЧАСТЬ САМИ
            ]
        ]);
    }
);

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

Посетите Github Readme для полной документации. Параметры конфигурации хорошо задокументированы в default_config.php файле самого себя. Код прост в понимании, если вы захотите изучить этот пакет самостоятельно.

Awesome-plugins/tracy_extensions

Tracy Расширения Панели для Flight

Это набор расширений, который делает работу с Flight более удобной.

Это Панель

Flight Bar

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

Flight Data Flight Database Flight Request

Установка

Выполните composer require flightphp/tracy-extensions --dev и вперед!

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

Вам нужно сделать очень мало для запуска этого. Вам нужно инициировать отладчик Tracy перед использованием https://tracy.nette.org/en/guide:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// код инициализации
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// Возможно вам потребуется указать ваше окружение с помощью Debugger::enable(Debugger::DEVELOPMENT)

// если вы используете подключения к базе данных в своем приложении, есть 
// обязательная обертка PDO для использования ТОЛЬКО В РАЗРАБОТКЕ (не в продакшене, пожалуйста!)
// Она имеет те же параметры, что и обычное соединение PDO
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// или если присоединить это к Flight framework
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// теперь при выполнении запроса он будет записывать время, запрос и параметры

// Это соединяет точки
if(Debugger::$showBar === true) {
    // Это должно быть false, иначе Tracy не сможет отобразить :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// больше кода

Flight::start();

Дополнительная Конфигурация

Данные Сеансов

Если у вас есть собственный обработчик сеансов (например, ghostff/session), вы можете передать любой массив данных сеанса в Tracy, и он автоматически выведет его для вас. Вы передаете его с ключом session_data во втором параметре конструктора TracyExtensionLoader.


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

if(Debugger::$showBar === true) {
    // Это должно быть false, иначе Tracy не сможет отобразить :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// маршруты и другие вещи...

Flight::start();

Latte

Если у вас установлен Latte в вашем проекте, вы можете использовать панель Latte для анализа ваших шаблонов. Вы можете передать экземпляр Latte в конструктор TracyExtensionLoader с ключом latte во втором параметре.



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    // здесь вы добавляете Панель Latte к Tracy
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // Это должно быть false, иначе Tracy не сможет отобразить :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/tracy

Tracy

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

Установка

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

composer require tracy/tracy

Базовая конфигурация

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


require 'vendor/autoload.php';

use Tracy\Debugger;

// Включение Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // иногда вам придется быть явным (также Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // также можно предоставить массив IP-адресов
// Здесь будут регистрироваться ошибки и исключения. Убедитесь, что этот каталог существует и доступен для записи.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // показывать все ошибки
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // все ошибки, кроме устаревших уведомлений
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // если панель отладки видима, тогда длина содержимого не может быть установлена Flight

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

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

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

Awesome-plugins/active_record

Flight Active Record

Активная запись - это сопоставление сущности базы данных с объектом PHP. Проще говоря, если у вас есть таблица пользователей в вашей базе данных, вы можете "перевести" строку в этой таблице в класс User и объект $user в вашем кодовой базе. См. пример использования.

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

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

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

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

/**
 * Класс Active Record обычно в единственном числе
 * 
 * Крайне рекомендуется добавить свойства таблицы в виде комментариев здесь
 * 
 * @property int    $id
 * @property string $name
 * @property string $password
 */ 
class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        // вы можете установить это так
        parent::__construct($database_connection, 'users');
        // или так
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Теперь посмотрите, как волшебство начинает работать!

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

// для mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');

// или mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// или mysqli с созданием без объекта
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');

$user = new User($database_connection);
$user->name = 'Бобби Тейблс';
$user->password = password_hash('некоторый крутой пароль');
$user->insert();
// или $user->save();

echo $user->id; // 1

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

echo $user->id; // 2

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

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

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

$users = $user->findAll();

А если вы хотите найти соответствующее условие?

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

Вам нравится этот процесс? Установим его и начнем!

Установка

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

composer require flightphp/active-record 

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

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

Самостоятельно

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

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

$User = new User($pdo_connection);

Фреймворк Flight PHP

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

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

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

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

Функции CRUD

find($id = null) : boolean|ActiveRecord

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

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

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

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

findAll(): array<int,ActiveRecord>

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

$user->findAll();

isHydrated(): boolean (v0.4.0)

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

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

insert(): boolean|ActiveRecord

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

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

update(): boolean|ActiveRecord

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

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

save(): boolean|ActiveRecord

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

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

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

delete(): boolean

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

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

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

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

dirty(array $dirty = []): ActiveRecord

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

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

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

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

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // обновятся и имя, и пароль.

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

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

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

isDirty(): boolean (v0.4.0)

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

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

reset(bool $include_query_data = true): ActiveRecord

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

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

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

getBuiltSql(): string (v0.4.1)

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

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

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

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

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

from(string $table)

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

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

join(string $table_name, string $join_condition)

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

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

where(string $where_conditions)

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

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

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

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

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

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

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

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

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

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

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

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

Условия WHERE

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

Где field = $value

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

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

Где field <> $value

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

isNull(string $field)

Где field IS NULL

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

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

Где field IS NOT NULL

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

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

Где field > $value

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

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

Где field < $value

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

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

Где field >= $value

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

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

Где field <= $value

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

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

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

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

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

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

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

between(string $field, array $values)

Где field BETWEEN $value AND $value1

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

Отношения

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

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

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

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

        // обязательно
        // в зависимости от типа отношения
        // self::HAS_ONE = внешний ключ, который ссылается на объединение
        self::HAS_MANY = внешний ключ, который ссылается на объединение
        // self::BELONGS_TO = локальный ключ, который ссылается на объединение
        'local_or_foreign_key',
        // просто 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' // это, если вы хотите обратную ссылку этого отношения обратно к себе Ex: $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; // это имя пользователя

Довольно прикольно, да?

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

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

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

Awesome-plugins/latte

Латте

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

Установка

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

composer require latte/latte

Базовая конфигурация

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


use Latte\Engine as LatteEngine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {

    // Здесь Латте будет кешировать ваши шаблоны, чтобы ускорить работу
    // Одна из хороших вещей в Latte - он автоматически обновляет ваш
    // кеш при внесении изменений в шаблоны!
    $latte->setTempDirectory(__DIR__ . '/../cache/');

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

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

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

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

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

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

<!-- Это содержимое, которое будет отображаться внутри макета внутри блока content -->
{block content}
    <h1>Главная страница</h1>
    <p>Добро пожаловать в мое приложение!</p>
{/block}

Затем, когда вы отображаете это внутри вашей функции или контроллера, вы сделаете что-то вроде этого:

// простой маршрут
Flight::route('/', function () {
    Flight::latte()->render('home.latte', [
        'title' => 'Главная страница'
    ]);
});

// или если вы используете контроллер
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::latte()->render('home.latte', [
            'title' => 'Главная страница'
        ]);
    }
}

Смотрите Документацию по Latte для получения более подробной информации о том, как использовать Latte на полную мощность!

Awesome-plugins/awesome_plugins

Плагины Flight

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

Кэширование

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

Cookies

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

Отладка

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

Базы данных

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

Шифрование

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

Сеансы

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

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

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

Содействие

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

Examples

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

Перейдите на flightphp/skeleton репозиторий, чтобы начать! Это проект, который включает в себя один файл страницы, содержащий все необходимое для запуска вашего приложения. Также включает более полноценный пример с контроллерами и представлениями.

Нуждаетесь вдохновения?

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

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

Если у вас есть проект, который вы хотите поделиться, пожалуйста, отправьте запрос на объединение (pull request), чтобы добавить его в этот список!