Learn/flight_vs_laravel
Flight vs Laravel
Что такое Laravel?
Laravel — это полнофункциональный фреймворк, который имеет все возможные функции и удивительную экосистему, ориентированную на разработчиков, но за счет производительности и сложности. Цель Laravel — обеспечить разработчику наивысший уровень производительности и сделать распространенные задачи простыми. Laravel — отличный выбор для разработчиков, которые хотят построить полнофункциональное корпоративное веб-приложение. Это влечет за собой некоторые компромиссы, в частности в плане производительности и сложности. Освоение основ Laravel может быть простым, но достижение мастерства в фреймворке может занять некоторое время.
Также существует множество модулей Laravel, из-за чего разработчики часто чувствуют, что единственный способ решить проблемы — это через эти модули, хотя на самом деле можно просто использовать другую библиотеку или написать свой собственный код.
Преимущества по сравнению с Flight
- Laravel имеет огромную экосистему разработчиков и модулей, которые можно использовать для решения распространенных проблем.
- Laravel имеет полнофункциональный ORM, который можно использовать для взаимодействия с вашей базой данных.
- Laravel имеет огромное количество документации и руководств, которые можно использовать для изучения фреймворка. Это может быть полезно для глубокого погружения в детали или плохо, потому что приходится проходить через слишком много материала.
- Laravel имеет встроенную систему аутентификации, которую можно использовать для обеспечения безопасности вашего приложения.
- Laravel имеет подкасты, конференции, встречи, видео и другие ресурсы, которые можно использовать для изучения фреймворка.
- Laravel ориентирован на опытного разработчика, который хочет построить полнофункциональное корпоративное веб-приложение.
Недостатки по сравнению с Flight
- Laravel имеет гораздо больше происходящего под капотом, чем Flight. Это влечет за собой драматические затраты в плане производительности. См. TechEmpower benchmarks для получения дополнительной информации.
- Flight ориентирован на разработчика, который хочет построить легковесное, быстрое и простое в использовании веб-приложение.
- Flight ориентирован на простоту и удобство использования.
- Одна из ключевых особенностей Flight — это то, что он старается поддерживать обратную совместимость. Laravel вызывает много раздражения между основными версиями.
- Flight предназначен для разработчиков, которые впервые вступают в мир фреймворков.
- Flight не имеет зависимостей, в то время как Laravel имеет отвратительное количество зависимостей
- Flight также может создавать приложения корпоративного уровня, но у него не так много шаблонного кода, как у Laravel. Кроме того, потребуется больше дисциплины со стороны разработчика, чтобы поддерживать порядок и хорошую структуру.
- Flight дает разработчику больше контроля над приложением, в то время как Laravel имеет множество магических функций за кулисами, которые могут раздражать.
Learn/migrating_to_v3
Миграция на v3
Обратная совместимость в основном сохранена, но есть некоторые изменения, о которых вы должны знать при миграции с v2 на v3. Некоторые изменения слишком сильно конфликтовали с шаблонами проектирования, поэтому пришлось внести корректировки.
Поведение буферизации вывода
v3.5.0
Буферизация вывода — это процесс, при котором вывод, генерируемый PHP-скриптом, хранится в буфере (внутреннем для PHP), прежде чем будет отправлен клиенту. Это позволяет модифицировать вывод перед его отправкой клиенту.
В MVC-приложении контроллер является "менеджером" и управляет тем, что делает представление. Генерация вывода вне контроллера (или в случае Flight иногда анонимной функцией) нарушает шаблон MVC. Это изменение сделано для большей соответствия шаблону MVC и чтобы сделать фреймворк более предсказуемым и удобным в использовании.
В v2 буферизация вывода обрабатывалась таким образом, что она не всегда последовательно закрывала свой буфер вывода, что затрудняло юнит-тестирование и потоковую передачу. Для большинства пользователей это изменение может не повлиять на вас. Однако, если вы выводите содержимое вне вызываемых функций и контроллеров (например, в хуке), вы, вероятно, столкнетесь с проблемами. Вывод содержимого в хуках и до фактического выполнения фреймворка мог работать в прошлом, но впредь работать не будет.
Где вы можете столкнуться с проблемами
// index.php
require 'vendor/autoload.php';
// just an example
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// this will actually be fine
echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});
Flight::before('start', function(){
// things like this will cause an error
echo '<html><head><title>My Page</title></head><body>';
});
Flight::route('/', function(){
// this is actually just fine
echo 'Hello World';
// This should be just fine as well
Flight::hello();
});
Flight::after('start', function(){
// this will cause an error
echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});
Включение поведения рендеринга v2
Можете ли вы оставить свой старый код как есть, без переписывания для работы с v3? Да, можете! Вы можете включить поведение рендеринга v2, установив опцию конфигурации flight.v2.output_buffering
в true
. Это позволит вам продолжать использовать старое поведение рендеринга, но рекомендуется исправить это в будущем. В v4 фреймворка это будет удалено.
// index.php
require 'vendor/autoload.php';
Flight::set('flight.v2.output_buffering', true);
Flight::before('start', function(){
// Now this will be just fine
echo '<html><head><title>My Page</title></head><body>';
});
// more code
Изменения в диспетчере
v3.7.0
Если вы напрямую вызывали статические методы для Dispatcher
, такие как Dispatcher::invokeMethod()
, Dispatcher::execute()
и т.д., вам нужно обновить свой код, чтобы не вызывать эти методы напрямую. Dispatcher
был преобразован для большей объектно-ориентированности, чтобы контейнеры внедрения зависимостей можно было использовать проще. Если вам нужно вызвать метод аналогично тому, как это делал Dispatcher, вы можете вручную использовать что-то вроде $result = $class->$method(...$params);
или call_user_func_array()
вместо этого.
Изменения в halt()
stop()
redirect()
и error()
v3.10.0
Поведение по умолчанию до 3.10.0 заключалось в очистке как заголовков, так и тела ответа. Это было изменено на очистку только тела ответа. Если вам также нужно очистить заголовки, вы можете использовать Flight::response()->clear()
.
Learn/configuration
Конфигурация
Обзор
Flight предоставляет простой способ настройки различных аспектов фреймворка в соответствии с потребностями вашего приложения. Некоторые из них установлены по умолчанию, но вы можете переопределить их по необходимости. Вы также можете установить свои собственные переменные для использования в приложении.
Понимание
Вы можете настроить определенные поведения Flight, устанавливая значения конфигурации
через метод set
.
Flight::set('flight.log_errors', true);
В файле app/config/config.php
вы можете увидеть все доступные по умолчанию переменные конфигурации.
Основное использование
Опции конфигурации Flight
Ниже приведен список всех доступных настроек конфигурации:
- flight.base_url
?string
- Переопределение базового URL запроса, если Flight работает в поддиректории. (по умолчанию: null) - flight.case_sensitive
bool
- Учет регистра при сопоставлении URL. (по умолчанию: false) - flight.handle_errors
bool
- Разрешить Flight обрабатывать все ошибки внутренне. (по умолчанию: true)- Если вы хотите, чтобы Flight обрабатывал ошибки вместо поведения PHP по умолчанию, это должно быть true.
- Если у вас установлен Tracy, вы хотите установить это в false, чтобы Tracy мог обрабатывать ошибки.
- Если у вас установлен плагин APM, вы хотите установить это в true, чтобы APM мог логировать ошибки.
- flight.log_errors
bool
- Логировать ошибки в файл лога ошибок веб-сервера. (по умолчанию: false)- Если у вас установлен Tracy, Tracy будет логировать ошибки на основе конфигураций Tracy, а не этой конфигурации.
- flight.views.path
string
- Директория, содержащая файлы шаблонов представлений. (по умолчанию: ./views) - flight.views.extension
string
- Расширение файла шаблона представления. (по умолчанию: .php) - flight.content_length
bool
- Установить заголовокContent-Length
. (по умолчанию: true)- Если вы используете Tracy, это должно быть установлено в false, чтобы Tracy мог правильно отображаться.
- flight.v2.output_buffering
bool
- Использовать устаревшую буферизацию вывода. См. миграцию на v3. (по умолчанию: false)
Конфигурация загрузчика
Кроме того, есть еще одна настройка конфигурации для загрузчика. Это позволит вам
автоматически загружать классы с _
в имени класса.
// Включить загрузку классов с подчеркиваниями
// По умолчанию true
Loader::$v2ClassLoading = false;
Переменные
Flight позволяет сохранять переменные, чтобы их можно было использовать в любом месте вашего приложения.
// Сохранить вашу переменную
Flight::set('id', 123);
// В другом месте вашего приложения
$id = Flight::get('id');
Чтобы проверить, установлена ли переменная, вы можете сделать:
if (Flight::has('id')) {
// Сделать что-то
}
Вы можете очистить переменную, сделав:
// Очищает переменную id
Flight::clear('id');
// Очищает все переменные
Flight::clear();
Примечание: Просто потому, что вы можете установить переменную, не значит, что вы должны. Используйте эту функцию умеренно. Причина в том, что все, что хранится здесь, становится глобальной переменной. Глобальные переменные плохи, потому что их можно изменить из любого места в вашем приложении, что затрудняет поиск ошибок. Кроме того, это может усложнить такие вещи, как юнит-тестирование.
Ошибки и исключения
Все ошибки и исключения перехватываются Flight и передаются в метод error
.
если flight.handle_errors
установлено в true.
Поведение по умолчанию — отправить общий ответ HTTP 500 Internal Server Error
с некоторой информацией об ошибке.
Вы можете переопределить это поведение для своих нужд:
Flight::map('error', function (Throwable $error) {
// Обработать ошибку
echo $error->getTraceAsString();
});
По умолчанию ошибки не логируются на веб-сервере. Вы можете включить это, изменив конфигурацию:
Flight::set('flight.log_errors', true);
404 Not Found
Когда URL не может быть найден, Flight вызывает метод notFound
. Поведение
по умолчанию — отправить ответ HTTP 404 Not Found
с простым сообщением.
Вы можете переопределить это поведение для своих нужд:
Flight::map('notFound', function () {
// Обработать не найдено
});
См. также
- Расширение Flight - Как расширить и настроить основную функциональность Flight.
- Юнит-тестирование - Как писать юнит-тесты для вашего приложения Flight.
- Tracy - Плагин для продвинутой обработки ошибок и отладки.
- Расширения Tracy - Расширения для интеграции Tracy с Flight.
- APM - Плагин для мониторинга производительности приложения и отслеживания ошибок.
Устранение неисправностей
- Если у вас проблемы с определением всех значений вашей конфигурации, вы можете сделать
var_dump(Flight::get());
Журнал изменений
- v3.5.0 - Добавлена конфигурация для
flight.v2.output_buffering
для поддержки устаревшего поведения буферизации вывода. - v2.0 - Добавлены основные конфигурации.
Learn/ai
AI и опыт разработчика с Flight
Обзор
Flight упрощает суперзарядку ваших PHP-проектов с помощью инструментов на базе ИИ и современных рабочих процессов разработчика. С встроенными командами для подключения к провайдерам LLM (Large Language Model) и генерации специфических для проекта инструкций по кодированию с ИИ, Flight помогает вам и вашей команде извлекать максимум из ИИ-ассистентов, таких как GitHub Copilot, Cursor и Windsurf.
Понимание
ИИ-ассистенты для кодирования наиболее полезны, когда они понимают контекст, конвенции и цели вашего проекта. ИИ-помощники Flight позволяют вам:
- Подключить ваш проект к популярным провайдерам LLM (OpenAI, Grok, Claude и т.д.)
- Генерировать и обновлять специфические для проекта инструкции для ИИ-инструментов, чтобы все получали последовательную, релевантную помощь
- Сохранять вашу команду в согласованности и продуктивности, тратя меньше времени на объяснение контекста
Эти функции встроены в основной CLI Flight и официальный стартовый проект flightphp/skeleton.
Базовое использование
Настройка учетных данных LLM
Команда ai:init
проведет вас через процесс подключения вашего проекта к провайдеру LLM.
php runway ai:init
Вам будет предложено:
- Выбрать вашего провайдера (OpenAI, Grok, Claude и т.д.)
- Ввести ваш API-ключ
- Установить базовый URL и имя модели
Это создаст файл .runway-creds.json
в корне вашего проекта (и убедится, что он добавлен в ваш .gitignore
).
Пример:
Welcome to AI Init!
Which LLM API do you want to use? [1] openai, [2] grok, [3] claude: 1
Enter the base URL for the LLM API [https://api.openai.com]:
Enter your API key for openai: sk-...
Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc) [gpt-4o]:
Credentials saved to .runway-creds.json
Генерация специфических для проекта инструкций ИИ
Команда ai:generate-instructions
помогает создать или обновить инструкции для ИИ-ассистентов кодирования, адаптированные к вашему проекту.
php runway ai:generate-instructions
Вы ответите на несколько вопросов о вашем проекте (описание, база данных, шаблонизация, безопасность, размер команды и т.д.). Flight использует вашего провайдера LLM для генерации инструкций, затем записывает их в:
.github/copilot-instructions.md
(для GitHub Copilot).cursor/rules/project-overview.mdc
(для Cursor).windsurfrules
(для Windsurf)
Пример:
Please describe what your project is for? My awesome API
What database are you planning on using? MySQL
What HTML templating engine will you plan on using (if any)? latte
Is security an important element of this project? (y/n) y
...
AI instructions updated successfully.
Теперь ваши ИИ-инструменты будут давать более умные, релевантные предложения на основе реальных нужд вашего проекта.
Расширенное использование
- Вы можете настроить расположение файлов учетных данных или инструкций с помощью опций команд (см.
--help
для каждой команды). - ИИ-помощники предназначены для работы с любым провайдером LLM, поддерживающим API, совместимые с OpenAI.
- Если вы хотите обновить инструкции по мере эволюции вашего проекта, просто перезапустите
ai:generate-instructions
и ответьте на подсказки снова.
См. также
- Flight Skeleton – Официальный стартер с интеграцией ИИ
- Runway CLI – Подробнее о CLI-инструменте, обеспечивающем эти команды
Устранение неисправностей
- Если вы видите "Missing .runway-creds.json", сначала запустите
php runway ai:init
. - Убедитесь, что ваш API-ключ действителен и имеет доступ к выбранной модели.
- Если инструкции не обновляются, проверьте разрешения на файлы в директории вашего проекта.
Журнал изменений
- v3.16.0 – Добавлены CLI-команды
ai:init
иai:generate-instructions
для интеграции ИИ.
Learn/unit_testing_and_solid_principles
Эта статья была originally опубликована на Airpair в 2015 году. Вся заслуга принадлежит Airpair и Brian Fenton, который originally написал эту статью, хотя веб-сайт более не доступен, и статья существует только в Wayback Machine. Эта статья была добавлена на сайт для обучения и образовательных целей для сообщества PHP в целом.
1 Настройка и конфигурация
1.1 Поддерживайте актуальность
Давайте обозначим это с самого начала - удручающе малое число установок PHP в реальном мире являются актуальными или поддерживаются в актуальном состоянии. Будь то из-за ограничений общего хостинга, стандартных настроек, которые никто не думает изменить, или отсутствия времени/бюджета на тестирование обновлений, скромные бинарники PHP склонны отставать. Таким образом, одна из четких лучших практик, которая нуждается в большем акценте, - всегда использовать актуальную версию PHP (5.6.x на момент написания этой статьи). Кроме того, важно планировать регулярные обновления как самого PHP, так и любых расширений или библиотек поставщиков, которые вы используете. Обновления предоставляют новые функции языка, улучшенную скорость, меньшее использование памяти и обновления безопасности. Чем чаще вы обновляетесь, тем менее болезненным становится процесс.
1.2 Установите разумные настройки по умолчанию
PHP делает приличную работу по установке хороших настроек по умолчанию в файлах php.ini.development и php.ini.production, но мы можем сделать лучше. Во-первых, они не устанавливают дату/временную зону для нас. Это имеет смысл с точки зрения распространения, но без нее PHP будет генерировать ошибку E_WARNING каждый раз, когда мы вызываем функцию, связанную с датой/временем. Вот некоторые рекомендуемые настройки:
- date.timezone - выберите из списка поддерживаемых временных зон
- session.savepath - если мы используем файлы для сессий и не какой-либо другой обработчик сохранения, установите это вне /tmp. Оставление этого как /tmp может быть рискованным в среде общего хостинга, поскольку /tmp_ обычно имеет широкие разрешения. Даже с установленным битом sticky, любой, у кого есть доступ к перечислению содержимого этой директории, может узнать все ваши активные идентификаторы сессий.
- session.cookie_secure - очевидно, включите это, если вы обслуживаете код PHP по HTTPS.
- session.cookie_httponly - установите это, чтобы предотвратить доступ к cookie сессий PHP через JavaScript
- Ещё... используйте инструмент вроде iniscan, чтобы проверить вашу конфигурацию на наличие распространенных уязвимостей
1.3 Расширения
Также хорошей идеей является отключение (или хотя бы не включение) расширений, которые вы не будете использовать, как драйверы баз данных. Чтобы увидеть, что включено, запустите команду phpinfo()
или перейдите в командную строку и запустите это.
$ php -i
Информация та же, но phpinfo() добавляет HTML-форматирование. Версия CLI проще для перенаправления в grep для поиска конкретной информации. Прим.
$ php -i | grep error_log
Один нюанс этого метода: возможно, что разные настройки PHP применяются к веб-ориентированной версии и версии CLI.
2 Используйте Composer
Это может показаться сюрпризом, но одна из лучших практик для написания современного PHP - писать меньше кода. Хотя правда, что один из лучших способов освоить программирование - делать это, существует множество уже решенных проблем в пространстве PHP, таких как маршрутизация, базовые библиотеки проверки ввода, преобразование единиц, слои абстракции баз данных и т.д... Просто перейдите на Packagist и посмотрите. Вы, вероятно, обнаружите, что значительная часть проблемы, которую вы пытаетесь решить, уже написана и протестирована.
Хотя tempting писать весь код самому (и нет ничего плохого в написании собственного фреймворка или библиотеки как опыта обучения) вы должны бороться с этими чувствами "Не Изобретено Здесь" и сэкономить себе кучу времени и головной боли. Следуйте доктрине PIE вместо этого - Proudly Invented Elsewhere. Кроме того, если вы решите написать свое собственное что-то, не выпускайте это, если оно делает что-то значительно другое или лучше, чем существующие предложения.
Composer - это менеджер пакетов для PHP, подобный pip в Python, gem в Ruby и npm в Node. Он позволяет определить JSON-файл, который перечисляет зависимости вашего кода, и он попытается разрешить эти требования, скачивая и устанавливая необходимый код.
2.1 Установка Composer
Мы предполагаем, что это локальный проект, так что давайте установим экземпляр Composer только для текущего проекта. Перейдите в директорию проекта и запустите это:
$ curl -sS https://getcomposer.org/installer | php
Помните, что перенаправление любого скачивания напрямую в интерпретатор скрипта (sh, ruby, php и т.д.) - это риск безопасности, так что прочитайте код установки и убедитесь, что вы комфортны с ним, прежде чем запускать любую такую команду.
Для удобства (если вы предпочитаете набирать composer install
вместо php composer.phar install
), вы можете использовать эту команду, чтобы установить единственную копию composer глобально:
$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer
Вам может потребоваться запустить эти с sudo
в зависимости от ваших прав доступа к файлам.
2.2 Использование Composer
У Composer две основные категории зависимостей, которые он может управлять: "require" и "require-dev". Зависимости, перечисленные как "require", устанавливаются везде, но зависимости "require-dev" устанавливаются только при конкретном запросе. Обычно это инструменты для активной разработки, такие как PHP_CodeSniffer. Строка ниже показывает пример, как установить Guzzle, популярную HTTP-библиотеку.
$ php composer.phar require guzzle/guzzle
Чтобы установить инструмент только для целей разработки, добавьте флаг --dev
:
$ php composer.phar require --dev 'sebastian/phpcpd'
Это устанавливает PHP Copy-Paste Detector, другой инструмент качества кода, как зависимость только для разработки.
2.3 Install vs update
Когда мы впервые запускаем composer install
, он установит любые библиотеки и их зависимости, которые нам нужны, на основе файла composer.json. Когда это сделано, composer создает файл блокировки, предсказуемо называемый composer.lock. Этот файл содержит список зависимостей, которые composer нашел для нас, и их точные версии, с хешами. Затем любой следующий раз, когда мы запускаем composer install
, он посмотрит в файл блокировки и установит именно эти версии.
composer update
- это немного другое существо. Он игнорирует файл composer.lock (если он присутствует) и попытается найти самые актуальные версии каждой из зависимостей, которые все еще удовлетворяют ограничениям в composer.json. Затем он запишет новый файл composer.lock, когда закончит.
2.4 Автозагрузка
Как composer install
, так и composer update
сгенерируют автозагрузчик для нас, который говорит PHP, где найти все необходимые файлы для использования библиотек, которые мы только что установили. Чтобы использовать его, просто добавьте эту строку (обычно в файл bootstrap, который выполняется на каждый запрос):
require 'vendor/autoload.php';
3 Следуйте хорошим принципам дизайна
3.1 SOLID
SOLID - это мнемоника, чтобы напомнить нам о пяти ключевых принципах хорошего объектно-ориентированного дизайна программного обеспечения.
3.1.1 S - Принцип единственной ответственности
Это гласит, что классы должны иметь только одну ответственность, или, иными словами, у них должен быть только один повод для изменения. Это хорошо сочетается с философией Unix множества малых инструментов, делающих одну вещь хорошо. Классы, которые делают только одну вещь, гораздо легче тестировать и отлаживать, и они менее склонны удивлять вас. Вы не хотите, чтобы вызов метода в классе Validator обновлял записи в БД. Вот пример нарушения SRP, который вы часто видите в приложении на основе шаблона ActiveRecord.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
public function save() {}
}
Итак, это довольно базовая модель сущности. Но одна из этих вещей не подходит сюда. Единственная ответственность модели сущности должна заключаться в поведении, связанном с сущностью, которую она представляет, она не должна быть responsible за сохранение себя.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
}
class DataStore
{
public function save(Model $model) {}
}
Это лучше. Модель Person вернулась к тому, чтобы делать только одну вещь, а поведение сохранения было перенесено в объект сохранения. Обратите внимание, что я указал тип только на Model, а не на Person. Мы вернемся к этому, когда дойдем до частей L и D SOLID.
3.1.2 O - Принцип открытости/замкнутости
Есть потрясающий тест для этого, который довольно точно суммирует, о чем этот принцип: подумайте о функции для реализации, вероятно, самой недавней, над которой вы работали или работаете. Можете ли вы реализовать эту функцию в существующей кодовой базе ТОЛЬКО путем добавления новых классов и без изменения каких-либо существующих классов в вашей системе? Ваша конфигурация и код wiring получают некоторое послабление, но в большинстве систем это удивительно сложно. Вам приходится полагаться на полиморфный dispatch, и большинство кодовых баз просто не настроены на это. Если вас это интересует, есть хороший Google talk на YouTube о полиморфизме и написании кода без If, который углубляется в это дальше. В качестве бонуса, доклад делает Miško Hevery, которого многие знают как создателя AngularJs.
3.1.3 L - Принцип подстановки Лисков
Этот принцип назван в честь Barbara Liskov и сформулирован ниже:
"Объекты в программе должны быть заменяемыми экземплярами своих подтипов без изменения корректности этой программы."
Это звучит хорошо, но это более четко иллюстрируется на примере.
abstract class Shape
{
public function getHeight();
public function setHeight($height);
public function getLength();
public function setLength($length);
}
Это будет представлять наш базовый четырехсторонний shape. Ничего особенного здесь.
class Square extends Shape
{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight($height) {
$this->size = $height;
}
public function getLength() {
return $this->size;
}
public function setLength($length) {
$this->size = $length;
}
}
Вот наш первый shape, квадрат. Довольно простой shape, правда? Вы можете предположить, что есть конструктор, где мы устанавливаем размеры, но вы видите здесь из этой реализации, что длина и высота всегда будут одинаковыми. Квадраты такие.
class Rectangle extends Shape
{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}
public function setHeight($height) {
$this->height = $height;
}
public function getLength() {
return $this->length;
}
public function setLength($length) {
$this->length = $length;
}
}
Итак, вот другой shape. Все еще имеет те же сигнатуры методов, это все еще четырехсторонний shape, но что, если мы начнем пытаться использовать их взаимозаменяемо? Теперь вдруг, если мы изменим высоту нашего Shape, мы больше не можем предположить, что длина нашего shape совпадет. Мы нарушили контракт, который имели с пользователем, когда дали им наш Square shape.
Это классический пример нарушения LSP, и нам нужен такой тип принципа, чтобы максимально использовать систему типов. Даже duck typing не скажет нам, если базовое поведение отличается, и поскольку мы не можем знать это без того, чтобы увидеть, как оно ломается, лучше убедиться, что оно не отличается изначально.
3.1.3 I - Принцип сегрегаации интерфейсов
Этот принцип говорит отдавать предпочтение многим малым, тонким интерфейсам по сравнению с одним большим. Интерфейсы должны основываться на поведении, а не на "это один из этих классов". Подумайте об интерфейсах, которые поставляются с PHP. Traversable, Countable, Serializable, вещи вроде того. Они рекламируют возможности, которыми обладает объект, а не то, от чего он наследует. Так что держите свои интерфейсы маленькими. Вы не хотите, чтобы интерфейс имел 30 методов, 3 - гораздо лучшая цель.
3.1.4 D - Принцип инверсии зависимостей
Вы, вероятно, слышали об этом в других местах, где говорили о Dependency Injection, но Dependency Inversion и Dependency Injection - не совсем одно и то же. Dependency inversion - это в основном способ сказать, что вы должны зависеть от абстракций в вашей системе, а не от ее деталей. Что это значит для вас в повседневной жизни?
Не используйте mysqli_query() напрямую по всему коду, используйте что-то вроде DataStore->query() вместо.
Ядро этого принципа - это абстракции. Это больше о том, чтобы сказать "используйте адаптер базы данных" вместо зависимости от прямых вызовов, как mysqli_query. Если вы напрямую используете mysqli_query в половине своих классов, то вы привязываете все напрямую к вашей базе данных. Ничего против MySQL, но если вы используете mysqli_query, этот тип низкоуровневых деталей должен быть скрыт в одном месте, а затем эта функциональность должна быть раскрыта через общий обертку.
Теперь я знаю, что это своего рода избитый пример, если вы подумаете об этом, потому что количество раз, когда вы фактически полностью измените движок базы данных после ввода продукта в производство, очень, очень мало. Я выбрал это, потому что подумал, что люди будут знакомы с идеей из своего собственного кода. Кроме того, даже если у вас есть база данных, с которой вы знаете, что останетесь, этот абстрактный объект-обертка позволяет вам исправлять ошибки, изменять поведение или реализовывать функции, которые вы желаете, чтобы ваша выбранная база данных имела. Это также делает unit testing возможным, где низкоуровневые вызовы не сделали бы.
4 Объектные упражнения
Это не полный погружение в эти принципы, но первые два легко запомнить, предоставляют хорошую ценность и могут быть немедленно применены к практически любой кодовой базе.
4.1 Не более одного уровня отступа на метод
Это полезный способ думать о разложении методов на меньшие фрагменты, оставляя код, который чище и более самодокументирующийся. Чем больше уровней отступа у вас есть, тем больше метод делает и тем больше состояния вы должны отслеживать в своей голове, пока работаете с ним.
Сразу я знаю, люди будут возражать против этого, но это просто рекомендация/эвристика, а не жесткое правило. Я не ожидаю, что кто-то будет применять правила PHP_CodeSniffer для этого (хотя люди делали).
Давайте пройдемся по быстрому образцу того, как это может выглядеть:
public function transformToCsv($data)
{
$csvLines = array();
$csvLines[] = implode(',', array_keys($data[0]));
foreach ($data as $row) {
if (!$row) {
continue;
}
$csvLines[] = implode(',', $row);
}
return $csvLines;
}
Хотя это не ужасный код (он технически правильный, тестируемый и т.д.), мы можем сделать гораздо больше, чтобы сделать это ясным. Как мы можем уменьшить уровни вложенности здесь?
Мы знаем, что нужно значительно упростить содержимое цикла foreach (или удалить его полностью), так давайте начнем с этого.
if (!$row) {
continue;
}
Эта первая часть проста. Это просто игнорирует пустые строки. Мы можем сократить весь этот процесс, используя встроенную функцию PHP до того, как добраться до цикла.
$data = array_filter($data);
foreach ($data as $row) {
$csvLines[] = implode(',', $row);
}
Теперь у нас один уровень вложенности. Но, looking at this, все, что мы делаем, - это применяем функцию к каждому элементу массива. Нам даже не нужен цикл foreach для этого.
$data = array_filter($data);
$csvLines = array_map(function($row) {
return implode(',', $row);
}, $data);
Теперь у нас нет вложенности вообще, и код, вероятно, будет быстрее, поскольку мы делаем весь цикл с нативными C-функциями вместо PHP. Нам приходится заниматься некоторым трюком, чтобы передать запятую в implode
, так что вы можете утверждать, что остановка на предыдущем шаге гораздо понятнее.
4.2 Старайтесь не использовать else
Это действительно касается двух основных идей. Первая - несколько операторов return из метода. Если у вас достаточно информации, чтобы принять решение об результате метода, просто примите это решение и вернитесь. Вторая - идея, известная как Guard Clauses. Это в основном проверки валидации, объединенные с ранними возвратами, обычно в начале метода. Позвольте мне показать, что я имею в виду.
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {
$sum = $first + $second + $third;
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
return $sum;
}
Итак, это довольно прямолинейно, оно добавляет 3 целых числа и возвращает результат или null
, если любой из параметров не является целым. Игнорируя тот факт, что мы могли объединить все эти проверки в одну строку с операторами AND, я думаю, вы можете увидеть, как вложенная структура if/else делает код сложнее для следования. Теперь посмотрите на этот пример вместо.
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
return null;
}
if (!is_int($third)) {
return null;
}
return $first + $second + $third;
}
Для меня этот пример гораздо легче следовать. Здесь мы используем guard clauses, чтобы проверить наши начальные утверждения о параметрах, которые мы передаем, и немедленно выйти из метода, если они не проходят. Мы также больше не имеем промежуточную переменную для отслеживания суммы на протяжении всего метода. В этом случае мы убедились, что уже находимся на happy path, и можем просто делать то, для чего пришли. Опять же, мы могли бы сделать все эти проверки в одном if
, но принцип должен быть ясен.
5 Unit testing
Unit testing - это практика написания малых тестов, которые проверяют поведение в вашем коде. Они почти всегда пишутся на том же языке, что и код (в данном случае PHP), и предназначены для того, чтобы быть достаточно быстрыми, чтобы запускаться в любое время. Они чрезвычайно ценны как инструмент для улучшения вашего кода. Помимо очевидных преимуществ обеспечения того, что ваш код делает то, что вы думаете, unit testing может предоставить очень полезную обратную связь дизайна. Если кусок кода трудно протестировать, это часто подчеркивает проблемы дизайна. Они также дают вам страховочную сеть против регрессий, и это позволяет вам рефакторить гораздо чаще и эволюционировать ваш код к чищему дизайну.
5.1 Инструменты
Существует несколько инструментов unit testing в PHP, но далеко не самый распространенный - PHPUnit. Вы можете установить его, скачав PHAR файл непосредственно, или установить с composer. Поскольку мы используем composer для всего остального, мы покажем этот метод. Кроме того, поскольку PHPUnit, вероятно, не будет развернут в production, мы можем установить его как зависимость dev с помощью следующей команды:
composer require --dev phpunit/phpunit
5.2 Тесты - это спецификация
Самая важная роль unit тестов в вашем коде - предоставить исполняемую спецификацию того, что код предполагается делать. Даже если код теста неверен или код имеет ошибки, знание того, что система должна делать, бесценно.
5.3 Пишите тесты сначала
Если у вас была возможность увидеть набор тестов, написанных перед кодом и один, написанный после завершения кода, они поразительно отличаются. "После" тесты гораздо больше озабочены деталями реализации класса и обеспечением хорошего покрытия строк, в то время как "перед" тесты больше о проверке желаемого внешнего поведения. Это именно то, что нас интересует с unit тестами в любом случае, - убедиться, что класс демонстрирует правильное поведение. Тесты, ориентированные на реализацию, на самом деле затрудняют рефакторинг, потому что они ломаются, если внутренности классов изменяются, и вы только что лишили себя преимуществ сокрытия информации от OOP.
5.4 Что делает хороший unit тест
Хорошие unit тесты делят много следующих характеристик:
- Быстрые - должны запускаться в миллисекундах.
- Без доступа к сети - должны уметь отключить беспроводную связь/вытащить шнур и все тесты все равно проходят.
- Ограниченный доступ к файловой системе - это добавляет к скорости и гибкости при развертывании кода в другие среды.
- Без доступа к базе данных - избегает costly setup и teardown деятельности.
- Тестировать только одну вещь за раз - unit тест должен иметь только одну причину для неудачи.
- Хорошо названные - см. 5.2 выше.
- В основном фейковые объекты - единственные "реальные" объекты в unit тестах должны быть объектом, который мы тестируем, и простыми объектами значений. Остальное должно быть какой-то формой test double
Есть причины пойти против некоторых из них, но как общие рекомендации они послужат вам хорошо.
5.5 Когда тестирование болезненно
Unit testing заставляет вас почувствовать боль плохого дизайна спереди - Michael Feathers
Когда вы пишете unit тесты, вы заставляете себя фактически использовать класс для достижения вещей. Если вы пишете тесты в конце или, что хуже, просто бросаете код через стену для QA или кого-то, чтобы написать тесты, вы не получаете никакой обратной связи о том, как класс на самом деле себя ведет. Если мы пишем тесты и класс - настоящая боль в использовании, мы узнаем об этом, пока пишем его, что почти самое дешевое время, чтобы исправить это.
Если класс трудно протестировать, это flaw дизайна. Разные недостатки проявляют себя по-разному, хотя. Если вам приходится делать тонну mocking, ваш класс, вероятно, имеет слишком много зависимостей или ваши методы делают слишком много. Чем больше настройки вам приходится делать для каждого теста, тем больше вероятность, что ваши методы делают слишком много. Если вам приходится писать очень запутанные сценарии тестов, чтобы проверить поведение, методы класса, вероятно, делают слишком много. Если вам приходится копаться внутри кучи приватных методов и состояния, чтобы протестировать вещи, возможно, другой класс пытается выбраться. Unit testing очень хорош в разоблачении "iceberg классов", где 80% того, что делает класс, спрятано в защищенном или приватном коде. Раньше я был большим поклонником делать как можно больше защищенным, но теперь я понял, что просто делал свои индивидуальные классы responsible за слишком многое, и настоящее решение - разбить класс на меньшие куски.
Написано Brian Fenton - Brian Fenton - PHP-разработчик в течение 8 лет в Среднем Западе и Bay Area, в настоящее время в Thismoment. Он фокусируется на craftsmanship кода и принципах дизайна. Блог на www.brianfenton.us, Twitter на @brianfenton. Когда он не занят отцом, он наслаждается едой, пивом, играми и обучением.
Learn/security
Безопасность
Обзор
Безопасность имеет большое значение для веб-приложений. Вы хотите убедиться, что ваше приложение защищено и данные ваших пользователей в безопасности. Flight предоставляет ряд функций, которые помогут вам обезопасить ваши веб-приложения.
Понимание
Существует ряд распространенных угроз безопасности, о которых вы должны знать при создании веб-приложений. Некоторые из наиболее распространенных угроз включают:
- Межсайтовый запрос подделки (CSRF)
- Межсайтовый скриптинг (XSS)
- Инъекция SQL
- Кросс-доменное совместное использование ресурсов (CORS)
Шаблоны помогают с XSS, экранируя вывод по умолчанию, чтобы вам не приходилось об этом помнить. Сессии могут помочь с CSRF, храня токен CSRF в сессии пользователя, как описано ниже. Использование подготовленных запросов с PDO может помочь предотвратить атаки инъекции SQL (или использование удобных методов в классе PdoWrapper). CORS можно обрабатывать с помощью простого хука перед вызовом Flight::start()
.
Все эти методы работают вместе, чтобы помочь сохранить ваши веб-приложения в безопасности. Всегда держите в уме изучение и понимание лучших практик безопасности.
Основное использование
Заголовки
HTTP-заголовки — один из самых простых способов обезопасить ваши веб-приложения. Вы можете использовать заголовки для предотвращения clickjacking, XSS и других атак. Существует несколько способов добавить эти заголовки в ваше приложение.
Два отличных сайта для проверки безопасности ваших заголовков — securityheaders.com и observatory.mozilla.org. После настройки кода ниже вы можете легко проверить, что ваши заголовки работают, с помощью этих двух сайтов.
Добавление вручную
Вы можете вручную добавить эти заголовки, используя метод header
объекта Flight\Response
.
// Установка заголовка X-Frame-Options для предотвращения clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
// Установка заголовка Content-Security-Policy для предотвращения XSS
// Примечание: этот заголовок может быть очень сложным, поэтому вы захотите
// проконсультироваться с примерами в интернете для вашего приложения
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
// Установка заголовка X-XSS-Protection для предотвращения XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');
// Установка заголовка X-Content-Type-Options для предотвращения MIME sniffing
Flight::response()->header('X-Content-Type-Options', 'nosniff');
// Установка заголовка Referrer-Policy для контроля количества отправляемой информации о реферере
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
// Установка заголовка Strict-Transport-Security для принудительного использования HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// Установка заголовка Permissions-Policy для контроля используемых функций и API
Flight::response()->header('Permissions-Policy', 'geolocation=()');
Эти заголовки можно добавить в начало ваших файлов routes.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, что обеспечивает наибольшую гибкость для выбора маршрутов, к которым это применяется. В общем случае эти заголовки должны применяться ко всем HTML- и API-ответам.
// app/middlewares/SecurityHeadersMiddleware.php
namespace app\middlewares;
use flight\Engine;
class SecurityHeadersMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
$response = $this->app->response();
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header("Content-Security-Policy", "default-src 'self'");
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');
$response->header('Referrer-Policy', 'no-referrer-when-downgrade');
$response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->header('Permissions-Policy', 'geolocation=()');
}
}
// index.php или где у вас есть маршруты
// К сведению, эта пустая строка группы действует как глобальный middleware для
// всех маршрутов. Конечно, вы можете сделать то же самое и добавить
// это только к конкретным маршрутам.
Flight::group('', function(Router $router) {
$router->get('/users', [ 'UserController', 'getUsers' ]);
// больше маршрутов
}, [ SecurityHeadersMiddleware::class ]);
Межсайтовый запрос подделки (CSRF)
Межсайтовый запрос подделки (CSRF) — это тип атаки, при которой вредоносный сайт может заставить браузер пользователя отправить запрос на ваш сайт. Это может быть использовано для выполнения действий на вашем сайте без ведома пользователя. Flight не предоставляет встроенный механизм защиты от CSRF, но вы можете легко реализовать свой собственный, используя middleware.
Настройка
Сначала вам нужно сгенерировать токен CSRF и сохранить его в сессии пользователя. Затем вы можете использовать этот токен в ваших формах и проверить его при отправке формы. Мы будем использовать плагин flightphp/session для управления сессиями.
// Генерация токена CSRF и сохранение его в сессии пользователя
// (предполагая, что вы создали объект сессии и прикрепили его к Flight)
// см. документацию по сессиям для получения дополнительной информации
Flight::register('session', flight\Session::class);
// Вам нужно генерировать только один токен на сессию (чтобы он работал
// в нескольких вкладках и запросах для одного пользователя)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
Использование стандартного шаблона PHP Flight
<!-- Использование токена CSRF в вашей форме -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- другие поля формы -->
</form>
Использование Latte
Вы также можете установить пользовательскую функцию для вывода токена CSRF в ваших шаблонах Latte.
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// другие конфигурации...
// Установка пользовательской функции для вывода токена CSRF
$latte->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
$latte->render($finalPath, $data, $block);
});
И теперь в ваших шаблонах Latte вы можете использовать функцию csrf()
для вывода токена CSRF.
<form method="post">
{csrf()}
<!-- другие поля формы -->
</form>
Проверка токена CSRF
Вы можете проверить токен CSRF с помощью нескольких методов.
Middleware
// app/middlewares/CsrfMiddleware.php
namespace app\middleware;
use flight\Engine;
class CsrfMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
if($this->app->request()->method == 'POST') {
$token = $this->app->request()->data->csrf_token;
if($token !== $this->app->session()->get('csrf_token')) {
$this->app->halt(403, 'Invalid CSRF token');
}
}
}
}
// index.php или где у вас есть маршруты
use app\middlewares\CsrfMiddleware;
Flight::group('', function(Router $router) {
$router->get('/users', [ 'UserController', 'getUsers' ]);
// больше маршрутов
}, [ CsrfMiddleware::class ]);
Фильтры событий
// Этот middleware проверяет, является ли запрос 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, 'Invalid CSRF token');
// или для JSON-ответа
Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
}
}
});
Межсайтовый скриптинг (XSS)
Межсайтовый скриптинг (XSS) — это тип атаки, при которой вредоносный ввод формы может внедрить код в ваш сайт. Большинство таких возможностей возникает из значений форм, которые заполнят конечные пользователи. Вы никогда не должны доверять выводу от ваших пользователей! Всегда предполагайте, что все они — лучшие хакеры в мире. Они могут внедрить вредоносный JavaScript или HTML в вашу страницу. Этот код может быть использован для кражи информации от ваших пользователей или выполнения действий на вашем сайте. Используя класс view Flight или другой движок шаблонов, такой как Latte, вы можете легко экранировать вывод для предотвращения атак XSS.
// Предположим, пользователь хитрый и пытается использовать это как свое имя
$name = '<script>alert("XSS")</script>';
// Это экранирует вывод
Flight::view()->set('name', $name);
// Это выведет: <script>alert("XSS")</script>
// Если вы используете что-то вроде Latte, зарегистрированное как ваш класс view, оно также автоматически экранирует это.
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 ]);
Небезопасный пример
Ниже показано, почему мы используем подготовленные SQL-запросы для защиты от невинных примеров, таких как ниже:
// конечный пользователь заполняет веб-форму.
// для значения формы хакер вводит что-то вроде этого:
$username = "' OR 1=1; -- ";
$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// После построения запроса он выглядит так
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Это выглядит странно, но это действительный запрос, который сработает. На самом деле,
// это очень распространенная атака инъекции SQL, которая вернет всех пользователей.
var_dump($users); // это выведет всех пользователей в базе данных, а не только одного с конкретным именем пользователя
CORS
Кросс-доменное совместное использование ресурсов (CORS) — это механизм, который позволяет запрашивать многие ресурсы (например, шрифты, JavaScript и т.д.) на веб-странице с другого домена, отличного от домена, с которого произошел ресурс. Flight не имеет встроенной функциональности, но это можно легко обработать с помощью хука, выполняемого перед методом Flight::start()
.
// app/utils/CorsUtil.php
namespace app\utils;
class CorsUtil
{
public function set(array $params): void
{
$request = Flight::request();
$response = Flight::response();
if ($request->getVar('HTTP_ORIGIN') !== '') {
$this->allowOrigins();
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Max-Age', '86400');
}
if ($request->method === 'OPTIONS') {
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
$response->header(
'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
);
}
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
$response->header(
"Access-Control-Allow-Headers",
$request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
);
}
$response->status(200);
$response->send();
exit;
}
}
private function allowOrigins(): void
{
// настройте здесь разрешенные хосты.
$allowed = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:4200',
'http://localhost:8080',
'http://localhost:8100',
];
$request = Flight::request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = Flight::response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// index.php или где у вас есть маршруты
$CorsUtil = new CorsUtil();
// Это должно выполняться перед запуском start.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);
Обработка ошибок
Скрывайте чувствительные детали ошибок в производстве, чтобы избежать утечки информации атакующим. В производстве логируйте ошибки вместо их отображения с display_errors
, установленным в 0
.
// В вашем bootstrap.php или index.php
// добавьте это в app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
ini_set('display_errors', 0); // Отключить отображение ошибок
ini_set('log_errors', 1); // Логировать ошибки вместо этого
ini_set('error_log', '/path/to/error.log');
}
// В ваших маршрутах или контроллерах
// Используйте Flight::halt() для контролируемых ответов на ошибки
Flight::halt(403, 'Access denied');
Санитизация ввода
Никогда не доверяйте вводу пользователя. Санитизируйте его с помощью filter_var перед обработкой, чтобы предотвратить проникновение вредоносных данных.
// Предположим, POST-запрос с $_POST['input'] и $_POST['email']
// Санитизация строкового ввода
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Санитизация email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);
Хеширование паролей
Храните пароли безопасно и проверяйте их безопасно с помощью встроенных функций PHP, таких как password_hash и password_verify. Пароли никогда не должны храниться в открытом виде, а также не должны шифроваться обратимыми методами. Хеширование обеспечивает, что даже если ваша база данных скомпрометирована, фактические пароли остаются защищенными.
$password = Flight::request()->data->password;
// Хеширование пароля при хранении (например, во время регистрации)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Проверка пароля (например, во время входа)
if (password_verify($password, $stored_hash)) {
// Пароль совпадает
}
Ограничение скорости
Защищайте от атак brute force или атак отказа в обслуживании, ограничивая скорость запросов с помощью кэша.
// Предполагая, что у вас установлен и зарегистрирован flightphp/cache
// Использование flightphp/cache в фильтре
Flight::before('start', function() {
$cache = Flight::cache();
$ip = Flight::request()->ip;
$key = "rate_limit_{$ip}";
$attempts = (int) $cache->retrieve($key);
if ($attempts >= 10) {
Flight::halt(429, 'Too many requests');
}
$cache->set($key, $attempts + 1, 60); // Сброс через 60 секунд
});
См. также
- Сессии - Как безопасно управлять сессиями пользователей.
- Шаблоны - Использование шаблонов для автоматического экранирования вывода и предотвращения XSS.
- PDO Wrapper - Упрощенные взаимодействия с базой данных с подготовленными запросами.
- Middleware - Как использовать middleware для упрощения процесса добавления заголовков безопасности.
- Ответы - Как настраивать HTTP-ответы с безопасными заголовками.
- Запросы - Как обрабатывать и санитизировать ввод пользователя.
- filter_var - Функция PHP для санитизации ввода.
- password_hash - Функция PHP для безопасного хеширования паролей.
- password_verify - Функция PHP для проверки хешированных паролей.
Устранение неисправностей
- Обращайтесь к разделу "См. также" выше для информации по устранению неисправностей, связанной с проблемами компонентов фреймворка Flight.
Журнал изменений
- v3.1.0 - Добавлены разделы о CORS, обработке ошибок, санитизации ввода, хешировании паролей и ограничении скорости.
- v2.0 - Добавлено экранирование для стандартных представлений для предотвращения XSS.
Learn/routing
Маршрутизация
Обзор
Маршрутизация в Flight PHP сопоставляет шаблоны URL с функциями обратного вызова или методами классов, обеспечивая быстрый и простой обработку запросов. Она разработана для минимальных накладных расходов, удобства для начинающих и расширяемости без внешних зависимостей.
Понимание
Маршрутизация — это основной механизм, который соединяет HTTP-запросы с логикой вашего приложения в Flight. Определяя маршруты, вы указываете, как разные URL запускают конкретный код, будь то через функции, методы классов или действия контроллера. Система маршрутизации Flight гибкая, поддерживает базовые шаблоны, именованные параметры, регулярные выражения и продвинутые функции, такие как внедрение зависимостей и ресурсная маршрутизация. Этот подход сохраняет ваш код организованным и легким в обслуживании, оставаясь быстрым и простым для начинающих и расширяемым для продвинутых пользователей.
Примечание: Хотите узнать больше о маршрутизации? Посмотрите страницу "почему фреймворк?" для более подробного объяснения.
Базовое использование
Определение простого маршрута
Базовая маршрутизация в Flight выполняется путем сопоставления шаблона URL с функцией обратного вызова или массивом класса и метода.
Flight::route('/', function(){
echo 'hello world!';
});
Маршруты сопоставляются в порядке их определения. Первый маршрут, соответствующий запросу, будет вызван.
Использование функций в качестве обратных вызовов
Обратный вызов может быть любым объектом, который можно вызвать. Таким образом, вы можете использовать обычную функцию:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');
Использование классов и методов в качестве контроллера
Вы также можете использовать метод (статический или нет) класса:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// or
Flight::route('/', [ GreetingController::class, 'hello' ]); // preferred method
// or
Flight::route('/', [ 'GreetingController::hello' ]);
// or
Flight::route('/', [ 'GreetingController->hello' ]);
Или создав объект сначала, а затем вызвав метод:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);
Примечание: По умолчанию, когда контроллер вызывается в рамках фреймворка, класс
flight\Engine
всегда внедряется, если вы не укажете это через контейнер внедрения зависимостей
Маршрутизация, специфичная для метода
По умолчанию шаблоны маршрутов сопоставляются со всеми методами запросов. Вы можете отвечать на конкретные методы, размещая идентификатор перед URL.
Flight::route('GET /', function () {
echo 'I received a GET request.';
});
Flight::route('POST /', function () {
echo 'I received a POST request.';
});
// You cannot use Flight::get() for routes as that is a method
// to get variables, not create a route.
Flight::post('/', function() { /* code */ });
Flight::patch('/', function() { /* code */ });
Flight::put('/', function() { /* code */ });
Flight::delete('/', function() { /* code */ });
Вы также можете сопоставить несколько методов с одним обратным вызовом, используя разделитель |
:
Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.';
});
Особая обработка запросов HEAD и OPTIONS
Flight предоставляет встроенную обработку HTTP-запросов HEAD
и OPTIONS
:
Запросы HEAD
- Запросы HEAD обрабатываются так же, как запросы
GET
, но Flight автоматически удаляет тело ответа перед отправкой клиенту. - Это означает, что вы можете определить маршрут для
GET
, и запросы HEAD к тому же URL вернут только заголовки (без содержимого), как ожидается по стандартам HTTP.
Flight::route('GET /info', function() {
echo 'This is some info!';
});
// A HEAD request to /info will return the same headers, but no body.
Запросы OPTIONS
Запросы OPTIONS
автоматически обрабатываются Flight для любого определенного маршрута.
- Когда получен запрос OPTIONS, Flight отвечает статусом
204 No Content
и заголовкомAllow
, перечисляющим все поддерживаемые HTTP-методы для этого маршрута. - Вам не нужно определять отдельный маршрут для OPTIONS.
// For a route defined as:
Flight::route('GET|POST /users', function() { /* ... */ });
// An OPTIONS request to /users will respond with:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS
Использование объекта Router
Кроме того, вы можете получить объект Router, который имеет некоторые вспомогательные методы для вашего использования:
$router = Flight::router();
// maps all methods just like Flight::route()
$router->map('/', function() {
echo 'hello world!';
});
// GET request
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* code */});
$router->put('/users/update/@id', function() { /* code */});
$router->delete('/users/@id', function() { /* code */});
$router->patch('/users/@id', function() { /* code */});
Регулярные выражения (Regex)
Вы можете использовать регулярные выражения в своих маршрутах:
Flight::route('/user/[0-9]+', function () {
// This will match /user/1234
});
Хотя этот метод доступен, рекомендуется использовать именованные параметры или именованные параметры с регулярными выражениями, поскольку они более читаемы и легче в обслуживании.
Именованные параметры
Вы можете указать именованные параметры в своих маршрутах, которые будут переданы в вашу функцию обратного вызова. Это больше для читаемости маршрута, чем для чего-либо другого. Пожалуйста, см. раздел ниже о важном предупреждении.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});
Вы также можете включить регулярные выражения с вашими именованными параметрами, используя разделитель :
:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// This will match /bob/123
// But will not match /bob/12345
});
Примечание: Сопоставление групп regex
()
с позиционными параметрами не поддерживается. Ex::'\(
Важное предупреждение
Хотя в примере выше кажется, что @name
напрямую связан с переменной $name
, это не так. Порядок параметров в функции обратного вызова определяет, что передается в нее. Если вы поменяете порядок параметров в функции обратного вызова, переменные также поменяются. Вот пример:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});
И если вы перейдете по следующему URL: /bob/123
, вывод будет hello, 123 (bob)!
.
Пожалуйста, будьте осторожны при настройке ваших маршрутов и функций обратного вызова!
Необязательные параметры
Вы можете указать именованные параметры, которые являются необязательными для сопоставления, обернув сегменты в скобки.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// This will match the following URLS:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Любые необязательные параметры, которые не сопоставлены, будут переданы как NULL
.
Маршрутизация с подстановочным знаком
Сопоставление выполняется только для отдельных сегментов URL. Если вы хотите сопоставить несколько сегментов, вы можете использовать подстановочный знак *
.
Flight::route('/blog/*', function () {
// This will match /blog/2000/02/01
});
Чтобы маршрутизировать все запросы к одному обратному вызову, вы можете сделать:
Flight::route('*', function () {
// Do something
});
Обработчик 404 Not Found
По умолчанию, если URL не найден, Flight отправит простой и обычный ответ HTTP 404 Not Found
.
Если вы хотите иметь более кастомный ответ 404, вы можете сопоставить свой собственный метод notFound
:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// You could also use Flight::render() with a custom template.
$output = <<<HTML
<h1>My Custom 404 Not Found</h1>
<h3>The page you have requested {$url} could not be found.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});
Обработчик Method Not Found
По умолчанию, если URL найден, но метод не разрешен, Flight отправит простой и обычный ответ HTTP 405 Method Not Allowed
(Ex: Method Not Allowed. Allowed Methods are: GET, POST). Он также включит заголовок Allow
с разрешенными методами для этого URL.
Если вы хотите иметь более кастомный ответ 405, вы можете сопоставить свой собственный метод methodNotFound
:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// You could also use Flight::render() with a custom template.
$output = <<<HTML
<h1>My Custom 405 Method Not Allowed</h1>
<h3>The method you have requested for {$url} is not allowed.</h3>
<p>Allowed Methods are: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});
Продвинутое использование
Внедрение зависимостей в маршрутах
Если вы хотите использовать внедрение зависимостей через контейнер (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) {
// do something with $this->pdoWrapper
$name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!";
}
}
// index.php
// Setup the container with whatever params you need
// See the Dependency Injection page for more information on PSR-11
$dice = new \Dice\Dice();
// Don't forget to reassign the variable with '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Register the container handler
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Routes like normal
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// or
Flight::route('/hello/@id', 'Greeting->hello');
// or
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Передача выполнения следующему маршруту
Устарело
Вы можете передать выполнение следующему соответствующему маршруту, вернув true
из вашей функции обратного вызова.
Flight::route('/user/@name', function (string $name) {
// Check some condition
if ($name !== "Bob") {
// Continue to next route
return true;
}
});
Flight::route('/user/*', function () {
// This will get called
});
Теперь рекомендуется использовать middleware для обработки сложных случаев вроде этого.
Псевдонимы маршрутов
Присваивая псевдоним маршруту, вы можете позже динамически вызывать этот псевдоним в вашем приложении для генерации позже в вашем коде (например: ссылка в HTML-шаблоне или генерация URL для перенаправления).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// or
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// later in code somewhere
class UserController {
public function update() {
// code to save user...
$id = $user['id']; // 5 for example
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // will return '/users/5'
Flight::redirect($redirectUrl);
}
}
Это особенно полезно, если ваш URL изменится. В приведенном выше примере предположим, что пользователи перемещены в /admin/users/@id
вместо.
С псевдонимами на месте для маршрута вам больше не нужно искать все старые URL в вашем коде и изменять их, потому что псевдоним теперь вернет /admin/users/5
, как в примере выше.
Псевдонимы маршрутов все еще работают в группах:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// or
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});
Просмотр информации о маршруте
Если вы хотите просмотреть информацию о соответствующем маршруте, есть 2 способа это сделать:
- Вы можете использовать свойство
executedRoute
на объектеFlight::router()
. - Вы можете запросить передачу объекта маршрута в ваш обратный вызов, передав
true
в качестве третьего параметра в методе маршрута. Объект маршрута всегда будет последним параметром, переданным в вашу функцию обратного вызова.
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// Do something with $route
// Array of HTTP methods matched against
$route->methods;
// Array of named parameters
$route->params;
// Matching regular expression
$route->regex;
// Contains the contents of any '*' used in the URL pattern
$route->splat;
// Shows the url path....if you really need it
$route->pattern;
// Shows what middleware is assigned to this
$route->middleware;
// Shows the alias assigned to this route
$route->alias;
});
Примечание: Свойство
executedRoute
будет установлено только после выполнения маршрута. Если вы попытаетесь получить к нему доступ до выполнения маршрута, оно будетNULL
. Вы также можете использовать executedRoute в middleware!
Передача true
в определение маршрута
Flight::route('/', function(\flight\net\Route $route) {
// Array of HTTP methods matched against
$route->methods;
// Array of named parameters
$route->params;
// Matching regular expression
$route->regex;
// Contains the contents of any '*' used in the URL pattern
$route->splat;
// Shows the url path....if you really need it
$route->pattern;
// Shows what middleware is assigned to this
$route->middleware;
// Shows the alias assigned to this route
$route->alias;
}, true);// <-- This true parameter is what makes that happen
Группировка маршрутов и Middleware
Могут быть случаи, когда вы хотите сгруппировать связанные маршруты вместе (например, /api/v1
).
Вы можете сделать это, используя метод group
:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matches /api/v1/users
});
Flight::route('/posts', function () {
// Matches /api/v1/posts
});
});
Вы даже можете вкладывать группы в группы:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() gets variables, it doesn't set a route! See object context below
Flight::route('GET /users', function () {
// Matches GET /api/v1/users
});
Flight::post('/posts', function () {
// Matches POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Matches PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() gets variables, it doesn't set a route! See object context below
Flight::route('GET /users', function () {
// Matches GET /api/v2/users
});
});
});
Группировка с контекстом объекта
Вы все еще можете использовать группировку маршрутов с объектом Engine
следующим образом:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// user the $router variable
$router->get('/users', function () {
// Matches GET /api/v1/users
});
$router->post('/posts', function () {
// Matches POST /api/v1/posts
});
});
Примечание: Это предпочтительный метод определения маршрутов и групп с объектом
$router
.
Группировка с Middleware
Вы также можете назначить middleware группе маршрутов:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matches /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // or [ new MyAuthMiddleware() ] if you want to use an instance
См. больше деталей на странице групповой middleware.
Ресурсная маршрутизация
Вы можете создать набор маршрутов для ресурса, используя метод resource
. Это создаст набор маршрутов для ресурса, следующий RESTful-конвенциям.
Чтобы создать ресурс, сделайте следующее:
Flight::resource('/users', UsersController::class);
И что произойдет в фоне — это создание следующих маршрутов:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]
И ваш контроллер будет использовать следующие методы:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
Примечание: Вы можете просмотреть newly added routes с помощью
runway
, запустивphp runway routes
.
Настройка ресурсных маршрутов
Есть несколько опций для настройки ресурсных маршрутов.
Базовый псевдоним
Вы можете настроить aliasBase
. По умолчанию псевдоним — это последняя часть указанного URL.
Например, /users/
приведет к aliasBase
равному users
. Когда эти маршруты создаются,
псевдонимы — users.index
, users.create
и т.д. Если вы хотите изменить псевдоним, установите aliasBase
в желаемое значение.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only и Except
Вы также можете указать, какие маршруты вы хотите создать, используя опции only
и except
.
// Whitelist only these methods and blacklist the rest
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Blacklist only these methods and whitelist the rest
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
Это по сути опции белого и черного списков, чтобы вы могли указать, какие маршруты вы хотите создать.
Middleware
Вы также можете указать middleware, которое будет выполняться на каждом из маршрутов, созданных методом resource
.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
Потоковые ответы
Теперь вы можете передавать потоковые ответы клиенту с помощью stream()
или streamWithHeaders()
.
Это полезно для отправки больших файлов, длительных процессов или генерации больших ответов.
Потоковый маршрут обрабатывается немного иначе, чем обычный маршрут.
Примечание: Потоковые ответы доступны только если у вас установлен
flight.v2.output_buffering
вfalse
.
Поток с ручными заголовками
Вы можете передать потоковый ответ клиенту, используя метод stream()
на маршруте. Если вы
делаете это, вы должны установить все заголовки вручную перед выводом чего-либо клиенту.
Это делается с помощью функции php header()
или метода Flight::response()->setRealHeader()
.
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// obviously you would sanitize the path and whatnot.
$fileNameSafe = basename($filename);
// If you have additional headers to set here after the route has executed
// you must define them before anything is echoed out.
// They must all be a raw call to the header() function or
// a call to Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// or
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File not found');
}
// manually set the content length if you'd like
header('Content-Length: '.filesize($filePath));
// or
$response->setRealHeader('Content-Length: '.filesize($filePath));
// Stream the file to the client as it's read
readfile($filePath);
// This is the magic line here
})->stream();
Поток с заголовками
Вы также можете использовать метод streamWithHeaders()
для установки заголовков перед началом потоковой передачи.
Flight::route('/stream-users', function() {
// you can add any additional headers you want here
// you just must use header() or Flight::response()->setRealHeader()
// however you pull your data, just as an example...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// This is required to send the data to the client
ob_flush();
}
echo '}';
// This is how you'll set the headers before you start streaming.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// optional status code, defaults to 200
'status' => 200
]);
См. также
- Middleware - Использование middleware с маршрутами для аутентификации, логирования и т.д.
- Dependency Injection - Упрощение создания и управления объектами в маршрутах.
- Why a Framework? - Понимание преимуществ использования фреймворка вроде Flight.
- Extending - Как расширить Flight своей функциональностью, включая метод
notFound
. - php.net: preg_match - Функция PHP для сопоставления регулярных выражений.
Устранение неисправностей
- Параметры маршрута сопоставляются по порядку, а не по имени. Убедитесь, что порядок параметров обратного вызова соответствует определению маршрута.
- Использование
Flight::get()
не определяет маршрут; используйтеFlight::route('GET /...')
для маршрутизации или контекст объекта Router в группах (например,$router->get(...)
). - Свойство executedRoute устанавливается только после выполнения маршрута; до выполнения оно NULL.
- Для потоковой передачи требуется отключить устаревшую функциональность буферизации вывода Flight (
flight.v2.output_buffering = false
). - Для внедрения зависимостей только определенные определения маршрутов поддерживают создание на основе контейнера.
404 Not Found или неожиданное поведение маршрута
Если вы видите ошибку 404 Not Found (но вы клянетесь своей жизнью, что это действительно там и это не опечатка), это на самом деле может быть проблема с возвратом значения в вашем конечном маршруте вместо простого эха. Причина для этого намеренная, но может подкрасться к некоторым разработчикам.
Flight::route('/hello', function(){
// This might cause a 404 Not Found error
return 'Hello World';
});
// What you probably want
Flight::route('/hello', function(){
echo 'Hello World';
});
Причина в том, что есть специальный механизм, встроенный в роутер, который обрабатывает возвращаемый вывод как сигнал "перейти к следующему маршруту". Вы можете увидеть поведение, документированное в разделе Routing.
Журнал изменений
- v3: Добавлена ресурсная маршрутизация, псевдонимы маршрутов, поддержка потоковой передачи, группы маршрутов и поддержка middleware.
- v1: Большинство базовых функций доступны.
Learn/learn
Изучите Flight
Flight — это быстрый, простой и расширяемый фреймворк для PHP. Он довольно универсален и может использоваться для создания любого вида веб-приложения. Он создан с учетом простоты и написан таким образом, чтобы его было легко понять и использовать.
Примечание: Вы увидите примеры, которые используют
Flight::
как статическую переменную, и некоторые, которые используют объект движка$app->
. Оба работают взаимозаменяемо друг с другом.$app
и$this->app
в контроллере/промежуточном ПО — это рекомендуемый подход от команды Flight.
Основные компоненты
Маршрутизация
Узнайте, как управлять маршрутами для вашего веб-приложения. Это также включает группировку маршрутов, параметры маршрутов и промежуточное ПО.
Промежуточное ПО
Узнайте, как использовать промежуточное ПО для фильтрации запросов и ответов в вашем приложении.
Автозагрузка
Узнайте, как автозагружать свои собственные классы в вашем приложении.
Запросы
Узнайте, как обрабатывать запросы и ответы в вашем приложении.
Ответы
Узнайте, как отправлять ответы вашим пользователям.
HTML-шаблоны
Узнайте, как использовать встроенный движок представлений для рендеринга ваших HTML-шаблонов.
Безопасность
Узнайте, как защитить ваше приложение от распространенных угроз безопасности.
Конфигурация
Узнайте, как настраивать фреймворк для вашего приложения.
Менеджер событий
Узнайте, как использовать систему событий для добавления пользовательских событий в ваше приложение.
Расширение Flight
Узнайте, как расширять фреймворк, добавляя свои собственные методы и классы.
Хуки методов и фильтрация
Узнайте, как добавлять хуки событий к вашим методам и внутренним методам фреймворка.
Контейнер внедрения зависимостей (DIC)
Узнайте, как использовать контейнеры внедрения зависимостей (DIC) для управления зависимостями вашего приложения.
Утилитарные классы
Коллекции
Коллекции используются для хранения данных и доступа к ним как к массиву или объекту для удобства использования.
Обертка JSON
Это несколько простых функций, чтобы сделать кодирование и декодирование вашего JSON последовательным.
Обертка PDO
PDO иногда может добавлять больше головной боли, чем необходимо. Этот простой класс-обертка может значительно упростить взаимодействие с вашей базой данных.
Обработчик загруженных файлов
Простой класс для помощи в управлении загруженными файлами и перемещении их в постоянное место.
Важные концепции
Почему фреймворк?
Вот короткая статья о том, почему вы должны использовать фреймворк. Это хорошая идея понять преимущества использования фреймворка, прежде чем начать его использовать.
Кроме того, отличный учебник создан @lubiana. Хотя он не углубляется в детали конкретно о Flight, это руководство поможет вам понять некоторые из основных концепций, окружающих фреймворк, и почему они полезны. Вы можете найти учебник здесь.
Flight по сравнению с другими фреймворками
Если вы мигрируете с другого фреймворка, такого как Laravel, Slim, Fat-Free или Symfony, на Flight, эта страница поможет вам понять различия между ними.
Другие темы
Юнит-тестирование
Следуйте этому руководству, чтобы узнать, как проводить юнит-тестирование вашего кода Flight, чтобы он был надежным.
ИИ и опыт разработчика
Узнайте, как Flight работает с инструментами ИИ и современными рабочими процессами разработчиков, чтобы помочь вам кодить быстрее и умнее.
Миграция v2 -> v3
Обратная совместимость в основном сохранена, но есть некоторые изменения, о которых вы должны знать при миграции с v2 на v3.
Learn/unit_testing
Тестирование единиц
Обзор
Тестирование единиц в Flight помогает убедиться, что ваше приложение ведет себя как ожидается, обнаруживать ошибки на ранних этапах и делать вашу кодовую базу проще в обслуживании. Flight разработан для беспроблемной работы с PHPUnit, наиболее популярным фреймворком для тестирования PHP.
Понимание
Тесты единиц проверяют поведение небольших частей вашего приложения (например, контроллеров или сервисов) в изоляции. В Flight это означает тестирование того, как ваши маршруты, контроллеры и логика реагируют на различные входные данные — без зависимости от глобального состояния или реальных внешних сервисов.
Ключевые принципы:
- Тестируйте поведение, а не реализацию: Сосредоточьтесь на том, что делает ваш код, а не на том, как он это делает.
- Избегайте глобального состояния: Используйте внедрение зависимостей вместо
Flight::set()
илиFlight::get()
. - Мокируйте внешние сервисы: Заменяйте такие вещи, как базы данных или почтовые сервисы, тестами-заменителями.
- Держите тесты быстрыми и сфокусированными: Тесты единиц не должны обращаться к реальным базам данных или API.
Основное использование
Настройка PHPUnit
- Установите PHPUnit с помощью Composer:
composer require --dev phpunit/phpunit
- Создайте директорию
tests
в корне вашего проекта. - Добавьте скрипт теста в ваш
composer.json
:"scripts": { "test": "phpunit --configuration phpunit.xml" }
- Создайте файл
phpunit.xml
:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Теперь вы можете запускать тесты с помощью composer test
.
Тестирование простого обработчика маршрута
Предположим, у вас есть маршрут, который валидирует email:
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
return $this->app->json(['status' => 'success', 'message' => 'Valid email']);
}
}
Простой тест для этого контроллера:
use PHPUnit\Framework\TestCase;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$app->request()->data->email = 'invalid-email';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Советы:
- Симулируйте POST-данные с помощью
$app->request()->data
. - Избегайте использования статических методов
Flight::
в тестах — используйте экземпляр$app
.
Использование внедрения зависимостей для тестируемых контроллеров
Внедряйте зависимости (например, базу данных или почтовый сервис) в контроллеры, чтобы сделать их простыми для мокинга в тестах:
use flight\database\PdoWrapper;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct($app, $db, $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
И тест с моками:
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$mockDb = $this->createMock(flight\database\PdoWrapper::class);
$mockDb->method('runQuery')->willReturn(true);
$mockMailer = new class {
public $sentEmail = null;
public function sendWelcome($email) { $this->sentEmail = $email; return true; }
};
$app = new flight\Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
}
Продвинутое использование
- Мокирование: Используйте встроенные моки PHPUnit или анонимные классы для замены зависимостей.
- Тестирование контроллеров напрямую: Создавайте экземпляры контроллеров с новым
Engine
и мокайте зависимости. - Избегайте чрезмерного мокирования: Позволяйте реальной логике выполняться, где это возможно; мокайте только внешние сервисы.
См. также
- Unit Testing Guide - Полное руководство по лучшим практикам тестирования единиц.
- Dependency Injection Container - Как использовать DIC для управления зависимостями и улучшения тестируемости.
- Extending - Как добавлять свои помощники или переопределять основные классы.
- PDO Wrapper - Упрощает взаимодействие с базой данных и проще мокать в тестах.
- Requests - Обработка HTTP-запросов в Flight.
- Responses - Отправка ответов пользователям.
- Unit Testing and SOLID Principles - Узнайте, как принципы SOLID могут улучшить ваши тесты единиц.
Устранение неисправностей
- Избегайте использования глобального состояния (
Flight::set()
,$_SESSION
и т.д.) в вашем коде и тестах. - Если ваши тесты медленные, возможно, вы пишете интеграционные тесты — мокайте внешние сервисы, чтобы держать тесты единиц быстрыми.
- Если настройка тестов сложная, рассмотрите рефакторинг кода для использования внедрения зависимостей.
Журнал изменений
- v3.15.0 - Добавлены примеры для внедрения зависимостей и мокирования.
Learn/flight_vs_symfony
Сравнение Flight и Symfony
Что такое Symfony?
Symfony - набор многоразовых компонентов PHP и фреймворк PHP для веб-проектов.
Стандартный фундамент, на котором строятся лучшие приложения на PHP. Выберите любые из 50 доступных автономных компонентов для ваших собственных приложений.
Ускорьте создание и поддержку ваших веб-приложений на PHP. Заканчивайте повторяющиеся задачи по кодированию и наслаждайтесь возможностью контролировать ваш код.
Преимущества по сравнению с Flight
- Symfony имеет огромную экосистему разработчиков и модулей, которые могут использоваться для решения общих проблем.
- Symfony имеет полнофункциональный ORM (Doctrine), который можно использовать для взаимодействия с вашей базой данных.
- Symfony имеет большое количество документации и учебных пособий, которые могут быть использованы для изучения фреймворка.
- Symfony имеет подкасты, конференции, встречи, видео и другие ресурсы, которые можно использовать для изучения фреймворка.
- Symfony ориентирован на опытного разработчика, который стремится создать полнофункциональное корпоративное веб-приложение.
Недостатки по сравнению с Flight
- Symfony имеет гораздо больше происходящего "под капотом", чем Flight. Это происходит за счет радикальных затрат в терминах производительности. См. бенчмарки TechEmpower для более подробной информации
- Flight ориентирован на разработчика, который стремится создать легкое, быстрое и простое в использовании веб-приложение.
- Flight ориентирован на простоту и удобство использования.
- Одной из основных особенностей Flight является то, что он делает все возможное для поддержания обратной совместимости.
- У Flight нет зависимостей, в то время как у Symfony есть целый ряд зависимостей
- Flight предназначен для разработчиков, которые впервые погружаются в мир фреймворков.
- Flight также может обрабатывать корпоративные приложения, но у него не так много примеров и учебных пособий, как у Symfony. Также для поддержания порядка и хорошей структуры разработчику потребуется больше дисциплины.
- Flight дает разработчику больший контроль над приложением, в то время как Symfony может скрыто использовать магию за кулисами.
Learn/flight_vs_another_framework
Сравнение Flight с другим фреймворком
Если вы переходите с другого фреймворка, такого как Laravel, Slim, Fat-Free или Symfony, на Flight, эта страница поможет вам понять различия между ними.
Laravel
Laravel - это полнофункциональный фреймворк со всеми плюшками и удивительной экосистемой, сосредоточенной на разработчике, но за счет производительности и сложности.
Slim
Slim - это микро-фреймворк, похожий на Flight. Он разработан с упором на легкость использования, но может быть немного сложнее, чем Flight.
Fat-Free
Fat-Free - это полностековый фреймворк в намного меньшем объеме. Хотя в нем есть все необходимые инструменты, его архитектура данных может усложнить некоторые проекты более, чем это необходимо.
Symfony
Symfony - модульный фреймворк корпоративного уровня, разработанный для гибкости и масштабируемости. Для меньших проектов или новых разработчиков Symfony может быть немного подавляющим.
Learn/pdo_wrapper
PdoWrapper Вспомогательный класс PDO
Обзор
Класс PdoWrapper
в Flight — это удобный помощник для работы с базами данных с использованием PDO. Он упрощает распространенные задачи работы с базами данных, добавляет полезные методы для получения результатов и возвращает результаты в виде Collections для легкого доступа. Он также поддерживает логирование запросов и мониторинг производительности приложений (APM) для продвинутых сценариев использования.
Понимание
Работа с базами данных в PHP может быть немного многословной, особенно при прямом использовании PDO. PdoWrapper
расширяет PDO и добавляет методы, которые делают запросы, получение и обработку результатов гораздо проще. Вместо жонглирования подготовленными выражениями и режимами получения, вы получаете простые методы для распространенных задач, и каждая строка возвращается как Collection, чтобы вы могли использовать нотацию массива или объекта.
Вы можете зарегистрировать PdoWrapper
как общую службу в Flight, а затем использовать его в любом месте вашего приложения через Flight::db()
.
Базовое использование
Регистрация вспомогательного PDO
Сначала зарегистрируйте класс PdoWrapper
в Flight:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
Теперь вы можете использовать Flight::db()
в любом месте для получения подключения к базе данных.
Выполнение запросов
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Используйте это для INSERT, UPDATE или когда вы хотите получить результаты вручную:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row is an array
}
Вы также можете использовать его для записи:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Получите одно значение из базы данных:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): Collection
Получите одну строку как Collection (доступ как к массиву/объекту):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// or
echo $user->name;
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Получите все строки как массив Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// or
echo $user->name;
}
Использование заполнителей IN()
Вы можете использовать один ?
в предложении IN()
и передать массив или строку с разделителями-запятыми:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// or
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);
Продвинутое использование
Логирование запросов и APM
Если вы хотите отслеживать производительность запросов, включите отслеживание APM при регистрации:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* options */], true // last param enables APM
]);
После выполнения запросов вы можете логировать их вручную, но APM будет логировать их автоматически, если включено:
Flight::db()->logQueries();
Это запустит событие (flight.db.queries
) с метриками подключения и запросов, на которое вы можете подписаться с помощью системы событий Flight.
Полный пример
Flight::route('/users', function () {
// Get all users
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Stream all users
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Get a single user
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Get a single value
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Special IN() syntax
$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']);
// Insert a new user
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
$insert_id = Flight::db()->lastInsertId();
// Update a user
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);
// Delete a user
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);
// Get the number of affected rows
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
$affected_rows = $statement->rowCount();
});
См. также
- Collections - Узнайте, как использовать класс Collection для легкого доступа к данным.
Устранение неисправностей
- Если вы получаете ошибку о подключении к базе данных, проверьте DSN, имя пользователя, пароль и опции.
- Все строки возвращаются как Collections — если вам нужен обычный массив, используйте
$collection->getData()
. - Для запросов
IN (?)
убедитесь, что вы передаете массив или строку с разделителями-запятыми.
Журнал изменений
- v3.2.0 - Первое выпущение PdoWrapper с базовыми методами запросов и получения.
Learn/dependency_injection_container
Контейнер внедрения зависимостей
Обзор
Контейнер внедрения зависимостей (DIC) — это мощное расширение, которое позволяет управлять зависимостями вашего приложения.
Понимание
Внедрение зависимостей (DI) — это ключевой концепт в современных PHP-фреймворках и используется для управления созданием и конфигурацией объектов. Некоторые примеры библиотек DIC: flightphp/container, Dice, Pimple, PHP-DI, и league/container.
DIC — это изысканный способ позволить вам создавать и управлять вашими классами в централизованном месте. Это полезно, когда вам нужно передавать один и тот же объект нескольким классам (например, вашим контроллерам или middleware).
Основное использование
Старый способ может выглядеть так:
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());
}
}
// в вашем файле routes.php
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// другие маршруты UserController...
Flight::start();
Из приведенного выше кода видно, что мы создаем новый объект PDO
и передаем его в класс UserController
. Это нормально для небольшого приложения, но по мере роста вашего приложения вы обнаружите, что создаете или передаете один и тот же объект PDO
в нескольких местах. Здесь DIC становится очень полезным.
Вот тот же пример с использованием DIC (используя Dice):
require 'vendor/autoload.php';
// тот же класс, что и выше. Ничего не изменилось
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// создаем новый контейнер
$container = new \Dice\Dice;
// добавляем правило, чтобы сказать контейнеру, как создать объект PDO
// не забудьте переприсвоить его самому себе, как ниже!
$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::class, 'view' ]);
Flight::start();
Я уверен, вы можете подумать, что в примере добавилось много лишнего кода. Магия происходит, когда у вас есть другой контроллер, которому нужен объект PDO
.
// Если все ваши контроллеры имеют конструктор, который требует объект PDO
// каждый из маршрутов ниже автоматически получит его внедренным!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);
Дополнительным преимуществом использования DIC является то, что модульное тестирование становится гораздо проще. Вы можете создать мок-объект и передать его в ваш класс. Это огромное преимущество при написании тестов для вашего приложения!
Создание централизованного обработчика DIC
Вы можете создать централизованный обработчик DIC в вашем файле services, расширив ваше приложение через extending. Вот пример:
// services.php
// создаем новый контейнер
$container = new \Dice\Dice;
// не забудьте переприсвоить его самому себе, как ниже!
$container = $container->addRule('PDO', [
// shared означает, что один и тот же объект будет возвращен каждый раз
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// теперь мы можем создать маппируемый метод для создания любого объекта.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Это регистрирует обработчик контейнера, чтобы Flight знал, как использовать его для контроллеров/middleware
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// предположим, у нас есть следующий пример класса, который принимает объект PDO в конструкторе
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// код для отправки email
}
}
// И наконец, вы можете создавать объекты с использованием внедрения зависимостей
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
flightphp/container
Flight имеет плагин, который предоставляет простой контейнер, соответствующий PSR-11, который вы можете использовать для обработки внедрения зависимостей. Вот быстрый пример, как его использовать:
// index.php например
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// выведет это правильно!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();
Расширенное использование flightphp/container
Вы также можете разрешать зависимости рекурсивно. Вот пример:
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// Реализация ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/
DICE
Вы также можете создать свой собственный обработчик DIC. Это полезно, если у вас есть кастомный контейнер, который вы хотите использовать и который не соответствует PSR-11 (Dice). См. раздел basic usage о том, как это сделать.
Кроме того, есть некоторые полезные значения по умолчанию, которые облегчат вам жизнь при использовании Flight.
Экземпляр Engine
Если вы используете экземпляр Engine
в ваших контроллерах/middleware, вот как вы бы его настроили:
// Где-то в вашем bootstrap-файле
$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 в ваших контроллерах/middleware
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');
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, но все равно выполняет задачу с теми же преимуществами!
См. также
- Extending Flight - Узнайте, как вы можете добавить внедрение зависимостей в свои собственные классы, расширив фреймворк.
- Configuration - Узнайте, как настроить Flight для вашего приложения.
- Routing - Узнайте, как определять маршруты для вашего приложения и как работает внедрение зависимостей с контроллерами.
- Middleware - Узнайте, как создавать middleware для вашего приложения и как работает внедрение зависимостей с middleware.
Устранение неисправностей
- Если у вас проблемы с контейнером, убедитесь, что вы передаете правильные имена классов в контейнер.
Журнал изменений
- v3.7.0 - Добавлена возможность регистрации обработчика DIC в Flight.
Learn/middleware
Middleware
Обзор
Flight поддерживает middleware для маршрутов и групп маршрутов. Middleware — это часть вашего приложения, где код выполняется до (или после) обратного вызова маршрута. Это отличный способ добавить проверки аутентификации API в ваш код или убедиться, что пользователь имеет разрешение на доступ к маршруту.
Понимание
Middleware может значительно упростить ваше приложение. Вместо сложного наследования абстрактных классов или переопределения методов middleware позволяет контролировать маршруты, присваивая им вашу пользовательскую логику приложения. Вы можете думать о middleware как о сэндвиче. У вас хлеб снаружи, а затем слои ингредиентов, такие как салат, помидоры, мясо и сыр. Затем представьте, что каждый запрос похож на укус сэндвича, где вы сначала едите внешние слои и продвигаетесь к центру.
Вот визуализация того, как работает middleware. Затем мы покажем вам практический пример того, как это функционирует.
Запрос пользователя по URL /api ---->
Middleware->before() выполняется ----->
Вызываемый метод, прикреплённый к /api, выполняется, и ответ генерируется ------>
Middleware->after() выполняется ----->
Пользователь получает ответ от сервера
А вот практический пример:
Пользователь переходит по URL /dashboard
LoggedInMiddleware->before() выполняется
before() проверяет наличие действительной сессии входа
если да, ничего не делать и продолжить выполнение
если нет, перенаправить пользователя на /login
Вызываемый метод, прикреплённый к /api, выполняется, и ответ генерируется
LoggedInMiddleware->after() ничего не определено, поэтому позволяет выполнению продолжиться
Пользователь получает HTML дашборда от сервера
Порядок выполнения
Функции middleware выполняются в порядке их добавления к маршруту. Выполнение аналогично тому, как Slim Framework обрабатывает это.
Методы before()
выполняются в порядке добавления, а методы after()
— в обратном порядке.
Пример: Middleware1->before(), Middleware2->before(), Middleware2->after(), Middleware1->after().
Базовое использование
Вы можете использовать middleware как любой вызываемый метод, включая анонимную функцию или класс (рекомендуется).
Анонимная функция
Вот простой пример:
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// Это выведет "Middleware first! Here I am!"
Примечание: При использовании анонимной функции интерпретируется только метод
before()
. Вы не можете определить поведениеafter()
с анонимным классом.
Использование классов
Middleware можно (и следует) регистрировать как класс. Если вам нужна функциональность "after", вы должны использовать класс.
class MyMiddleware {
public function before($params) {
echo 'Middleware first!';
}
public function after($params) {
echo 'Middleware last!';
}
}
$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware);
// также ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);
Flight::start();
// Это отобразит "Middleware first! Here I am! Middleware last!"
Вы также можете просто указать имя класса middleware, и он будет создан экземпляр.
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class);
Примечание: Если вы передаёте только имя middleware, оно автоматически будет выполнено контейнером внедрения зависимостей, и middleware будет выполнен с параметрами, которые ему нужны. Если у вас не зарегистрирован контейнер внедрения зависимостей, по умолчанию будет передан экземпляр
flight\Engine
в__construct(Engine $app)
.
Использование маршрутов с параметрами
Если вам нужны параметры из маршрута, они будут переданы в виде единого массива в функцию middleware. (function($params) { ... }
или public function before($params) { ... }
). Причина в том, что вы можете структурировать параметры в группы, и в некоторых из этих групп параметры могут появляться в другом порядке, что нарушит функцию middleware при обращении к неправильному параметру. Таким образом, вы можете обращаться к ним по имени, а не по позиции.
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
// jobId может быть передан или нет
$jobId = $params['jobId'] ?? 0;
// возможно, если нет ID задания, вам не нужно ничего искать.
if($jobId === 0) {
return;
}
// выполнить поиск какого-то рода в вашей базе данных
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
// Эта группа ниже всё ещё получает middleware родителя
// Но параметры передаются в одном единственном массиве
// в middleware.
$router->group('/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// больше маршрутов...
});
}, [ RouteSecurityMiddleware::class ]);
Группировка маршрутов с middleware
Вы можете добавить группу маршрутов, и каждый маршрут в этой группе будет иметь одинаковый middleware. Это полезно, если вам нужно сгруппировать множество маршрутов, например, с помощью middleware Auth для проверки API-ключа в заголовке.
// добавлено в конце метода группы
Flight::group('/api', function() {
// Этот "пустой" маршрут на самом деле соответствует /api
Flight::route('', function() { echo 'api'; }, false, 'api');
// Это соответствует /api/users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Это соответствует /api/users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);
Если вы хотите применить глобальный middleware ко всем вашим маршрутам, вы можете добавить "пустую" группу:
// добавлено в конце метода группы
Flight::group('', function() {
// Это всё ещё /users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// И это всё ещё /users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // или [ new ApiAuthMiddleware() ], одно и то же
Распространённые случаи использования
Валидация API-ключа
Если вы хотите защитить маршруты /api
, проверяя, что API-ключ правильный, вы можете легко справиться с этим с помощью middleware.
use flight\Engine;
class ApiMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$authorizationHeader = $this->app->request()->getHeader('Authorization');
$apiKey = str_replace('Bearer ', '', $authorizationHeader);
// выполнить поиск в вашей базе данных для API-ключа
$apiKeyHash = hash('sha256', $apiKey);
$hasValidApiKey = !!$this->db()->fetchField("SELECT 1 FROM api_keys WHERE hash = ? AND valid_date >= NOW()", [ $apiKeyHash ]);
if($hasValidApiKey !== true) {
$this->app->jsonHalt(['error' => 'Invalid API Key']);
}
}
}
// routes.php
$router->group('/api', function(Router $router) {
$router->get('/users', [ ApiController::class, 'getUsers' ]);
$router->get('/companies', [ ApiController::class, 'getCompanies' ]);
// больше маршрутов...
}, [ ApiMiddleware::class ]);
Теперь все ваши API-маршруты защищены этим middleware валидации API-ключа, который вы настроили! Если вы добавите больше маршрутов в группу роутера, они мгновенно получат ту же защиту!
Валидация входа в систему
Хотите ли вы защитить некоторые маршруты, чтобы они были доступны только пользователям, которые вошли в систему? Это легко достижимо с помощью middleware!
use flight\Engine;
class LoggedInMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$session = $this->app->session();
if($session->get('logged_in') !== true) {
$this->app->redirect('/login');
exit;
}
}
}
// routes.php
$router->group('/admin', function(Router $router) {
$router->get('/dashboard', [ DashboardController::class, 'index' ]);
$router->get('/clients', [ ClientController::class, 'index' ]);
// больше маршрутов...
}, [ LoggedInMiddleware::class ]);
Валидация параметров маршрута
Хотите ли вы защитить своих пользователей от изменения значений в URL для доступа к данным, к которым они не должны иметь доступ? Это можно решить с помощью middleware!
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
$jobId = $params['jobId'];
// выполнить поиск какого-то рода в вашей базе данных
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// больше маршрутов...
}, [ RouteSecurityMiddleware::class ]);
Обработка выполнения middleware
Предположим, у вас есть middleware аутентификации, и вы хотите перенаправить пользователя на страницу входа, если он не аутентифицирован. У вас есть несколько вариантов:
- Вы можете вернуть false из функции middleware, и Flight автоматически вернёт ошибку 403 Forbidden, но без настройки.
- Вы можете перенаправить пользователя на страницу входа с помощью
Flight::redirect()
. - Вы можете создать пользовательскую ошибку внутри middleware и остановить выполнение маршрута.
Простой и прямолинейный
Вот простой пример return false;
:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
return false;
}
// поскольку это true, всё просто продолжается
}
}
Пример перенаправления
Вот пример перенаправления пользователя на страницу входа:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
Flight::redirect('/login');
exit;
}
}
}
Пример пользовательской ошибки
Предположим, вам нужно выбросить JSON-ошибку, потому что вы строите API. Вы можете сделать это так:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->getHeader('Authorization');
if(empty($authorization)) {
Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
// или
Flight::json(['error' => 'You must be logged in to access this page.'], 403);
exit;
// или
Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']);
}
}
}
См. также
- Маршрутизация - Как сопоставлять маршруты с контроллерами и рендерить представления.
- Запросы - Понимание того, как обрабатывать входящие запросы.
- Ответы - Как настраивать HTTP-ответы.
- Внедрение зависимостей - Упрощение создания и управления объектами в маршрутах.
- Почему фреймворк? - Понимание преимуществ использования фреймворка вроде Flight.
- Пример стратегии выполнения middleware
Устранение неисправностей
- Если у вас есть перенаправление в middleware, но ваше приложение не перенаправляется, убедитесь, что вы добавили инструкцию
exit;
в middleware.
Журнал изменений
- v3.1: Добавлена поддержка middleware.
Learn/filtering
Фильтрация
Обзор
Flight позволяет вам фильтровать отображенные методы до и после их вызова.
Понимание
Нет предопределенных хуков, которые вам нужно запоминать. Вы можете фильтровать любой из стандартных методов фреймворка, а также любые пользовательские методы, которые вы отобразили.
Функция фильтра выглядит так:
/**
* @param array $params Параметры, переданные в фильтруемый метод.
* @param string $output (только буферизация вывода v2) Вывод фильтруемого метода.
* @return bool Верните true/void или не возвращайте ничего, чтобы продолжить цепочку, false, чтобы прервать цепочку.
*/
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!";
});
// Добавьте фильтр before
Flight::before('hello', function (array &$params, string &$output): bool {
// Манипулируйте параметром
$params[0] = 'Fred';
return true;
});
// Добавьте фильтр after
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
, не могут быть отфильтрованы, потому что они вызываются напрямую и не вызываются динамически. См. Расширение Flight для получения дополнительной информации.
См. также
Устранение неисправностей
- Убедитесь, что вы возвращаете
false
из ваших функций фильтра, если хотите, чтобы цепочка остановилась. Если вы ничего не возвращаете, цепочка продолжится.
Журнал изменений
- v2.0 - Первое издание.
Learn/requests
Запросы
Обзор
Flight инкапсулирует HTTP-запрос в один объект, к которому можно получить доступ следующим образом:
$request = Flight::request();
Понимание
HTTP-запросы — это один из основных аспектов, которые нужно понять о жизненном цикле HTTP. Пользователь выполняет действие в веб-браузере или HTTP-клиенте, и они отправляют серию заголовков, тело, URL и т.д. в ваш проект. Вы можете захватить эти заголовки (язык браузера, тип сжатия, который они могут обрабатывать, пользовательский агент и т.д.) и захватить тело и URL, отправляемые в ваше приложение Flight. Эти запросы необходимы для вашего приложения, чтобы понять, что делать дальше.
Основное использование
PHP имеет несколько суперглобальных переменных, включая $_GET
, $_POST
, $_REQUEST
, $_SERVER
, $_FILES
и $_COOKIE
. Flight абстрагирует их в удобные Collections. Вы можете обращаться к свойствам query
, data
, cookies
и files
как к массивам или объектам.
Примечание: СТРОГО не рекомендуется использовать эти суперглобальные переменные в вашем проекте; они должны ссылаться через объект
request()
.Примечание: Нет доступной абстракции для
$_ENV
.
$_GET
Вы можете получить доступ к массиву $_GET
через свойство query
:
// GET /search?keyword=something
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
// or
$keyword = Flight::request()->query->keyword;
echo "You are searching for: $keyword";
// query a database or something else with the $keyword
});
$_POST
Вы можете получить доступ к массиву $_POST
через свойство data
:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
// or
$name = Flight::request()->data->name;
$email = Flight::request()->data->email;
echo "You submitted: $name, $email";
// save to a database or something else with the $name and $email
});
$_COOKIE
Вы можете получить доступ к массиву $_COOKIE
через свойство cookies
:
Flight::route('GET /login', function(){
$savedLogin = Flight::request()->cookies['myLoginCookie'];
// or
$savedLogin = Flight::request()->cookies->myLoginCookie;
// check if it's really saved or not and if it is auto log them in
if($savedLogin) {
Flight::redirect('/dashboard');
return;
}
});
Для помощи по установке новых значений cookie см. overclokk/cookie
$_SERVER
Доступен ярлык для доступа к массиву $_SERVER
через метод getVar()
:
$host = Flight::request()->getVar('HTTP_HOST');
$_FILES
Вы можете получить доступ к загруженным файлам через свойство files
:
// raw access to $_FILES property. See below for recommended approach
$uploadedFile = Flight::request()->files['myFile'];
// or
$uploadedFile = Flight::request()->files->myFile;
См. Uploaded File Handler для получения дополнительной информации.
Обработка загрузки файлов
v3.12.0
Вы можете обрабатывать загрузку файлов с помощью фреймворка, используя некоторые вспомогательные методы. По сути, это сводится к извлечению данных файла из запроса и перемещению его в новое место.
Flight::route('POST /upload', function(){
// If you had an input field like <input type="file" name="myFile">
$uploadedFileData = Flight::request()->getUploadedFiles();
$uploadedFile = $uploadedFileData['myFile'];
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});
Если у вас загружено несколько файлов, вы можете перебрать их:
Flight::route('POST /upload', function(){
// If you had an input field like <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
foreach ($uploadedFiles as $uploadedFile) {
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
}
});
Примечание по безопасности: Всегда проверяйте и очищайте пользовательский ввод, особенно при работе с загрузкой файлов. Всегда проверяйте типы расширений, которые вы разрешаете загружать, но также проверяйте "магические байты" файла, чтобы убедиться, что это действительно тип файла, который утверждает пользователь. Есть статьи и библиотеки, доступные для помощи в этом.
Тело запроса
Чтобы получить сырое тело HTTP-запроса, например, при работе с POST/PUT-запросами, вы можете сделать:
Flight::route('POST /users/xml', function(){
$xmlBody = Flight::request()->getBody();
// do something with the XML that was sent.
});
JSON-тело
Если вы получаете запрос с типом содержимого application/json
и примером данных {"id": 123}
, он будет доступен из свойства data
:
$id = Flight::request()->data->id;
Заголовки запроса
Вы можете получить доступ к заголовкам запроса с помощью метода getHeader()
или getHeaders()
:
// Maybe you need Authorization header
$host = Flight::request()->getHeader('Authorization');
// or
$host = Flight::request()->header('Authorization');
// If you need to grab all headers
$headers = Flight::request()->getHeaders();
// or
$headers = Flight::request()->headers();
Метод запроса
Вы можете получить доступ к методу запроса с помощью свойства method
или метода getMethod()
:
$method = Flight::request()->method; // actually populated by getMethod()
$method = Flight::request()->getMethod();
Примечание: Метод getMethod()
сначала извлекает метод из $_SERVER['REQUEST_METHOD']
, затем он может быть перезаписан $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']
, если он существует, или $_REQUEST['_method']
, если он существует.
Свойства объекта запроса
Объект запроса предоставляет следующие свойства:
- body - Сырое тело HTTP-запроса
- url - Запрашиваемый URL
- base - Родительская поддиректория URL
- method - Метод запроса (GET, POST, PUT, DELETE)
- referrer - URL реферера
- ip - IP-адрес клиента
- ajax - Является ли запрос AJAX-запросом
- scheme - Протокол сервера (http, https)
- user_agent - Информация о браузере
- type - Тип содержимого
- length - Длина содержимого
- query - Параметры строки запроса
- data - Данные POST или JSON-данные
- cookies - Данные cookie
- files - Загруженные файлы
- secure - Является ли соединение безопасным
- accept - Параметры HTTP accept
- proxy_ip - IP-адрес прокси клиента. Сканирует массив
$_SERVER
на наличиеHTTP_CLIENT_IP
,HTTP_X_FORWARDED_FOR
,HTTP_X_FORWARDED
,HTTP_X_CLUSTER_CLIENT_IP
,HTTP_FORWARDED_FOR
,HTTP_FORWARDED
в этом порядке. - host - Имя хоста запроса
- servername - SERVER_NAME из
$_SERVER
Вспомогательные методы
Есть несколько вспомогательных методов для сборки частей URL или работы с определенными заголовками.
Полный URL
Вы можете получить доступ к полному URL запроса с помощью метода getFullUrl()
:
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
Базовый URL
Вы можете получить доступ к базовому URL с помощью метода getBaseUrl()
:
// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// Notice, no trailing slash.
Разбор запроса
Вы можете передать URL методу parseQuery()
, чтобы разобрать строку запроса в ассоциативный массив:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
Переговоры по типам содержимого Accept
v3.17.2
Вы можете использовать метод negotiateContentType()
, чтобы определить лучший тип содержимого для ответа на основе заголовка Accept
, отправленного клиентом.
// Example Accept header: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// The below defines what you support.
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
// Serve JSON response
} elseif ($typeToServe === 'application/xml') {
// Serve XML response
} else {
// Default to something else or throw an error
}
Примечание: Если ни один из доступных типов не найден в заголовке
Accept
, метод вернетnull
. Если заголовокAccept
не определен, метод вернет первый тип в массиве$availableTypes
.
См. также
- Routing - См., как сопоставлять маршруты с контроллерами и рендерить представления.
- Responses - Как настраивать HTTP-ответы.
- Why a Framework? - Как запросы вписываются в общую картину.
- Collections - Работа с коллекциями данных.
- Uploaded File Handler - Обработка загрузки файлов.
Устранение неисправностей
request()->ip
иrequest()->proxy_ip
могут отличаться, если ваш веб-сервер находится за прокси, балансировщиком нагрузки и т.д.
Журнал изменений
- v3.17.2 - Добавлен negotiateContentType()
- v3.12.0 - Добавлена возможность обработки загрузки файлов через объект запроса.
- v1.0 - Первое выпущение.
Learn/why_frameworks
Почему фреймворк?
Некоторые программисты решительно против использования фреймворков. Они утверждают, что фреймворки избыточны, медленны и сложны в изучении. Они говорят, что фреймворки не нужны, и что можно писать лучший код без них. Конечно, есть несколько обоснованных аргументов против использования фреймворков. Однако, есть также много преимуществ в использовании фреймворков.
Причины использования фреймворка
Вот несколько причин, почему вам может захотеться рассмотреть использование фреймворка:
- Быстрая разработка: Фреймворки предоставляют много функциональности из коробки. Это означает, что вы можете создавать веб-приложения быстрее. Вам не нужно писать столько кода, потому что фреймворк предоставляет много функциональности, которая вам нужна.
- Согласованность: Фреймворки предоставляют последовательный способ сделать вещи. Это облегчает понимание работы кода и упрощает другим разработчикам понимание вашего кода. Если у вас есть скрипт за скриптом, вы можете потерять согласованность между скриптами, особенно если вы работаете с командой разработчиков.
- Безопасность: Фреймворки предоставляют функции безопасности, которые помогают защитить ваши веб-приложения от распространенных угроз безопасности. Это означает, что вам не нужно беспокоиться о безопасности, потому что фреймворк заботится о большей части этого за вас.
- Сообщество: У фреймворков есть большие сообщества разработчиков, которые вносят свой вклад во фреймворк. Это означает, что вы можете получить помощь от других разработчиков, когда у вас возникают вопросы или проблемы. Это также означает, что доступно множество ресурсов для помощи в изучении использования фреймворка.
- Лучшие практики: Фреймворки созданы с использованием лучших практик. Это означает, что вы можете учиться у фреймворка и использовать те же лучшие практики в своем собственном коде. Это может помочь вам стать лучшим программистом. Иногда вы не знаете, чего не знаете, и это может вам плохо повлиять в конечном итоге.
- Расширяемость: Фреймворки разработаны для расширения. Это означает, что вы можете добавлять свою собственную функциональность в фреймворк. Это позволяет создавать веб-приложения, которые соответствуют вашим конкретным потребностям.
Flight - это микрофреймворк. Это означает, что он небольшой и легкий. Он не предоставляет так много функциональности, как более крупные фреймворки, такие как Laravel или Symfony. Однако он предоставляет много функциональности, которая вам нужна для создания веб-приложений. Его также легко изучить и использовать. Это делает его хорошим выбором для быстрого и простого создания веб-приложений. Если вы новичок в фреймворках, Flight - отличный фреймворк для начала. Он поможет вам узнать о преимуществах использования фреймворков, не перегружая вас слишком сложностью. После того как у вас будет опыт работы с Flight, будет легче перейти на более сложные фреймворки, такие как Laravel или Symfony, однако Flight все равно может создать успешное надежное приложение.
Что такое маршрутизация?
Маршрутизация является основой фреймворка Flight, но что это такое? Маршрутизация - это процесс принятия URL и сопоставления его с определенной функцией в вашем коде.
Таким образом вы можете заставить ваш веб-сайт делать разные вещи в зависимости от запрошенного URL. Например, вы могли бы показать профиль пользователя, когда
они посещают /user/1234
, но показать список всех пользователей, когда они посещают /users
. Все это делается через маршрутизацию.
Это может работать так:
- Пользователь заходит в ваш браузер и вводит
http://example.com/user/1234
. - Сервер получает запрос, смотрит на URL и передает его в ваш код приложения Flight.
- Предположим, в вашем коде Flight у вас есть что-то вроде
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
. Ваш код приложения Flight смотрит на URL и видит, что он соответствует определенному маршруту, затем выполняет код, который вы определили для этого маршрута. - Маршрутизатор Flight затем запустится и вызовет метод
viewUserProfile($id)
в классеUserController
, передавая1234
в аргумент$id
метода. - Код в вашем методе
viewUserProfile()
затем будет выполняться и делать то, что вы ему сказали делать. Вы можете закончить выводом некоторого HTML для страницы профиля пользователя, или если это RESTful API, вы можете вывести JSON-ответ с информацией о пользователе. - Flight завернет это в красивый бантик, сгенерирует заголовки ответа и отправит обратно в браузер пользователя.
- Пользователь будет полон радости и даст себе теплый объятие!
И зачем это важно?
Иметь правильный централизованный маршрутизатор может действительно сильно облегчить вашу жизнь! Просто сначала это может быть трудно увидеть. Вот несколько причин:
- Централизованная маршрутизация: Вы можете хранить все свои маршруты в одном месте. Это позволяет легче видеть, какие маршруты у вас есть и что они делают. Также это упрощает их изменение, если вам это понадобится.
- Параметры маршрута: Вы можете использовать параметры маршрута, чтобы передавать данные в ваши методы маршрутов. Это отличный способ держать ваш код чистым и организованным.
- Группировка маршрутов: Вы можете группировать маршруты вместе. Это отлично для организации вашего кода и для применения middleware к группе маршрутов.
- Псевдонимы маршрутов: Вы можете присвоить псевдоним маршруту, чтобы URL мог динамически генерироваться позже в вашем коде (например, как шаблон). Например, вместо хардкода
/user/1234
в вашем коде, вы можете обратиться к псевдонимуuser_view
и передатьid
как параметр. Это замечательно в случае, если вы решите изменить его на/admin/user/1234
позднее. Вам не придется изменять все свои жестко закодированные URL, просто URL, привязанный к маршруту. - Промежуточное ПО маршрута: Вы можете добавлять промежуточное программное обеспечение к вашим маршрутам. Промежуточное программное обеспечение невероятно мощно в добавлении определенных поведений к вашему приложению, таких как аутентификация того, что определенный пользователь может получить доступ к маршруту или группе маршрутов.
Я уверен, что вы знакомы со способом создания веб-сайта, описанным скрипт за скриптом. Может быть у вас есть файл под названием index.php
, который содержит множество условных операторов if
для проверки URL, а затем запуска определенной функции на основе URL. Это форма маршрутизации, но она не очень организована и может
быстро выйти из-под контроля. Система маршрутизации Flight - это гораздо более организованный и мощный способ управления маршрутами.
Это?
// /user/view_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
viewUserProfile($id);
}
// /user/edit_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
editUserProfile($id);
}
// и так далее...
или это?
// index.php
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
Flight::route('/user/@id/edit', [ 'UserController', 'editUserProfile' ]);
// Возможно, в вашем app/controllers/UserController.php
class UserController {
public function viewUserProfile($id) {
// сделать что-то
}
public function editUserProfile($id) {
// сделать что-то
}
}
Надеюсь, теперь вы начинаете видеть преимущества использования централизованной системы маршрутизации. Это намного проще управлять и понимать в долгосрочной перспективе!
Запросы и ответы
Flight предоставляет простой и легкий способ обработки запросов и ответов. Это ядро функционала веб-фреймворка. Он принимает запрос от браузера пользователя, обрабатывает его, а затем отправляет ответ. Именно так вы можете создавать веб-приложения, которые показывают профиль пользователя, позволяют пользователю войти в систему или опубликовать новый блог.
Запросы
Запрос - это то, что браузер пользователя отправляет на ваш сервер при посещении вашего веб-сайта. Этот запрос содержит информацию о том, что хочет сделать пользователь. Например, он может содержать информацию о том, какой URL пользователь хочет посетить, какие данные пользователь хочет отправить на ваш сервер, или какие данные пользователь хочет получить от вашего сервера. Важно знать, что запрос только для чтения. Вы не можете изменить запрос, но можете читать его.
Flight предоставляет простой способ получить доступ к информации о запросе. Вы можете получить доступ к информации о запросе, используя метод Flight::request()
Метод возвращает объект Request
, который содержит информацию о запросе. Вы можете использовать этот объект для доступа к информации о запросе, такой как URL, метод или данные,
которые пользователь отправил на ваш сервер.
Ответы
Ответ - это то, что ваш сервер отправляет обратно на браузер пользователя при посещении вашего веб-сайта. Этот ответ содержит информацию о том, что ваш сервер хочет сделать. Например, он может содержать информацию о том, какие данные ваш сервер хочет отправить пользователю, какие данные ваш сервер хочет получить от пользователя, или какие данные ваш сервер хочет сохранить на компьютере пользователя.
Flight предоставляет простой способ отправить ответ браузеру пользователя. Вы можете отправить ответ, используя метод Flight::response()
Метод принимает объект Response
в качестве аргумента и отправляет ответ браузеру пользователя. Вы можете использовать этот объект, чтобы отправить ответ браузеру пользователя,
такой как HTML, JSON или файл. Flight помогает автоматически генерировать некоторые части ответа, чтобы сделать вещи легкими, но в конечном итоге у вас есть
контроль над тем, что вы отправляете обратно пользователю.
Learn/responses
Responses
Обзор
Flight помогает генерировать часть заголовков ответа для вас, но вы контролируете большую часть того, что отправляете обратно пользователю. Большинство времени вы будете обращаться напрямую к объекту response()
, но Flight имеет некоторые вспомогательные методы для установки некоторых заголовков ответа за вас.
Понимание
После того как пользователь отправит свой запрос в ваше приложение, вам нужно сгенерировать правильный ответ для него. Они отправили вам информацию, такую как предпочитаемый язык, могут ли они обрабатывать определенные типы сжатия, их пользовательский агент и т.д., и после обработки всего этого пришло время отправить им правильный ответ. Это может быть установка заголовков, вывод тела HTML или JSON для них или перенаправление на страницу.
Базовое использование
Отправка тела ответа
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();
});
JSON
Flight предоставляет поддержку для отправки JSON и JSONP ответов. Чтобы отправить JSON-ответ, вы передаете некоторые данные для кодирования в JSON:
Flight::route('/@companyId/users', function(int $companyId) {
// каким-то образом извлеките своих пользователей из базы данных, например
$users = Flight::db()->fetchAll("SELECT id, first_name, last_name FROM users WHERE company_id = ?", [ $companyId ]);
Flight::json($users);
});
// [{"id":1,"first_name":"Bob","last_name":"Jones"}, /* больше пользователей */ ]
Примечание: По умолчанию Flight отправит заголовок
Content-Type: application/json
с ответом. Он также будет использовать флагиJSON_THROW_ON_ERROR
иJSON_UNESCAPED_SLASHES
при кодировании JSON.
JSON с кодом статуса
Вы также можете передать код статуса как второй аргумент:
Flight::json(['id' => 123], 201);
JSON с красивой печатью
Вы также можете передать аргумент в последнее положение для включения красивой печати:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Изменение порядка аргументов JSON
Flight::json()
— это очень устаревший метод, но цель Flight — поддерживать обратную совместимость для проектов. На самом деле это очень просто, если вы хотите переделать порядок аргументов для использования более простого синтаксиса, вы можете просто переназначить метод JSON как любой другой метод Flight:
Flight::map('json', function($data, $code = 200, $options = 0) {
// теперь вам не нужно `true, 'utf-8'` при использовании метода json()!
Flight::_json($data, $code, true, 'utf-8', $options);
}
// И теперь это можно использовать так
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON и остановка выполнения
v3.10.0
Если вы хотите отправить JSON-ответ и остановить выполнение, вы можете использовать метод jsonHalt()
. Это полезно для случаев, когда вы проверяете, возможно, какой-то тип авторизации, и если пользователь не авторизован, вы можете сразу отправить JSON-ответ, очистить существующее содержимое тела и остановить выполнение.
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Проверьте, авторизован ли пользователь
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
// нет выхода; здесь не нужно.
}
// Продолжите с остальной частью маршрута
});
До v3.10.0 вы бы сделали что-то вроде этого:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Проверьте, авторизован ли пользователь
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// Продолжите с остальной частью маршрута
});
Очистка тела ответа
Если вы хотите очистить тело ответа, вы можете использовать метод clearBody
:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
Случай использования выше, вероятно, не распространен, однако он может быть более распространен, если это используется в middleware.
Выполнение обратного вызова на теле ответа
Вы можете выполнить обратный вызов на теле ответа, используя метод addResponseBodyCallback
:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// Это сожмет gzip все ответы для любого маршрута
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
Вы можете добавить несколько обратных вызовов, и они будут выполняться в порядке добавления. Поскольку это может принимать любой вызываемый, он может принимать массив класса [ $class, 'method' ]
, замыкание $strReplace = function($body) { str_replace('hi', 'there', $body); };
или имя функции 'minify'
, если у вас есть функция для минимизации вашего html-кода, например.
Примечание: Обратные вызовы маршрутов не будут работать, если вы используете опцию конфигурации flight.v2.output_buffering
.
Обратный вызов конкретного маршрута
Если вы хотите, чтобы это применялось только к конкретному маршруту, вы можете добавить обратный вызов в сам маршрут:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// Это сожмет gzip только ответ для этого маршрута
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
Опция Middleware
Вы также можете использовать middleware для применения обратного вызова ко всем маршрутам через middleware:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// Примените обратный вызов здесь на объекте response().
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// минимизируйте тело каким-то образом
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
Коды статуса
Вы можете установить код статуса ответа, используя метод 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');
// или
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
Перенаправление
Вы можете перенаправить текущий запрос, используя метод redirect()
и передав новый URL:
Flight::route('/login', function() {
$username = Flight::request()->data->username;
$password = Flight::request()->data->password;
$passwordConfirm = Flight::request()->data->password_confirm;
if($password !== $passwordConfirm) {
Flight::redirect('/new/location');
return; // это необходимо, чтобы функциональность ниже не выполнялась
}
// добавьте нового пользователя...
Flight::db()->runQuery("INSERT INTO users ....");
Flight::redirect('/admin/dashboard');
});
Примечание: По умолчанию Flight отправляет HTTP 303 ("See Other") код статуса. Вы можете опционально установить пользовательский код:
Flight::redirect('/new/location', 301); // постоянный
Остановка выполнения маршрута
Вы можете остановить фреймворк и немедленно выйти в любой момент, вызвав метод halt
:
Flight::halt();
Вы также можете указать опциональный код статуса HTTP
и сообщение:
Flight::halt(200, 'Be right back...');
Вызов halt
отбросит любое содержимое ответа до этого момента и остановит все выполнение. Если вы хотите остановить фреймворк и вывести текущий ответ, используйте метод stop
:
Flight::stop($httpStatusCode = null);
Примечание:
Flight::stop()
имеет некоторые странные поведения, такие как вывод ответа, но продолжение выполнения вашего скрипта, что может быть не тем, что вы хотите. Вы можете использоватьexit
илиreturn
после вызоваFlight::stop()
, чтобы предотвратить дальнейшее выполнение, но в целом рекомендуется использоватьFlight::halt()
.
Это сохранит ключ и значение заголовка в объекте ответа. В конце жизненного цикла запроса он построит заголовки и отправит ответ.
Расширенное использование
Отправка заголовка немедленно
Могут быть случаи, когда вам нужно сделать что-то пользовательское с заголовком, и вам нужно отправить заголовок на той самой строке кода, с которой вы работаете. Если вы устанавливаете потоковый маршрут, это то, что вам понадобится. Это достижимо через response()->setRealHeader()
.
Flight::route('/', function() {
Flight::response()->setRealHeader('Content-Type: text/plain');
echo 'Streaming response...';
sleep(5);
echo 'Done!';
})->stream();
JSONP
Для JSONP-запросов вы можете опционально передать имя параметра запроса, которое вы используете для определения вашей функции обратного вызова:
Flight::jsonp(['id' => 123], 'q');
Таким образом, при выполнении GET-запроса с использованием ?q=my_func
вы должны получить вывод:
my_func({"id":123});
Если вы не передадите имя параметра запроса, оно по умолчанию будет jsonp
.
Примечание: Если вы все еще используете JSONP-запросы в 2025 году и позже, присоединяйтесь к чату и расскажите нам почему! Мы любим слышать хорошие истории сражений/ужасов!
Очистка данных ответа
Вы можете очистить тело ответа и заголовки, используя метод clear()
. Это очистит любые заголовки, назначенные ответу, очистит тело ответа и установит код статуса в 200
.
Flight::response()->clear();
Очистка только тела ответа
Если вы хотите очистить только тело ответа, вы можете использовать метод clearBody()
:
// Это все еще сохранит любые заголовки, установленные на объекте response().
// Это все еще сохранит любые заголовки, установленные на объекте response().
Flight::response()->clearBody();
Кэширование HTTP
Flight предоставляет встроенную поддержку кэширования на уровне HTTP. Если условие кэширования выполнено, Flight вернет HTTP-ответ 304 Not Modified
. В следующий раз, когда клиент запросит тот же ресурс, ему будет предложено использовать локально кэшированную версию.
Кэширование на уровне маршрута
Если вы хотите кэшировать весь свой ответ, вы можете использовать метод cache()
и передать время кэширования.
// Это закэширует ответ на 5 минут
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// В качестве альтернативы вы можете использовать строку, которую вы бы передали
// методу strtotime()
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Last-Modified
Вы можете использовать метод lastModified
и передать UNIX-временную метку для установки даты и времени последнего изменения страницы. Клиент продолжит использовать свой кэш, пока значение последнего изменения не изменится.
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
Кэширование ETag
похоже на Last-Modified
, за исключением того, что вы можете указать любой идентификатор, который вы хотите для ресурса:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
Имейте в виду, что вызов либо lastModified
, либо etag
установит и проверит значение кэша. Если значение кэша одинаково между запросами, Flight немедленно отправит ответ HTTP 304
и остановит обработку.
Скачивание файла
v3.12.0
Есть вспомогательный метод для потоковой передачи файла конечному пользователю. Вы можете использовать метод download
и передать путь.
Flight::route('/download', function () {
Flight::download('/path/to/file.txt');
// Начиная с v3.17.1 вы можете указать пользовательское имя файла для скачивания
Flight::download('/path/to/file.txt', 'custom_name.txt');
});
См. также
- Маршрутизация — Как сопоставлять маршруты с контроллерами и рендерить представления.
- Запросы — Понимание того, как обрабатывать входящие запросы.
- Middleware — Использование middleware с маршрутами для аутентификации, логирования и т.д.
- Почему фреймворк? — Понимание преимуществ использования фреймворка вроде Flight.
- Расширение — Как расширять Flight своей собственной функциональностью.
Устранение неисправностей
- Если у вас проблемы с перенаправлениями, которые не работают, убедитесь, что вы добавили
return;
в метод. stop()
иhalt()
— это не одно и то же.halt()
остановит выполнение немедленно, в то время какstop()
позволит выполнению продолжаться.
Журнал изменений
- v3.17.1 — Добавлен
$fileName
в методdownloadFile()
. - v3.12.0 — Добавлен вспомогательный метод downloadFile.
- v3.10.0 — Добавлен
jsonHalt
. - v1.0 — Первое выпущение.
Learn/events
Менеджер событий
начиная с v3.15.0
Обзор
События позволяют регистрировать и вызывать пользовательское поведение в вашем приложении. С добавлением Flight::onEvent()
и Flight::triggerEvent()
вы теперь можете подключаться к ключевым моментам жизненного цикла вашего приложения или определять свои собственные события (например, уведомления и emails), чтобы сделать ваш код более модульным и расширяемым. Эти методы являются частью mappable methods в Flight, что означает, что вы можете переопределить их поведение в соответствии с вашими потребностями.
Понимание
События позволяют разделять разные части вашего приложения, чтобы они не зависели друг от друга слишком сильно. Это разделение — часто называемое decoupling — делает ваш код проще для обновления, расширения или отладки. Вместо того чтобы писать всё в одном большом блоке, вы можете разделить логику на меньшие, независимые части, которые реагируют на конкретные действия (события).
Представьте, что вы строите приложение для блога:
- Когда пользователь публикует комментарий, вы можете захотеть:
- Сохранить комментарий в базу данных.
- Отправить email владельцу блога.
- Записать действие для безопасности.
Без событий вы бы запихнули всё это в одну функцию. С событиями вы можете разделить: одна часть сохраняет комментарий, другая вызывает событие вроде 'comment.posted'
, а отдельные слушатели обрабатывают email и логирование. Это делает ваш код чище и позволяет добавлять или удалять функции (например, уведомления) без касания основной логики.
Распространенные случаи использования
В основном события хороши для вещей, которые являются опциональными, но не абсолютной основной частью вашей системы. Например, следующие вещи хорошо иметь, но если они по какой-то причине не сработают, ваше приложение всё равно должно работать:
- Логирование: Записывать действия вроде входов или ошибок без засорения основного кода.
- Уведомления: Отправлять emails или оповещения, когда что-то происходит.
- Обновления кэша: Обновлять кэш или уведомлять другие системы об изменениях.
Однако, предположим, у вас есть функция "забыл пароль". Это должно быть частью вашей основной функциональности, а не событием, потому что если этот email не уйдёт, пользователь не сможет сбросить пароль и использовать ваше приложение.
Базовое использование
Система событий Flight построена вокруг двух основных методов: Flight::onEvent()
для регистрации слушателей событий и Flight::triggerEvent()
для вызова событий. Вот как вы можете их использовать:
Регистрация слушателей событий
Чтобы слушать событие, используйте Flight::onEvent()
. Этот метод позволяет определить, что должно происходить, когда событие происходит.
Flight::onEvent(string $event, callable $callback): void
$event
: Имя для вашего события (например,'user.login'
).$callback
: Функция, которая выполняется, когда событие вызывается.
Вы "подписываетесь" на событие, сообщая Flight, что делать, когда оно происходит. Callback может принимать аргументы, переданные от вызова события.
Система событий Flight синхронная, что означает, что каждый слушатель события выполняется последовательно, один за другим. Когда вы вызываете событие, все зарегистрированные слушатели для этого события выполнятся до завершения, прежде чем ваш код продолжится. Это важно понимать, поскольку это отличается от асинхронных систем событий, где слушатели могут выполняться параллельно или позже.
Простой пример
Flight::onEvent('user.login', function ($username) {
echo "Welcome back, $username!";
// you can send an email if the login is from a new location
});
Здесь, когда событие 'user.login'
вызывается, оно приветствует пользователя по имени и может также включать логику для отправки email, если нужно.
Примечание: Callback может быть функцией, анонимной функцией или методом из класса.
Вызов событий
Чтобы событие произошло, используйте Flight::triggerEvent()
. Это говорит Flight выполнить все слушатели, зарегистрированные для этого события, передавая любые данные, которые вы предоставите.
Flight::triggerEvent(string $event, ...$args): void
$event
: Имя события, которое вы вызываете (должно соответствовать зарегистрированному событию)....$args
: Опциональные аргументы для отправки слушателям (может быть любое количество аргументов).
Простой пример
$username = 'alice';
Flight::triggerEvent('user.login', $username);
Это вызывает событие 'user.login'
и отправляет 'alice'
слушателю, который мы определили ранее, что выведет: Welcome back, alice!
.
- Если слушатели не зарегистрированы, ничего не происходит — ваше приложение не сломается.
- Используйте spread operator (
...
) для гибкой передачи нескольких аргументов.
Остановка событий
Если слушатель возвращает false
, дополнительные слушатели для этого события не будут выполнены. Это позволяет остановить цепочку событий на основе конкретных условий. Помните, порядок слушателей имеет значение, поскольку первый, вернувший false
, остановит остальные.
Пример:
Flight::onEvent('user.login', function ($username) {
if (isBanned($username)) {
logoutUser($username);
return false; // Stops subsequent listeners
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // this is never sent
});
Переопределение методов событий
Flight::onEvent()
и Flight::triggerEvent()
доступны для расширения, что означает, что вы можете переопределить, как они работают. Это отлично для продвинутых пользователей, которые хотят кастомизировать систему событий, например, добавляя логирование или изменяя, как события диспетчеризуются.
Пример: Кастомизация onEvent
Flight::map('onEvent', function (string $event, callable $callback) {
// Log every event registration
error_log("New event listener added for: $event");
// Call the default behavior (assuming an internal event system)
Flight::_onEvent($event, $callback);
});
Теперь каждый раз, когда вы регистрируете событие, оно логируется перед продолжением.
Почему переопределять?
- Добавить отладку или мониторинг.
- Ограничить события в определённых окружениях (например, отключить в тестировании).
- Интегрировать с другой библиотекой событий.
Куда размещать события
Если вы новичок в концепциях событий в вашем проекте, вы можете задаться вопросом: куда мне регистрировать все эти события в приложении? Простота Flight означает, что нет строгого правила — вы можете размещать их там, где это имеет смысл для вашего проекта. Однако, поддерживая их организованными, вы помогаете поддерживать код по мере роста приложения. Вот несколько практических вариантов и лучших практик, адаптированных к лёгковесной природе Flight:
Вариант 1: В основном index.php
Для маленьких приложений или быстрых прототипов вы можете регистрировать события прямо в файле index.php
рядом с маршрутами. Это держит всё в одном месте, что нормально, когда простота — ваш приоритет.
require 'vendor/autoload.php';
// Register events
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
// Define routes
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Плюсы: Просто, без лишних файлов, отлично для маленьких проектов.
- Минусы: Может стать беспорядочным по мере роста приложения с большим количеством событий и маршрутов.
Вариант 2: Отдельный файл events.php
Для чуть большего приложения рассмотрите перемещение регистраций событий в отдельный файл вроде app/config/events.php
. Включите этот файл в index.php
перед маршрутами. Это имитирует, как часто организуются маршруты в app/config/routes.php
в проектах Flight.
// app/config/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Плюсы: Держит
index.php
сосредоточенным на маршрутизации, организует события логично, легко найти и редактировать. - Минусы: Добавляет немного структуры, что может показаться излишним для очень маленьких приложений.
Вариант 3: Рядом с местом вызова
Другой подход — регистрировать события близко к месту их вызова, например, внутри контроллера или определения маршрута. Это хорошо работает, если событие специфично для одной части приложения.
Flight::route('/signup', function () {
// Register event here
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $email!";
});
$email = 'jane@example.com';
Flight::triggerEvent('user.registered', $email);
echo "Signed up!";
});
- Плюсы: Держит связанный код вместе, хорошо для изолированных функций.
- Минусы: Разбрасывает регистрации событий, делая сложнее увидеть все события сразу; риск дублирования регистраций, если не осторожны.
Лучшая практика для Flight
- Начните просто: Для крошечных приложений размещайте события в
index.php
. Это быстро и соответствует минимализму Flight. - Растите умно: По мере расширения приложения (например, больше 5-10 событий) используйте файл
app/config/events.php
. Это естественный шаг вверх, как организация маршрутов, и держит код аккуратным без добавления сложных фреймворков. - Избегайте переусложнения: Не создавайте полноценный класс "event manager" или директорию, если приложение не огромно — Flight процветает на простоте, так что держите лёгковесно.
Совет: Группируйте по назначению
В events.php
группируйте связанные события (например, все события, связанные с пользователем, вместе) с комментариями для ясности:
// app/config/events.php
// User Events
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
});
// Page Events
Flight::onEvent('page.updated', function ($pageId) {
Flight::cache()->delete("page_$pageId");
});
Эта структура хорошо масштабируется и остаётся дружелюбной для новичков.
Реальные примеры
Давайте пройдёмся по некоторым реальным сценариям, чтобы показать, как работают события и почему они полезны.
Пример 1: Логирование входа пользователя
// Step 1: Register a listener
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
});
// Step 2: Trigger it in your app
Flight::route('/login', function () {
$username = 'bob'; // Pretend this comes from a form
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
});
Почему полезно: Код входа не нуждается в знании о логировании — он просто вызывает событие. Вы можете позже добавить больше слушателей (например, отправить приветственный email) без изменения маршрута.
Пример 2: Уведомление о новых пользователях
// Listener for new registrations
Flight::onEvent('user.registered', function ($email, $name) {
// Simulate sending an email
echo "Email sent to $email: Welcome, $name!";
});
// Trigger it when someone signs up
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
});
Почему полезно: Логика регистрации сосредоточена на создании пользователя, в то время как событие обрабатывает уведомления. Вы можете добавить больше слушателей (например, логировать регистрацию) позже.
Пример 3: Очистка кэша
// Listener to clear a cache
Flight::onEvent('page.updated', function ($pageId) {
// if using the flightphp/cache plugin
Flight::cache()->delete("page_$pageId");
echo "Cache cleared for page $pageId.";
});
// Trigger when a page is edited
Flight::route('/edit-page/(@id)', function ($pageId) {
// Pretend we updated the page
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
});
Почему полезно: Код редактирования не заботится о кэшировании — он просто сигнализирует об обновлении. Другие части приложения могут реагировать по необходимости.
Лучшие практики
- Называйте события ясно: Используйте конкретные имена вроде
'user.login'
или'page.updated'
, чтобы было очевидно, что они делают. - Держите слушателей простыми: Не размещайте медленные или сложные задачи в слушателях — держите приложение быстрым.
- Тестируйте события: Вызывайте их вручную, чтобы убедиться, что слушатели работают как ожидается.
- Используйте события разумно: Они отличны для decoupling, но слишком много может сделать код сложным для отслеживания — используйте их, когда это имеет смысл.
Система событий в Flight PHP с Flight::onEvent()
и Flight::triggerEvent()
даёт вам простой, но мощный способ строить гибкие приложения. Позволяя разным частям приложения общаться друг с другом через события, вы можете держать код организованным, переиспользуемым и простым для расширения. Будь то логирование действий, отправка уведомлений или управление обновлениями, события помогают делать это без запутывания логики. Плюс, с возможностью переопределения этих методов, у вас есть свобода адаптировать систему под ваши нужды. Начните с одного события и посмотрите, как оно трансформирует структуру вашего приложения!
Встроенные события
Flight PHP поставляется с несколькими встроенными событиями, которые вы можете использовать для подключения к жизненному циклу фреймворка. Эти события вызываются в конкретных точках цикла запрос/ответ, позволяя выполнять пользовательскую логику, когда происходят определённые действия.
Список встроенных событий
- flight.request.received:
function(Request $request)
Вызывается, когда запрос получен, разобраны и обработан. - flight.error:
function(Throwable $exception)
Вызывается, когда происходит ошибка во время жизненного цикла запроса. - flight.redirect:
function(string $url, int $status_code)
Вызывается, когда инициируется перенаправление. - flight.cache.checked:
function(string $cache_key, bool $hit, float $executionTime)
Вызывается, когда кэш проверяется для конкретного ключа и попадание или промах в кэш. - flight.middleware.before:
function(Route $route)
Вызывается после выполнения middleware before. - flight.middleware.after:
function(Route $route)
Вызывается после выполнения middleware after. - flight.middleware.executed:
function(Route $route, $middleware, string $method, float $executionTime)
Вызывается после выполнения любого middleware - flight.route.matched:
function(Route $route)
Вызывается, когда маршрут совпадает, но ещё не запущен. - flight.route.executed:
function(Route $route, float $executionTime)
Вызывается после выполнения и обработки маршрута.$executionTime
— время, потраченное на выполнение маршрута (вызов контроллера и т.д.). - flight.view.rendered:
function(string $template_file_path, float $executionTime)
Вызывается после рендеринга вида.$executionTime
— время, потраченное на рендеринг шаблона. Примечание: Если вы переопределите методrender
, вам нужно будет повторно вызвать это событие. - flight.response.sent:
function(Response $response, float $executionTime)
Вызывается после отправки ответа клиенту.$executionTime
— время, потраченное на построение ответа.
См. также
- Extending Flight - Как расширять и кастомизировать основную функциональность Flight.
- Cache - Пример использования событий для очистки кэша при обновлении страницы.
Устранение неисправностей
- Если вы не видите, что ваши слушатели событий вызываются, убедитесь, что регистрируете их перед вызовом событий. Порядок регистрации имеет значение.
Журнал изменений
- v3.15.0 - Добавлены события в Flight.
Learn/templates
HTML-шаблоны и представления
Обзор
Flight предоставляет базовую функциональность шаблонизации HTML по умолчанию. Шаблонизация — это очень эффективный способ отделить логику приложения от слоя представления.
Понимание
При создании приложения вам, вероятно, потребуется HTML, который вы захотите передать конечному пользователю. PHP сам по себе является языком шаблонизации, но очень легко включить в файл HTML бизнес-логику, такую как вызовы базы данных, API и т.д., что делает тестирование и разделение очень сложным процессом. Передавая данные в шаблон и позволяя шаблону рендериться самостоятельно, становится гораздо проще разделять и проводить модульное тестирование вашего кода. Вы поблагодарите нас, если будете использовать шаблоны!
Базовое использование
Flight позволяет заменить стандартный движок представлений, просто зарегистрировав свой собственный класс представлений. Прокрутите вниз, чтобы увидеть примеры использования Smarty, Latte, Blade и других!
Latte
рекомендуется
Вот как вы можете использовать движок шаблонов Latte для ваших представлений.
Установка
composer require latte/latte
Базовая конфигурация
Основная идея в том, чтобы переопределить метод render
для использования Latte вместо стандартного рендерера PHP.
// переопределите метод render для использования latte вместо стандартного рендерера PHP
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Где latte специально хранит свой кэш
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Использование Latte в Flight
Теперь, когда вы можете рендерить с помощью Latte, вы можете сделать что-то вроде этого:
<!-- app/views/home.latte -->
<html>
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {$name}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.latte', [
'title' => 'Home Page',
'name' => $name
]);
});
Когда вы посетите /Bob
в вашем браузере, вывод будет следующим:
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
Дополнительное чтение
Более сложный пример использования Latte с макетами показан в разделе awesome plugins этой документации.
Вы можете узнать больше о полных возможностях Latte, включая перевод и языковые возможности, прочитав официальную документацию.
Встроенный движок представлений
устарело
Примечание: Хотя это всё ещё функциональность по умолчанию и технически работает.
Чтобы отобразить шаблон представления, вызовите метод render
с именем файла шаблона и необязательными данными шаблона:
Flight::render('hello.php', ['name' => 'Bob']);
Данные шаблона, которые вы передаёте, автоматически внедряются в шаблон и могут быть использованы как локальная переменная. Файлы шаблонов — это просто файлы PHP. Если содержимое файла шаблона hello.php
выглядит так:
Hello, <?= $name ?>!
Вывод будет:
Hello, Bob!
Вы также можете вручную установить переменные представлений с помощью метода set:
Flight::view()->set('name', 'Bob');
Переменная name
теперь доступна во всех ваших представлениях. Таким образом, вы можете просто сделать:
Flight::render('hello');
Обратите внимание, что при указании имени шаблона в методе render вы можете опустить расширение .php
.
По умолчанию Flight будет искать директорию views
для файлов шаблонов. Вы можете установить альтернативный путь для ваших шаблонов, задав следующую конфигурацию:
Flight::set('flight.views.path', '/path/to/views');
Макеты
Обычно для веб-сайтов используется один файл шаблона макета с изменяемым содержимым. Чтобы отрендерить содержимое для использования в макете, вы можете передать необязательный параметр в метод render
.
Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');
Ваше представление затем сохранит переменные с именами headerContent
и bodyContent
. Затем вы можете отрендерить свой макет следующим образом:
Flight::render('layout', ['title' => 'Home Page']);
Если файлы шаблонов выглядят так:
header.php
:
<h1><?= $heading ?></h1>
body.php
:
<div><?= $body ?></div>
layout.php
:
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<?= $headerContent ?>
<?= $bodyContent ?>
</body>
</html>
Вывод будет:
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello</h1>
<div>World</div>
</body>
</html>
Smarty
Вот как вы можете использовать движок шаблонов Smarty для ваших представлений:
// Загрузите библиотеку Smarty
require './Smarty/libs/Smarty.class.php';
// Зарегистрируйте Smarty как класс представлений
// Также передайте функцию обратного вызова для конфигурации Smarty при загрузке
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
$smarty->setTemplateDir('./templates/');
$smarty->setCompileDir('./templates_c/');
$smarty->setConfigDir('./config/');
$smarty->setCacheDir('./cache/');
});
// Назначьте данные шаблона
Flight::view()->assign('name', 'Bob');
// Отобразите шаблон
Flight::view()->display('hello.tpl');
Для полноты вы также должны переопределить стандартный метод render Flight:
Flight::map('render', function(string $template, array $data): void {
Flight::view()->assign($data);
Flight::view()->display($template);
});
Blade
Вот как вы можете использовать движок шаблонов Blade для ваших представлений:
Сначала вам нужно установить библиотеку BladeOne через Composer:
composer require eftec/bladeone
Затем вы можете настроить BladeOne как класс представлений в Flight:
<?php
// Загрузите библиотеку BladeOne
use eftec\bladeone\BladeOne;
// Зарегистрируйте BladeOne как класс представлений
// Также передайте функцию обратного вызова для конфигурации BladeOne при загрузке
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
$views = __DIR__ . '/../views';
$cache = __DIR__ . '/../cache';
$blade->setPath($views);
$blade->setCompiledPath($cache);
});
// Назначьте данные шаблона
Flight::view()->share('name', 'Bob');
// Отобразите шаблон
echo Flight::view()->run('hello', []);
Для полноты вы также должны переопределить стандартный метод render Flight:
<?php
Flight::map('render', function(string $template, array $data): void {
echo Flight::view()->run($template, $data);
});
В этом примере файл шаблона hello.blade.php может выглядеть так:
<?php
Hello, {{ $name }}!
Вывод будет:
Hello, Bob!
См. также
- Расширение — Как переопределить метод
render
для использования другого движка шаблонов. - Маршрутизация — Как сопоставлять маршруты с контроллерами и рендерить представления.
- Ответы — Как настраивать HTTP-ответы.
- Зачем фреймворк? — Как шаблоны вписываются в общую картину.
Устранение неисправностей
- Если у вас есть перенаправление в вашем middleware, но приложение не перенаправляется, убедитесь, что вы добавили инструкцию
exit;
в ваш middleware.
Журнал изменений
- v2.0 — Первоначальный релиз.
Learn/collections
Коллекции
Обзор
Класс Collection
в Flight — это удобная утилита для управления наборами данных. Она позволяет обращаться к данным и манипулировать ими с использованием как нотации массива, так и нотации объекта, делая ваш код чище и более гибким.
Понимание
Collection
— это по сути обертка вокруг массива, но с дополнительными возможностями. Вы можете использовать его как массив, перебирать, подсчитывать элементы и даже обращаться к элементам как к свойствам объекта. Это особенно полезно, когда вы хотите передавать структурированные данные в вашем приложении или сделать код более читаемым.
Коллекции реализуют несколько интерфейсов PHP:
ArrayAccess
(чтобы вы могли использовать синтаксис массива)Iterator
(чтобы вы могли перебирать с помощьюforeach
)Countable
(чтобы вы могли использоватьcount()
)JsonSerializable
(чтобы вы могли легко преобразовать в JSON)
Основное использование
Создание коллекции
Вы можете создать коллекцию, просто передав массив в её конструктор:
use flight\util\Collection;
$data = [
'name' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$collection = new Collection($data);
Доступ к элементам
Вы можете обращаться к элементам с использованием нотации массива или объекта:
// Нотация массива
echo $collection['name']; // Вывод: FlightPHP
// Нотация объекта
echo $collection->version; // Вывод: 3
Если вы попытаетесь обратиться к ключу, которого не существует, вы получите null
вместо ошибки.
Установка элементов
Вы можете устанавливать элементы с использованием любой нотации:
// Нотация массива
$collection['author'] = 'Mike Cao';
// Нотация объекта
$collection->license = 'MIT';
Проверка и удаление элементов
Проверьте, существует ли элемент:
if (isset($collection['name'])) {
// Сделайте что-то
}
if (isset($collection->version)) {
// Сделайте что-то
}
Удалите элемент:
unset($collection['author']);
unset($collection->license);
Перебор коллекции
Коллекции итерируемы, поэтому вы можете использовать их в цикле foreach
:
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
Подсчет элементов
Вы можете подсчитать количество элементов в коллекции:
echo count($collection); // Вывод: 4
Получение всех ключей или данных
Получите все ключи:
$keys = $collection->keys(); // ['name', 'version', 'features', 'license']
Получите все данные как массив:
$data = $collection->getData();
Очистка коллекции
Удалите все элементы:
$collection->clear();
Сериализация JSON
Коллекции легко преобразуются в JSON:
echo json_encode($collection);
// Вывод: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}
Расширенное использование
Вы можете полностью заменить внутренний массив данных, если нужно:
$collection->setData(['foo' => 'bar']);
Коллекции особенно полезны, когда вы хотите передавать структурированные данные между компонентами или предоставить более объектно-ориентированный интерфейс для данных массива.
См. также
- Requests - Узнайте, как обрабатывать HTTP-запросы и как коллекции могут использоваться для управления данными запроса.
- PDO Wrapper - Узнайте, как использовать обертку PDO в Flight и как коллекции могут использоваться для управления результатами базы данных.
Устранение неисправностей
- Если вы попытаетесь обратиться к ключу, которого не существует, вы получите
null
вместо ошибки. - Помните, что коллекции не рекурсивны: вложенные массивы не преобразуются автоматически в коллекции.
- Если вам нужно сбросить коллекцию, используйте
$collection->clear()
или$collection->setData([])
.
Журнал изменений
- v3.0 - Улучшены подсказки типов и поддержка PHP 8+.
- v1.0 - Первоначальный выпуск класса Collection.
Learn/flight_vs_fat_free
Flight против Fat-Free
Что такое Fat-Free?
Fat-Free (нежное прозвище F3) — это мощный, но простой в использовании PHP-микрофреймворк, предназначенный для помощи в создании динамичных и надежных веб-приложений — быстро!
Flight сравнивается с Fat-Free во многих отношениях и, вероятно, является ближайшим родственником по функциям и простоте. Fat-Free имеет много функций, которых нет у Flight, но также имеет много функций, которые есть у Flight. Fat-Free начинает проявлять свой возраст и не так популярен, как раньше.
Обновления становятся менее частыми, а сообщество не такое активное, как прежде. Код достаточно простой, но иногда отсутствие дисциплины в синтаксисе может затруднить чтение и понимание. Он работает с PHP 8.3, но сам код все еще выглядит так, будто живет в PHP 5.3.
Преимущества по сравнению с Flight
- Fat-Free имеет немного больше звезд на GitHub, чем Flight.
- Fat-Free имеет неплохую документацию, но в некоторых областях ей не хватает ясности.
- Fat-Free имеет некоторые скудные ресурсы, такие как уроки на YouTube и онлайн-статьи, которые можно использовать для изучения фреймворка.
- Fat-Free имеет некоторые полезные плагины, встроенные, которые иногда бывают полезны.
- Fat-Free имеет встроенный ORM под названием Mapper, который можно использовать для взаимодействия с вашей базой данных. Flight имеет active-record.
- Fat-Free имеет встроенные Sessions, Caching и локализацию. Flight требует использования сторонних библиотек, но это покрыто в документации.
- Fat-Free имеет небольшую группу плагинов, созданных сообществом, которые можно использовать для расширения фреймворка. Flight имеет некоторые, покрытые в документации и примерах.
- Fat-Free, как и Flight, не имеет зависимостей.
- Fat-Free, как и Flight, ориентирован на предоставление разработчику контроля над своим приложением и простого опыта разработки.
- Fat-Free поддерживает обратную совместимость, как и Flight (частично потому, что обновления становятся менее частыми).
- Fat-Free, как и Flight, предназначен для разработчиков, которые впервые осваивают мир фреймворков.
- Fat-Free имеет встроенный шаблонизатор, который более robustный, чем шаблонизатор Flight. Flight рекомендует Latte для этого.
- Fat-Free имеет уникальную CLI-команду типа "route", где вы можете строить CLI-приложения внутри самого Fat-Free и обрабатывать их как
GET
-запрос. Flight достигает этого с помощью runway.
Недостатки по сравнению с Flight
- Fat-Free имеет некоторые тесты реализации и даже имеет свой собственный класс test, который очень базовый. Однако он не на 100% покрыт unit-тестами, как Flight.
- Вам приходится использовать поисковую систему вроде Google, чтобы действительно искать по сайту документации.
- Flight имеет темный режим на своем сайте документации. (мик-дроп)
- Fat-Free имеет некоторые модули, которые ужасно не поддерживаются.
- Flight имеет простой PdoWrapper, который немного проще, чем встроенный класс
DB\SQL
Fat-Free. - Flight имеет плагин permissions, который можно использовать для защиты вашего приложения. Fat-Free требует использования сторонней библиотеки.
- Flight имеет ORM под названием active-record, который ощущается больше как ORM, чем Mapper Fat-Free.
Дополнительное преимущество
active-record
в том, что вы можете определять отношения между записями для автоматических джойнов, в то время как Mapper Fat-Free требует создания SQL-представлений. - Удивительно, но Fat-Free не имеет корневого пространства имен. Flight имеет пространства имен на протяжении всего пути, чтобы не конфликтовать с вашим собственным кодом.
Класс
Cache
— самый большой нарушитель здесь. - Fat-Free не имеет middleware. Вместо этого есть хуки
beforeroute
иafterroute
, которые можно использовать для фильтрации запросов и ответов в контроллерах. - Fat-Free не может группировать маршруты.
- Fat-Free имеет обработчик контейнера внедрения зависимостей, но документация невероятно скудная по поводу того, как его использовать.
- Отладка может быть немного сложной, поскольку практически все хранится в том, что называется
HIVE
Learn/extending
Расширение
Обзор
Flight разработан как расширяемая платформа. Фреймворк поставляется с набором стандартных методов и компонентов, но позволяет вам отображать свои собственные методы, регистрировать свои собственные классы или даже переопределять существующие классы и методы.
Понимание
Есть 2 способа расширить функциональность Flight:
- Отображение методов — это используется для создания простых пользовательских методов, которые вы можете вызывать из любого места в вашем приложении. Они обычно используются для утилитарных функций, которые вы хотите вызывать из любого места в вашем коде.
- Регистрация классов — это используется для регистрации ваших собственных классов в Flight. Это обычно используется для классов, которые имеют зависимости или требуют конфигурации.
Вы также можете переопределять существующие методы фреймворка, чтобы изменить их поведение по умолчанию, чтобы лучше соответствовать потребностям вашего проекта.
Если вы ищете DIC (Dependency Injection Container), перейдите на страницу Dependency Injection Container.
Основное использование
Переопределение методов фреймворка
Flight позволяет вам переопределять его стандартную функциональность в соответствии с вашими потребностями, без необходимости изменять какой-либо код. Вы можете просмотреть все методы, которые можно переопределить, ниже.
Например, когда Flight не может сопоставить URL с маршрутом, он вызывает метод notFound
,
который отправляет общий ответ HTTP 404
. Вы можете переопределить это поведение,
используя метод map
:
Flight::map('notFound', function() {
// Отображение пользовательской страницы 404
include 'errors/404.html';
});
Flight также позволяет заменить основные компоненты фреймворка. Например, вы можете заменить стандартный класс Router на свой собственный пользовательский класс:
// создание вашего пользовательского класса Router
class MyRouter extends \flight\net\Router {
// переопределение методов здесь
// например, сокращение для GET-запросов, чтобы удалить
// функцию передачи маршрута
public function get($pattern, $callback, $alias = '') {
return parent::get($pattern, $callback, false, $alias);
}
}
// Регистрация вашего пользовательского класса
Flight::register('router', MyRouter::class);
// Когда Flight загружает экземпляр Router, он загрузит ваш класс
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
echo "Hello World!";
}, 'hello_alias');
Однако методы фреймворка, такие как map
и register
, нельзя переопределять. Вы получите
ошибку, если попытаетесь это сделать (см. ниже для списка методов).
Отображаемые методы фреймворка
Ниже приведен полный набор методов для фреймворка. Он состоит из основных методов, которые являются обычными статическими методами, и расширяемых методов, которые являются отображенными методами, которые можно фильтровать или переопределять.
Основные методы
Эти методы являются основными для фреймворка и не могут быть переопределены.
Flight::map(string $name, callable $callback, bool $pass_route = false) // Создает пользовательский метод фреймворка.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Регистрирует класс для метода фреймворка.
Flight::unregister(string $name) // Отменяет регистрацию класса для метода фреймворка.
Flight::before(string $name, callable $callback) // Добавляет фильтр перед методом фреймворка.
Flight::after(string $name, callable $callback) // Добавляет фильтр после метода фреймворка.
Flight::path(string $path) // Добавляет путь для автозагрузки классов.
Flight::get(string $key) // Получает переменную, установленную Flight::set().
Flight::set(string $key, mixed $value) // Устанавливает переменную внутри движка Flight.
Flight::has(string $key) // Проверяет, установлена ли переменная.
Flight::clear(array|string $key = []) // Очищает переменную.
Flight::init() // Инициализирует фреймворк с настройками по умолчанию.
Flight::app() // Получает экземпляр объекта приложения
Flight::request() // Получает экземпляр объекта запроса
Flight::response() // Получает экземпляр объекта ответа
Flight::router() // Получает экземпляр объекта маршрутизатора
Flight::view() // Получает экземпляр объекта представления
Расширяемые методы
Flight::start() // Запускает фреймворк.
Flight::stop() // Останавливает фреймворк и отправляет ответ.
Flight::halt(int $code = 200, string $message = '') // Останавливает фреймворк с опциональным кодом статуса и сообщением.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Отображает шаблон URL на callback.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Отображает шаблон URL POST-запроса на callback.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Отображает шаблон URL PUT-запроса на callback.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Отображает шаблон URL PATCH-запроса на callback.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Отображает шаблон URL DELETE-запроса на callback.
Flight::group(string $pattern, callable $callback) // Создает группировку для URL, шаблон должен быть строкой.
Flight::getUrl(string $name, array $params = []) // Генерирует URL на основе псевдонима маршрута.
Flight::redirect(string $url, int $code) // Перенаправляет на другой URL.
Flight::download(string $filePath) // Скачивает файл.
Flight::render(string $file, array $data, ?string $key = null) // Рендерит файл шаблона.
Flight::error(Throwable $error) // Отправляет ответ HTTP 500.
Flight::notFound() // Отправляет ответ HTTP 404.
Flight::etag(string $id, string $type = 'string') // Выполняет кэширование HTTP ETag.
Flight::lastModified(int $time) // Выполняет кэширование HTTP последнего изменения.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Отправляет JSON-ответ.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Отправляет JSONP-ответ.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Отправляет JSON-ответ и останавливает фреймворк.
Flight::onEvent(string $event, callable $callback) // Регистрирует слушатель события.
Flight::triggerEvent(string $event, ...$args) // Запускает событие.
Любые пользовательские методы, добавленные с помощью map
и register
, также могут быть отфильтрованы. Для примеров того, как фильтровать эти методы, см. руководство Filtering Methods.
Расширяемые классы фреймворка
Есть несколько классов, функциональность которых вы можете переопределить, расширив их и регистрируя свой собственный класс. Эти классы:
Flight::app() // Класс приложения — расширьте класс flight\Engine
Flight::request() // Класс запроса — расширьте класс flight\net\Request
Flight::response() // Класс ответа — расширьте класс flight\net\Response
Flight::router() // Класс маршрутизатора — расширьте класс flight\net\Router
Flight::view() // Класс представления — расширьте класс flight\template\View
Flight::eventDispatcher() // Класс диспетчера событий — расширьте класс flight\core\Dispatcher
Отображение пользовательских методов
Чтобы отобразить свой собственный простой пользовательский метод, вы используете функцию map
:
// Отображение вашего метода
Flight::map('hello', function (string $name) {
echo "hello $name!";
});
// Вызов вашего пользовательского метода
Flight::hello('Bob');
Хотя возможно создавать простые пользовательские методы, рекомендуется просто создавать стандартные функции в PHP. Это обеспечивает автодополнение в IDE и легче читается. Эквивалент приведенного выше кода будет:
function hello(string $name) {
echo "hello $name!";
}
hello('Bob');
Это используется чаще, когда вам нужно передавать переменные в ваш метод, чтобы получить ожидаемое
значение. Использование метода register()
, как ниже, больше подходит для передачи конфигурации,
а затем вызова вашего предварительно настроенного класса.
Регистрация пользовательских классов
Чтобы зарегистрировать свой собственный класс и настроить его, вы используете функцию register
. Преимущество этого над map() заключается в том, что вы можете повторно использовать тот же класс при вызове этой функции (это будет полезно с Flight::db()
, чтобы делить один и тот же экземпляр).
// Регистрация вашего класса
Flight::register('user', User::class);
// Получение экземпляра вашего класса
$user = Flight::user();
Метод register также позволяет передавать параметры конструктору вашего класса. Таким образом, когда вы загружаете свой пользовательский класс, он будет предварительно инициализирован. Вы можете определить параметры конструктора, передав дополнительный массив. Вот пример загрузки соединения с базой данных:
// Регистрация класса с параметрами конструктора
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();
}
}
Если вы передадите дополнительный параметр callback, он будет выполнен сразу после создания класса. Это позволяет выполнить любые процедуры настройки для вашего нового объекта. Функция callback принимает один параметр — экземпляр нового объекта.
// Callback будет передан объект, который был создан
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 не имеет встроенной системы логирования, однако очень легко использовать библиотеку логирования с Flight. Вот пример с использованием библиотеки Monolog:
// services.php
// Регистрация логгера с Flight
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});
Теперь, когда он зарегистрирован, вы можете использовать его в своем приложении:
// В вашем контроллере или маршруте
Flight::log()->warning('This is a warning message');
Это запишет сообщение в указанный вами файл лога. Что, если вы хотите записать что-то, когда происходит
ошибка? Вы можете использовать метод error
:
// В вашем контроллере или маршруте
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// Отображение вашей пользовательской страницы ошибки
include 'errors/500.html';
});
Вы также можете создать базовую систему APM (Application Performance Monitoring),
используя методы before
и after
:
// В вашем файле services.php
Flight::before('start', function() {
Flight::set('start_time', microtime(true));
});
Flight::after('start', function() {
$end = microtime(true);
$start = Flight::get('start_time');
Flight::log()->info('Request '.Flight::request()->url.' took ' . round($end - $start, 4) . ' seconds');
// Вы также можете добавить заголовки запроса или ответа
// для их логирования (будьте осторожны, поскольку это будет много
// данных, если у вас много запросов)
Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});
Кэширование
Flight не имеет встроенной системы кэширования, однако очень легко использовать библиотеку кэширования с Flight. Вот пример с использованием библиотеки PHP File Cache:
// services.php
// Регистрация кэша с Flight
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
$cache->setDevMode(ENVIRONMENT === 'development');
});
Теперь, когда он зарегистрирован, вы можете использовать его в своем приложении:
// В вашем контроллере или маршруте
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
// Выполните некоторую обработку, чтобы получить данные
$data = [ 'some' => 'data' ];
Flight::cache()->set('my_cache_key', $data, 3600); // кэшировать на 1 час
}
Легкая инстанциация объектов DIC
Если вы используете DIC (Dependency Injection Container) в своем приложении, вы можете использовать Flight, чтобы помочь вам инстанцировать ваши объекты. Вот пример с использованием библиотеки Dice:
// services.php
// создание нового контейнера
$container = new \Dice\Dice;
// не забудьте переприсвоить его самому себе, как ниже!
$container = $container->addRule('PDO', [
// shared означает, что тот же объект будет возвращен каждый раз
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// теперь мы можем создать отображаемый метод для создания любого объекта.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Это регистрирует обработчик контейнера, чтобы Flight знал, как использовать его для контроллеров/промежуточного ПО
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// предположим, у нас есть следующий пример класса, который принимает объект PDO в конструкторе
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// код, который отправляет email
}
}
// И наконец, вы можете создавать объекты с использованием dependency injection
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
Круто, правда?
См. также
- Dependency Injection Container — Как использовать DIC с Flight.
- File Cache — Пример использования библиотеки кэширования с Flight.
Устранение неполадок
- Помните, что отображенные методы имеют приоритет над зарегистрированными классами. Если вы объявите оба с одним и тем же именем, будет вызван только отображенный метод.
Журнал изменений
- v2.0 — Первое издание.
Learn/json
Обёртка JSON
Обзор
Класс Json
в Flight предоставляет простой и последовательный способ кодирования и декодирования данных JSON в вашем приложении. Он оборачивает встроенные функции JSON PHP с улучшенной обработкой ошибок и некоторыми полезными настройками по умолчанию, делая работу с JSON проще и безопаснее.
Понимание
Работа с JSON очень распространена в современных PHP-приложениях, особенно при создании API или обработке AJAX-запросов. Класс Json
централизует всё кодирование и декодирование JSON, так что вам не нужно беспокоиться о странных крайних случаях или загадочных ошибках от встроенных функций PHP.
Ключевые особенности:
- Последовательная обработка ошибок (выбрасывает исключения при неудаче)
- Настройки по умолчанию для кодирования/декодирования (например, неэкранированные слеши)
- Утилитные методы для красивой печати и валидации
Базовое использование
Кодирование данных в JSON
Чтобы преобразовать данные PHP в строку JSON, используйте Json::encode()
:
use flight\util\Json;
$data = [
'framework' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$json = Json::encode($data);
echo $json;
// Вывод: {"framework":"Flight","version":3,"features":["routing","views","extending"]}
Если кодирование не удастся, вы получите исключение с полезным сообщением об ошибке.
Красивая печать
Хотите, чтобы JSON был читаемым для человека? Используйте prettyPrint()
:
echo Json::prettyPrint($data);
/*
{
"framework": "Flight",
"version": 3,
"features": [
"routing",
"views",
"extending"
]
}
*/
Декодирование строк JSON
Чтобы преобразовать строку JSON обратно в данные PHP, используйте Json::decode()
:
$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Вывод: Flight
Если вы хотите ассоциативный массив вместо объекта, передайте true
в качестве второго аргумента:
$data = Json::decode($json, true);
echo $data['framework']; // Вывод: Flight
Если декодирование не удастся, вы получите исключение с ясным сообщением об ошибке.
Валидация JSON
Проверьте, является ли строка валидным JSON:
if (Json::isValid($json)) {
// Это валидно!
} else {
// Не валидный JSON
}
Получение последней ошибки
Если вы хотите проверить последнее сообщение об ошибке JSON (из встроенных функций PHP):
$error = Json::getLastError();
if ($error !== '') {
echo "Последняя ошибка JSON: $error";
}
Расширенное использование
Вы можете настроить опции кодирования и декодирования, если вам нужно больше контроля (см. опции json_encode в PHP):
// Кодирование с опцией HEX_TAG
$json = Json::encode($data, JSON_HEX_TAG);
// Декодирование с пользовательской глубиной
$data = Json::decode($json, false, 1024);
См. также
- Collections - Для работы со структурированными данными, которые можно легко преобразовать в JSON.
- Configuration - Как настроить ваше приложение Flight.
- Extending - Как добавить свои утилиты или переопределить основные классы.
Устранение неисправностей
- Если кодирование или декодирование не удастся, выбрасывается исключение — оберните вызовы в try/catch, если хотите обрабатывать ошибки грациозно.
- Если вы получаете неожиданные результаты, проверьте данные на циклические ссылки или не-UTF8 символы.
- Используйте
Json::isValid()
, чтобы проверить, является ли строка валидным JSON перед декодированием.
Журнал изменений
- v3.16.0 - Добавлен утилитный класс обёртки JSON.
Learn/flight_vs_slim
Flight vs Slim
Что такое Slim?
Slim — это PHP-микрофреймворк, который помогает быстро писать простые, но мощные веб-приложения и API.
Много вдохновения для некоторых функций версии 3 Flight на самом деле пришло из Slim. Группировка маршрутов и выполнение middleware в определённом порядке — это две функции, вдохновлённые Slim. Slim v3 вышла с акцентом на простоту, но по поводу v4 есть смешанные отзывы.
Преимущества по сравнению с Flight
- Slim имеет более крупное сообщество разработчиков, которые, в свою очередь, создают полезные модули, чтобы вам не приходилось изобретать велосипед.
- Slim следует многим интерфейсам и стандартам, распространённым в сообществе PHP, что повышает совместимость.
- У Slim есть приличная документация и руководства, которые можно использовать для изучения фреймворка (ничего по сравнению с Laravel или Symfony).
- У Slim есть различные ресурсы, такие как руководства на YouTube и онлайн-статьи, которые можно использовать для изучения фреймворка.
- Slim позволяет использовать любые компоненты для обработки основных функций маршрутизации, поскольку он соответствует PSR-7.
Недостатки по сравнению с Flight
- Удивительно, но Slim не так быстр, как вы могли бы подумать для микрофреймворка. Посмотрите бенчмарки TechEmpower для получения дополнительной информации.
- Flight ориентирован на разработчика, который хочет создать лёгкое, быстрое и простое в использовании веб-приложение.
- Flight не имеет зависимостей, в то время как у Slim есть несколько зависимостей, которые вам нужно установить.
- Flight ориентирован на простоту и удобство использования.
- Одна из ключевых особенностей Flight — это то, что он старается поддерживать обратную совместимость. Переход от Slim v3 к v4 был разрывом совместимости.
- Flight предназначен для разработчиков, которые впервые вступают в мир фреймворков.
- Flight также может создавать приложения уровня enterprise, но у него нет столько примеров и руководств, сколько у Slim. Также от разработчика потребуется больше дисциплины, чтобы держать вещи организованными и хорошо структурированными.
- Flight даёт разработчику больше контроля над приложением, в то время как Slim может незаметно вводить магию за кулисами.
- Flight имеет простой PdoWrapper, который можно использовать для взаимодействия с вашей базой данных. Slim требует использования сторонней библиотеки.
- Flight имеет плагин разрешений permissions, который можно использовать для защиты вашего приложения. Slim требует использования сторонней библиотеки.
- Flight имеет ORM под названием active-record, который можно использовать для взаимодействия с вашей базой данных. Slim требует использования сторонней библиотеки.
- Flight имеет CLI-приложение под названием runway, которое можно использовать для запуска вашего приложения из командной строки. У Slim такого нет.
Learn/autoloading
Автозагрузка
Обзор
Автозагрузка — это концепция в PHP, где вы указываете каталог или каталоги для загрузки классов. Это гораздо полезнее, чем использование require
или include
для загрузки классов. Это также обязательное требование для использования пакетов Composer.
Понимание
По умолчанию любой класс Flight
автоматически автозагружается благодаря composer. Однако, если вы хотите автозагружать свои собственные классы, вы можете использовать метод Flight::path()
для указания каталога, из которого будут загружаться классы.
Использование автозагрузчика может значительно упростить ваш код. Вместо того чтобы файлы начинались с множества операторов include
или require
вверху для захвата всех используемых в этом файле классов, вы можете динамически вызывать свои классы, и они будут включаться автоматически.
Базовое использование
Предположим, у нас есть дерево каталогов, подобное следующему:
# Пример пути
/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
*/
// пространства имен обязательны
// пространства имен такие же, как структура каталогов
// пространства имен должны следовать тому же регистру, что и структура каталогов
// пространства имен и каталоги не могут содержать подчеркивания (если не установлен Loader::setV2ClassLoading(false))
namespace app\controllers;
// Рекомендуется использовать Pascal Case для всех автозагружаемых классов (каждое слово с заглавной буквы, без пробелов)
// Начиная с 3.7.2, вы можете использовать Pascal_Snake_Case для имен классов, запустив Loader::setV2ClassLoading(false);
class MyController {
public function index() {
// сделать что-то
}
}
А если вы хотите автозагрузить класс в каталоге utils, вы сделаете в основном то же самое:
/**
* app/UTILS/ArrayHelperUtil.php
*/
// пространство имен должно соответствовать структуре каталога и регистру (обратите внимание, что каталог UTILS все заглавные буквы
// как в дереве файлов выше)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// сделать что-то
}
}
Подчеркивания в именах классов
Начиная с 3.7.2, вы можете использовать Pascal_Snake_Case для имен классов, запустив Loader::setV2ClassLoading(false);
.
Это позволит вам использовать подчеркивания в именах классов.
Это не рекомендуется, но доступно для тех, кто в этом нуждается.
use flight\core\Loader;
/**
* public/index.php
*/
// Добавьте путь к автозагрузчику
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);
/**
* app/controllers/My_Controller.php
*/
// пространства имен не требуются
class My_Controller {
public function index() {
// сделать что-то
}
}
См. также
- Маршрутизация - Как сопоставлять маршруты с контроллерами и отображать представления.
- Почему фреймворк? - Понимание преимуществ использования фреймворка вроде Flight.
Устранение неисправностей
- Если вы не можете понять, почему ваши классы с пространствами имен не находятся, помните использовать
Flight::path()
к корневому каталогу в вашем проекте, а не к каталогуapp/
илиsrc/
или эквиваленту.
Класс не найден (автозагрузка не работает)
Для этого может быть несколько причин. Ниже приведены некоторые примеры, но также убедитесь, что вы проверили раздел автозагрузка.
Неправильное имя файла
Наиболее распространенная причина — имя класса не соответствует имени файла.
Если у вас есть класс с именем MyClass
, то файл должен называться MyClass.php
. Если у вас есть класс с именем MyClass
, а файл называется myclass.php
то автозагрузчик не сможет его найти.
Неправильное пространство имен
Если вы используете пространства имен, то пространство имен должно соответствовать структуре каталога.
// ...код...
// если ваш MyController находится в каталоге app/controllers и он с пространством имен
// это не сработает.
Flight::route('/hello', 'MyController->hello');
// вам нужно выбрать один из этих вариантов
Flight::route('/hello', 'app\controllers\MyController->hello');
// или если у вас есть оператор use вверху
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// также может быть написано
Flight::route('/hello', MyController::class.'->hello');
// также...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);
path()
не определен
В скелетном приложении это определено внутри файла config.php
, но чтобы ваши классы были найдены, вам нужно убедиться, что метод path()
определен (вероятно, к корню вашего каталога) до того, как вы попытаетесь его использовать.
// Добавьте путь к автозагрузчику
Flight::path(__DIR__.'/../');
Журнал изменений
- v3.7.2 - Вы можете использовать Pascal_Snake_Case для имен классов, запустив
Loader::setV2ClassLoading(false);
- v2.0 - Добавлена функциональность автозагрузки.
Learn/uploaded_file
Обработчик загруженного файла
Обзор
Класс UploadedFile
в Flight упрощает и делает безопасной обработку загрузки файлов в вашем приложении. Он оборачивает детали процесса загрузки файлов PHP, предоставляя простой объектно-ориентированный способ доступа к информации о файле и перемещения загруженных файлов.
Понимание
Когда пользователь загружает файл через форму, PHP сохраняет информацию о файле в суперглобальной переменной $_FILES
. В Flight вы редко взаимодействуете с $_FILES
напрямую. Вместо этого объект Request
Flight (доступный через Flight::request()
) предоставляет метод getUploadedFiles()
, который возвращает массив объектов UploadedFile
, делая обработку файлов гораздо более удобной и надежной.
Класс UploadedFile
предоставляет методы для:
- Получения оригинального имени файла, типа MIME, размера и временного расположения
- Проверки ошибок загрузки
- Перемещения загруженного файла в постоянное расположение
Этот класс помогает избежать распространенных ошибок при загрузке файлов, таких как обработка ошибок или безопасное перемещение файлов.
Базовое использование
Доступ к загруженным файлам из запроса
Рекомендуемый способ доступа к загруженным файлам — через объект запроса:
Flight::route('POST /upload', function() {
// Для поля формы <input type="file" name="myFile">
$uploadedFiles = Flight::request()->getUploadedFiles();
$file = $uploadedFiles['myFile'];
// Теперь вы можете использовать методы UploadedFile
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Файл успешно загружен!";
} else {
echo "Загрузка не удалась: " . $file->getError();
}
});
Обработка множественной загрузки файлов
Если ваша форма использует name="myFiles[]"
для множественной загрузки, вы получите массив объектов UploadedFile
:
Flight::route('POST /upload', function() {
// Для поля формы <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles();
foreach ($uploadedFiles['myFiles'] as $file) {
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Загружено: " . $file->getClientFilename() . "<br>";
} else {
echo "Не удалось загрузить: " . $file->getClientFilename() . "<br>";
}
}
});
Создание экземпляра UploadedFile вручную
Обычно вы не создаете UploadedFile
вручную, но это возможно при необходимости:
use flight\net\UploadedFile;
$file = new UploadedFile(
$_FILES['myfile']['name'],
$_FILES['myfile']['type'],
$_FILES['myfile']['size'],
$_FILES['myfile']['tmp_name'],
$_FILES['myfile']['error']
);
Доступ к информации о файле
Вы можете легко получить детали о загруженном файле:
echo $file->getClientFilename(); // Оригинальное имя файла с компьютера пользователя
echo $file->getClientMediaType(); // Тип MIME (например, image/png)
echo $file->getSize(); // Размер файла в байтах
echo $file->getTempName(); // Временный путь к файлу на сервере
echo $file->getError(); // Код ошибки загрузки (0 означает отсутствие ошибки)
Перемещение загруженного файла
После валидации файла переместите его в постоянное расположение:
try {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Файл успешно загружен!";
} catch (Exception $e) {
echo "Загрузка не удалась: " . $e->getMessage();
}
Метод moveTo()
вызовет исключение, если что-то пойдет не так (например, ошибка загрузки или проблема с правами доступа).
Обработка ошибок загрузки
Если во время загрузки возникла проблема, вы можете получить читаемое сообщение об ошибке:
if ($file->getError() !== UPLOAD_ERR_OK) {
// Вы можете использовать код ошибки или поймать исключение от moveTo()
echo "Произошла ошибка при загрузке файла.";
}
См. также
- Requests - Узнайте, как получать доступ к загруженным файлам из HTTP-запросов и увидеть больше примеров загрузки файлов.
- Configuration - Как настроить лимиты загрузки и директории в PHP.
- Extending - Как настроить или расширить основные классы Flight.
Устранение неисправностей
- Всегда проверяйте
$file->getError()
перед перемещением файла. - Убедитесь, что директория загрузки доступна для записи веб-сервером.
- Если
moveTo()
не срабатывает, проверьте сообщение исключения для деталей. - Настройки PHP
upload_max_filesize
иpost_max_size
могут ограничивать загрузку файлов. - Для множественной загрузки файлов всегда проходите циклом по массиву объектов
UploadedFile
.
Журнал изменений
- v3.12.0 - Добавлен класс
UploadedFile
в объект запроса для упрощения обработки файлов.
Guides/unit_testing
Unit Testing в Flight PHP с PHPUnit
Этот гид вводит в unit testing в Flight PHP с использованием PHPUnit, предназначен для начинающих, которые хотят понять почему unit testing важен и как применять его на практике. Мы сосредоточимся на тестировании поведения — обеспечении того, что ваше приложение делает то, что ожидается, например, отправка email или сохранение записи — вместо тривиальных вычислений. Мы начнем с простого route handler и перейдем к более сложному controller, включая dependency injection (DI) и mocking сторонних сервисов.
Почему Unit Test?
Unit testing обеспечивает, что ваш код ведет себя как ожидается, ловит баги до того, как они попадут в production. Это особенно ценно в Flight, где легковесный routing и гибкость могут привести к сложным взаимодействиям. Для solo-разработчиков или команд unit tests действуют как safety net, документируя ожидаемое поведение и предотвращая регрессии при возвращении к коду позже. Они также улучшают дизайн: код, который трудно тестировать, часто сигнализирует о чрезмерной сложности или тесной связанности классов.
В отличие от простых примеров (например, тестирование x * y = z
), мы сосредоточимся на реальном поведении, таком как валидация ввода, сохранение данных или запуск действий вроде email. Наша цель — сделать тестирование доступным и значимым.
Общие Руководящие Принципы
- Тестируйте Поведение, Не Реализацию: Сосредоточьтесь на результатах (например, «email отправлен» или «запись сохранена») вместо внутренних деталей. Это делает тесты устойчивыми к рефакторингу.
- Перестаньте использовать
Flight::
: Статические методы Flight невероятно удобны, но усложняют тестирование. Вы должны привыкнуть использовать переменную$app
из$app = Flight::app();
.$app
имеет все те же методы, что иFlight::
. Вы все еще сможете использовать$app->route()
или$this->app->json()
в вашем controller и т.д. Также вы должны использовать реальный Flight router с$router = $app->router()
и затем вы сможете использовать$router->get()
,$router->post()
,$router->group()
и т.д. См. Routing. - Держите Тесты Быстрыми: Быстрые тесты поощряют частое выполнение. Избегайте медленных операций, таких как вызовы базы данных в unit tests. Если у вас есть медленный тест, это признак, что вы пишете integration test, а не unit test. Integration tests — это когда вы действительно вовлекаете реальные базы данных, реальные HTTP-вызовы, реальную отправку email и т.д. У них есть свое место, но они медленные и могут быть flaky, то есть иногда падают по неизвестной причине.
- Используйте Описательные Имена: Имена тестов должны четко описывать тестируемое поведение. Это улучшает читаемость и поддерживаемость.
- Избегайте Globals Как Чумы: Минимизируйте использование
$app->set()
и$app->get()
, поскольку они действуют как global state, требуя mocks в каждом тесте. Предпочитайте DI или DI container (см. Dependency Injection Container). Даже использование метода$app->map()
технически является "global" и должно избегаться в пользу DI. Используйте библиотеку сессий, такую как flightphp/session, чтобы вы могли mock объект сессии в ваших тестах. Не вызывайте$_SESSION
напрямую в вашем коде, поскольку это внедряет global variable в ваш код, усложняя тестирование. - Используйте Dependency Injection: Внедряйте зависимости (например,
PDO
, mailers) в controllers для изоляции логики и упрощения mocking. Если у вас есть класс с слишком многими зависимостями, рассмотрите рефакторинг его в меньшие классы, каждый с одной ответственностью, следуя SOLID principles. - Mock Сторонние Сервисы: Mock базы данных, HTTP-клиенты (cURL) или email-сервисы, чтобы избежать внешних вызовов. Тестируйте на один-два уровня в глубину, но позволяйте вашей основной логике работать. Например, если ваше приложение отправляет SMS, вы НЕ хотите реально отправлять SMS каждый раз, когда запускаете тесты, потому что эти расходы накопятся (и это будет медленнее). Вместо этого mock сервис SMS и просто проверьте, что ваш код вызвал сервис SMS с правильными параметрами.
- Стремитесь к Высокому Покрытию, Не к Совершенству: 100% покрытие строк хорошо, но это не значит, что все в вашем коде протестировано правильно (погуглите branch/path coverage в PHPUnit). Приоритизируйте критические поведения (например, регистрацию пользователя, ответы API и захват неудачных ответов).
- Используйте Controllers для Routes: В ваших определениях routes используйте controllers, а не closures.
flight\Engine $app
по умолчанию внедряется в каждый controller через конструктор. В тестах используйте$app = new Flight\Engine()
для инстанцирования Flight в тесте, внедрите его в ваш controller и вызывайте методы напрямую (например,$controller->register()
). См. Extending Flight и Routing. - Выберите Стиль Mocking и Придерживайтесь Его: PHPUnit поддерживает несколько стилей mocking (например, prophecy, встроенные mocks), или вы можете использовать anonymous classes, которые имеют свои преимущества, такие как code completion, поломка при изменении определения метода и т.д. Просто будьте последовательны в ваших тестах. См. PHPUnit Mock Objects.
- Используйте
protected
visibility для методов/свойств, которые вы хотите тестировать в подклассах: Это позволяет переопределять их в тестовых подклассах без их публичности, это особенно полезно для anonymous class mocks.
Настройка PHPUnit
Сначала настройте PHPUnit в вашем проекте Flight PHP с использованием Composer для удобного тестирования. См. PHPUnit Getting Started guide для более подробной информации.
-
В директории вашего проекта запустите:
composer require --dev phpunit/phpunit
Это установит последнюю версию PHPUnit как development dependency.
-
Создайте директорию
tests
в корне вашего проекта для файлов тестов. -
Добавьте скрипт теста в
composer.json
для удобства:// other composer.json content "scripts": { "test": "phpunit --configuration phpunit.xml" }
-
Создайте файл
phpunit.xml
в корне:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Теперь, когда ваши тесты собраны, вы можете запустить composer test
для выполнения тестов.
Тестирование Простого Route Handler
Давайте начнем с базового route, который валидирует email-ввод пользователя. Мы протестируем его поведение: возвращение сообщения об успехе для валидных email и ошибки для невалидных. Для валидации email мы используем filter_var
.
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
$responseArray = [];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$responseArray = ['status' => 'error', 'message' => 'Invalid email'];
} else {
$responseArray = ['status' => 'success', 'message' => 'Valid email'];
}
$this->app->json($responseArray);
}
}
Чтобы протестировать это, создайте файл теста. См. Unit Testing and SOLID Principles для большего количества информации о структурировании тестов:
// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'test@example.com'; // Simulate POST data
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'invalid-email'; // Simulate POST data
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Ключевые Моменты:
- Мы симулируем POST-данные с использованием request class. Не используйте globals вроде
$_POST
,$_GET
и т.д., поскольку это усложняет тестирование (вы всегда должны сбрасывать эти значения, иначе другие тесты могут сломаться). - Все controllers по умолчанию будут иметь инстанс
flight\Engine
, внедренный в них, даже без настройки DIC container. Это делает гораздо проще тестировать controllers напрямую. - Нет использования
Flight::
вообще, что делает код проще для тестирования. - Тесты проверяют поведение: правильный статус и сообщение для валидных/невалидных email.
Запустите composer test
, чтобы проверить, что route ведет себя как ожидается. Для большего количества информации о requests и responses в Flight см. соответствующие docs.
Использование Dependency Injection для Testable Controllers
Для более сложных сценариев используйте dependency injection (DI), чтобы сделать controllers testable. Избегайте globals Flight (например, Flight::set()
, Flight::map()
, Flight::register()
), поскольку они действуют как global state, требуя mocks для каждого теста. Вместо этого используйте DI container Flight, DICE, PHP-DI или manual DI.
Давайте используем flight\database\PdoWrapper
вместо raw PDO. Этот wrapper гораздо проще mock и unit test!
Вот controller, который сохраняет пользователя в базу данных и отправляет welcome email:
use flight\database\PdoWrapper;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, PdoWrapper $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// adding the return here helps unit testing to stop execution
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Ключевые Моменты:
- Controller зависит от инстанса
PdoWrapper
иMailerInterface
(предполагаемый сторонний email-сервис). - Зависимости внедряются через конструктор, избегая globals.
Тестирование Controller с Mocks
Теперь протестируем поведение UserController
: валидацию email, сохранение в базу данных и отправку email. Мы замоким базу данных и mailer, чтобы изолировать controller.
// tests/UserControllerDICTest.php
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// Иногда смешивание стилей mocking необходимо
// Здесь мы используем встроенный mock PHPUnit для PDOStatement
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// Используя anonymous class для mocking PdoWrapper
$mockDb = new class($statementMock) extends PdoWrapper {
protected $statementMock;
public function __construct($statementMock) {
$this->statementMock = $statementMock;
}
// Когда мы моким его таким образом, мы не делаем реальный вызов базы данных.
// Мы можем дополнительно настроить это, чтобы изменить mock PDOStatement для симуляции сбоев и т.д.
public function runQuery(string $sql, array $params = []): PDOStatement {
return $this->statementMock;
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
$this->sentEmail = $email;
return true;
}
};
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends PdoWrapper {
// An empty constructor bypasses the parent constructor
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('Should not be called');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('Should not be called');
}
};
$app = new Engine();
$app->request()->data->email = 'invalid-email';
// Need to map jsonHalt to avoid exiting
$app->map('jsonHalt', function($data) use ($app) {
$app->json($data, 400);
});
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('error', $result['status']);
$this->assertEquals('Invalid email', $result['message']);
}
}
Ключевые Моменты:
- Мы моким
PdoWrapper
иMailerInterface
, чтобы избежать реальных вызовов базы данных или email. - Тесты проверяют поведение: валидные email запускают вставки в базу данных и отправку email; невалидные email пропускают оба.
- Mock сторонние зависимости (например,
PdoWrapper
,MailerInterface
), позволяя логике controller работать.
Слишком Много Mocking
Будьте осторожны, чтобы не mock слишком много вашего кода. Позвольте мне дать пример ниже, почему это может быть плохой идеей, используя наш UserController
. Мы изменим эту проверку на метод под названием isEmailValid
(используя filter_var
) и другие новые добавления в отдельный метод под названием registerUser
.
use flight\database\PdoWrapper;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, PdoWrapper $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!$this->isEmailValid($email)) {
// adding the return here helps unit testing to stop execution
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'User registered']);
}
protected function isEmailValid($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
protected function registerUser($email) {
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
}
}
И теперь overmocked unit test, который на самом деле ничего не тестирует:
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
// we are skipping the extra dependency injection here cause it's "easy"
$controller = new class($app) extends UserControllerDICV2 {
protected $app;
// Bypass the deps in the construct
public function __construct($app) {
$this->app = $app;
}
// We'll just force this to be valid.
protected function isEmailValid($email) {
return true; // Always return true, bypassing real validation
}
// Bypass the actual DB and mailer calls
protected function registerUser($email) {
return false;
}
};
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
}
}
Ура, у нас есть unit tests и они проходят! Но подождите, что если я на самом деле изменю внутреннюю работу isEmailValid
или registerUser
? Мои тесты все равно пройдут, потому что я замокил всю функциональность. Позвольте мне показать, что я имею в виду.
// UserControllerDICV2.php
class UserControllerDICV2 {
// ... other methods ...
protected function isEmailValid($email) {
// Changed logic
$validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
// Now it should only have a specific domain
$validDomain = strpos($email, '@example.com') !== false;
return $validEmail && $validDomain;
}
}
Если я запущу свои unit tests выше, они все равно пройдут! Но потому что я не тестировал поведение (на самом деле позволяя некоторому коду работать), я потенциально закодировал баг, ожидающий проявления в production. Тест должен быть модифицирован, чтобы учесть новое поведение, и также противоположность, когда поведение не то, что мы ожидаем.
Полный Пример
Вы можете найти полный пример проекта Flight PHP с unit tests на GitHub: n0nag0n/flight-unit-tests-guide. Для более глубокого понимания см. Unit Testing and SOLID Principles.
Распространенные Ошибки
- Over-Mocking: Не мокайте каждую зависимость; позвольте некоторой логике (например, валидации в controller) работать, чтобы тестировать реальное поведение. См. Unit Testing and SOLID Principles.
- Global State: Использование global PHP-переменных (например,
$_SESSION
,$_COOKIE
) сильно делает тесты хрупкими. То же самое сFlight::
. Рефакторьте, чтобы передавать зависимости явно. - Сложная Настройка: Если настройка теста громоздкая, ваш класс может иметь слишком много зависимостей или ответственностей, нарушая SOLID principles.
Масштабирование с Unit Tests
Unit tests сияют в больших проектах или при возвращении к коду через месяцы. Они документируют поведение и ловят регрессии, спасая вас от повторного изучения вашего app. Для solo devs тестируйте критические пути (например, регистрацию пользователя, обработку платежей). Для команд тесты обеспечивают последовательное поведение среди вкладов. См. Why Frameworks? для большего количества информации о преимуществах использования фреймворков и тестов.
Внесите свои собственные советы по тестированию в репозиторий документации Flight PHP!
Написано n0nag0n 2025
Guides/blog
Создание простого блога с Flight PHP
Этот гид проведет вас через создание базового блога с использованием фреймворка Flight PHP. Вы настроите проект, определите маршруты, управляйте постами с помощью JSON и отображайте их с помощью шаблонизатора Latte — все это демонстрирует простоту и гибкость Flight. В конце у вас будет функциональный блог с домашней страницей, страницами отдельных постов и формой для создания.
Предварительные требования
- PHP 7.4+: Установлен на вашей системе.
- Composer: Для управления зависимостями.
- Текстовый редактор: Любой редактор, например, VS Code или PHPStorm.
- Базовые знания PHP и веб-разработки.
Шаг 1: Настройте свой проект
Начните с создания новой директории проекта и установки Flight через Composer.
-
Создайте директорию:
mkdir flight-blog cd flight-blog
-
Установите Flight:
composer require flightphp/core
-
Создайте публичную директорию: Flight использует одну точку входа (
index.php
). Создайте папкуpublic/
для этого:mkdir public
-
Базовый
index.php
: Создайтеpublic/index.php
с простым маршрутом "hello world":<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo 'Привет, Flight!'; }); Flight::start();
-
Запустите встроенный сервер: Проверьте вашу настройку с помощью веб-сервера разработки PHP:
php -S localhost:8000 -t public/
Посетите
http://localhost:8000
, чтобы увидеть "Привет, Flight!".
Шаг 2: Организуйте структуру вашего проекта
Для чистой настройки структурируйте ваш проект следующим образом:
flight-blog/
├── app/
│ ├── config/
│ └── views/
├── data/
├── public/
│ └── index.php
├── vendor/
└── composer.json
app/config/
: Файлы конфигурации (например, события, маршруты).app/views/
: Шаблоны для отображения страниц.data/
: JSON-файл для хранения постов блога.public/
: Веб-корень сindex.php
.
Шаг 3: Установите и настройте Latte
Latte — это легкий шаблонизатор, который хорошо интегрируется с Flight.
-
Установите Latte:
composer require latte/latte
-
Настройте Latte в Flight: Обновите
public/index.php
, чтобы зарегистрировать Latte как движок представлений:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'Мой блог']); }); Flight::start();
-
Создайте шаблон разметки: В
app/views/layout.latte
:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>Мой блог</h1> <nav> <a href="/">Главная</a> | <a href="/create">Создать пост</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Блог Flight</p> </footer> </body> </html>
-
Создайте шаблон для главной страницы: В
app/views/home.latte
:{extends 'layout.latte'} {block content} <h2>{$title}</h2> <ul> {foreach $posts as $post} <li><a href="/post/{$post['slug']}">{$post['title']}</a></li> {/foreach} </ul> {/block}
Перезапустите сервер, если вы вышли из него, и посетите
http://localhost:8000
, чтобы увидеть отрендеренную страницу. -
Создайте файл данных:
Используйте JSON-файл, чтобы смоделировать базу данных для простоты.
В
data/posts.json
:[ { "slug": "first-post", "title": "Мой первый пост", "content": "Это мой самый первый пост в блоге с Flight PHP!" } ]
Шаг 4: Определите маршруты
Отделите ваши маршруты в файл конфигурации для лучшей организации.
-
Создание
routes.php
: Вapp/config/routes.php
:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'Мой блог']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Пост: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Создать пост']); });
-
Обновите
index.php
: Включите файл маршрутов:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); require '../app/config/routes.php'; Flight::start();
Шаг 5: Хранение и получение постов блога
Добавьте методы для загрузки и сохранения постов.
-
Добавьте метод для постов: В
index.php
добавьте метод для загрузки постов:Flight::map('posts', function () { $file = __DIR__ . '/../data/posts.json'; return json_decode(file_get_contents($file), true); });
-
Обновите маршруты: Измените
app/config/routes.php
, чтобы использовать посты:<?php Flight::route('/', function () { $posts = Flight::posts(); Flight::view()->render('home.latte', [ 'title' => 'Мой блог', 'posts' => $posts ]); }); Flight::route('/post/@slug', function ($slug) { $posts = Flight::posts(); $post = array_filter($posts, fn($p) => $p['slug'] === $slug); $post = reset($post) ?: null; if (!$post) { Flight::notFound(); return; } Flight::view()->render('post.latte', [ 'title' => $post['title'], 'post' => $post ]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Создать пост']); });
Шаг 6: Создание шаблонов
Обновите ваши шаблоны для отображения постов.
-
Страница поста (
app/views/post.latte
):{extends 'layout.latte'} {block content} <h2>{$post['title']}</h2> <div class="post-content"> <p>{$post['content']}</p> </div> {/block}
Шаг 7: Добавление создания постов
Обработайте отправку формы для добавления новых постов.
-
Создайте форму (
app/views/create.latte
):{extends 'layout.latte'} {block content} <h2>{$title}</h2> <form method="POST" action="/create"> <div class="form-group"> <label for="title">Заголовок:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">Содержимое:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">Сохранить пост</button> </form> {/block}
-
Добавьте маршрут POST: В
app/config/routes.php
:Flight::route('POST /create', function () { $request = Flight::request(); $title = $request->data['title']; $content = $request->data['content']; $slug = strtolower(str_replace(' ', '-', $title)); $posts = Flight::posts(); $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content]; file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT)); Flight::redirect('/'); });
-
Проверьте это:
- Посетите
http://localhost:8000/create
. - Отправьте новый пост (например, "Второй пост" с некоторым содержимым).
- Проверьте главную страницу, чтобы увидеть его в списке.
- Посетите
Шаг 8: Улучшите обработку ошибок
Переопределите метод notFound
для лучшего опыта 404.
В index.php
:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'Страница не найдена']);
});
Создайте app/views/404.latte
:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>Извините, этой страницы не существует!</p>
{/block}
Следующие шаги
- Добавить стили: Используйте CSS в ваших шаблонах для лучшего внешнего вида.
- База данных: Замените
posts.json
на базу данных, такую как SQLite, используяPdoWrapper
. - Валидация: Добавьте проверки на дублирующиеся слаги или пустые вводы.
- Промежуточное ПО: Реализуйте аутентификацию для создания постов.
Заключение
Вы создали простой блог с Flight PHP! Этот гид демонстрирует основные функции, такие как маршрутизация, шаблонизация с помощью Latte и обработка отправок форм — при этом все оставаясь легковесным. Изучите документацию Flight для более сложных функций, чтобы развить ваш блог дальше!
License
Лицензия MIT (MIT)
Авторское право © 2024
@mikecao, @n0nag0n
Настоящим предоставляется разрешение на бесплатное использование любому лицу, получившему копию данного программного обеспечения и сопроводительной документации (далее - "Программное обеспечение"), без ограничений, включая право использовать, копировать, изменять, объединять, публиковать, распространять, подлицензировать и/или продавать копии Программного обеспечения и разрешать лицам, которым предоставляется Программное обеспечение, сделать то же самое, при соблюдении следующих условий:
Вышеприведенное уведомление об авторском праве и это уведомление о разрешении должны быть включены во все копии или существенные части Программного обеспечения.
ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ "КАК ЕСТЬ", БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОГО СОСТОЯНИЯ, ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ И НЕНАРУШЕНИЯ. НИ В КОЕМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО ОТВЕТСТВЕННОСТИ, ВЫТЕКАЮЩЕЙ ИЗ ДОГОВОРА, ДЕЛИКТА ИЛИ ИНАЧЕ, В СВЯЗИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ИЛИ ДРУГИМИ ОБРАЩЕНИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
About
Фреймворк PHP Flight
Flight — это быстрый, простой и расширяемый фреймворк для PHP, созданный для разработчиков, которые хотят быстро выполнять задачи без лишних хлопот. Независимо от того, создаете ли вы классическое веб-приложение, сверхбыстрый API или экспериментируете с последними инструментами на базе ИИ, низкая нагрузка и прямолинейный дизайн Flight делают его идеальным выбором. Flight предназначен для того, чтобы быть легким, но при этом он может справляться с требованиями корпоративной архитектуры.
Почему выбрать Flight?
- Дружественный для начинающих: Flight — отличная отправная точка для новых разработчиков PHP. Его четкая структура и простой синтаксис помогают освоить веб-разработку, не запутавшись в шаблонном коде.
- Любимый профессионалами: Опытные разработчики любят Flight за его гибкость и контроль. Вы можете масштабировать от маленького прототипа до полноценного приложения, не переключаясь на другие фреймворки.
- Дружественный к ИИ: Минимальная нагрузка и чистая архитектура Flight идеально подходят для интеграции инструментов и API ИИ. Если вы создаете умные чатботы, дашборды на базе ИИ или просто экспериментируете, Flight не мешает, позволяя сосредоточиться на главном. В skeleton app уже есть предустановленные инструкции для основных ассистентов ИИ прямо из коробки! Узнайте больше об использовании ИИ с Flight
Обзор в видео
Быстрый старт
Для быстрой базовой установки установите его с помощью Composer:
composer require flightphp/core
Или вы можете скачать ZIP-архив репозитория here. Затем у вас будет базовый файл index.php
, как в следующем примере:
<?php
// если установлен с помощью composer
require 'vendor/autoload.php';
// или если установлен вручную из ZIP-файла
// require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
});
Flight::route('/json', function() {
Flight::json([
'hello' => 'world'
]);
});
Flight::start();
Вот и все! У вас есть базовое приложение Flight. Теперь вы можете запустить этот файл с помощью php -S localhost:8000
и посетить http://localhost:8000
в браузере, чтобы увидеть вывод.
Пример приложения (Skeleton/Boilerplate)
Есть пример приложения, чтобы помочь вам начать проект с Flight. В нем есть структурированная разметка, базовые конфигурации и обработка сценариев Composer прямо из коробки! Посмотрите flightphp/skeleton для готового проекта или посетите страницу examples для вдохновения. Хотите увидеть, как вписывается ИИ? Изучите примеры на базе ИИ.
Установка примера приложения
Очень просто!
# Создайте новый проект
composer create-project flightphp/skeleton my-project/
# Войдите в директорию нового проекта
cd my-project/
# Запустите локальный сервер разработки, чтобы начать сразу!
composer start
Это создаст структуру проекта, настроит необходимые файлы, и вы готовы к работе!
Высокая производительность
Flight — один из самых быстрых фреймворков PHP. Его легкий ядро означает меньше нагрузки и больше скорости — идеально для традиционных приложений и современных проектов на базе ИИ. Вы можете увидеть все тесты производительности на TechEmpower.
Смотрите тест ниже с некоторыми популярными фреймворками PHP.
Framework | Plaintext Reqs/sec | JSON Reqs/sec |
---|---|---|
Flight | 190,421 | 182,491 |
Yii | 145,749 | 131,434 |
Fat-Free | 139,238 | 133,952 |
Slim | 89,588 | 87,348 |
Phalcon | 95,911 | 87,675 |
Symfony | 65,053 | 63,237 |
Lumen | 40,572 | 39,700 |
Laravel | 26,657 | 26,901 |
CodeIgniter | 20,628 | 19,901 |
Flight и ИИ
Интересно, как он работает с ИИ? Узнайте, как Flight облегчает работу с вашим любимым LLM для кодирования!
Сообщество
Мы в Matrix Chat
И в Discord
Вклад
Есть два способа внести вклад в Flight:
- Внести вклад в основной фреймворк, посетив core repository.
- Помочь улучшить документацию! Этот сайт документации размещен на Github. Если вы заметили ошибку или хотите что-то улучшить, отправьте pull request. Мы любим обновления и новые идеи — особенно связанные с ИИ и новыми технологиями!
Требования
Flight требует PHP 7.4 или выше.
Примечание: PHP 7.4 поддерживается, потому что на момент написания (2024) PHP 7.4 является версией по умолчанию для некоторых дистрибутивов Linux с долгосрочной поддержкой. Принудительный переход на PHP >8 вызвал бы проблемы для пользователей. Фреймворк также поддерживает PHP >8.
Лицензия
Flight распространяется под MIT лицензией.
Awesome-plugins/php_cookie
Cookies
overclokk/cookie это простая библиотека для управления куки в вашем приложении.
Установка
Установка проста с помощью composer.
composer require overclokk/cookie
Использование
Использование так же просто, как регистрация нового метода в классе Flight.
use Overclokk\Cookie\Cookie;
/*
* Установите в вашем файле bootstrap или public/index.php
*/
Flight::register('cookie', Cookie::class);
/**
* ExampleController.php
*/
class ExampleController {
public function login() {
// Установить куки
// вам нужно, чтобы это было false, чтобы получить новый экземпляр
// используйте комментарий ниже, если хотите автозаполнение
/** @var \Overclokk\Cookie\Cookie $cookie */
$cookie = Flight::cookie(false);
$cookie->set(
'stay_logged_in', // имя куки
'1', // значение, которое вы хотите установить
86400, // количество секунд, на которое должно длиться куки
'/', // путь, по которому куки будут доступны
'example.com', // домен, на котором будут доступны куки
true, // куки будут передаваться только через безопасное соединение HTTPS
true // куки будут доступны только через протокол HTTP
);
// необязательно, если вы хотите сохранить значения по умолчанию
// и иметь быстрый способ установить куки на длительное время
$cookie->forever('stay_logged_in', '1');
}
public function home() {
// Проверить, есть ли у вас куки
if (Flight::cookie()->has('stay_logged_in')) {
// поместите их в область панели управления, например.
Flight::redirect('/dashboard');
}
}
}
Awesome-plugins/php_encryption
Шифрование PHP
defuse/php-encryption - это библиотека, которая может быть использована для шифрования и дешифрования данных. Начать использование довольно просто для начала шифрования и дешифрования данных. У них есть отличное руководство, которое помогает объяснить основы использования библиотеки, а также важные аспекты безопасности, касающиеся шифрования.
Установка
Установка проста с помощью композитора.
composer require defuse/php-encryption
Настройка
Затем вам нужно сгенерировать ключ шифрования.
vendor/bin/generate-defuse-key
Это выдаст ключ, который вам нужно будет хранить в надежном месте. Вы можете сохранить ключ в вашем файле app/config/config.php
в массиве внизу файла. Хотя это не идеальное место, это хотя бы что-то.
Использование
Теперь, когда у вас есть библиотека и ключ шифрования, вы можете начать шифровать и дешифровать данные.
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
/*
* Set in your bootstrap or public/index.php file
*/
// Метод шифрования
Flight::map('encrypt', function($raw_data) {
$encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});
// Метод дешифрования
Flight::map('decrypt', function($encrypted_data) {
$encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
try {
$raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
// Атака! Загружен неверный ключ или зашифрованный текст был изменен с момента его создания -- либо поврежден в базе данных, либо намеренно изменен Злодеем, пытающимся провести атаку.
// ... обработайте этот случай так, чтобы он подходил для вашего приложения ...
}
return $raw_data;
});
Flight::route('/encrypt', function() {
$encrypted_data = Flight::encrypt('Это секрет');
echo $encrypted_data;
});
Flight::route('/decrypt', function() {
$encrypted_data = '...'; // Получите зашифрованные данные откуда-нибудь
$decrypted_data = Flight::decrypt($encrypted_data);
echo $decrypted_data;
});
Awesome-plugins/php_file_cache
flightphp/cache
Легкий, простой и автономный класс PHP для кэширования в файле, форкнутый из Wruczek/PHP-File-Cache
Преимущества
- Легкий, автономный и простой
- Весь код в одном файле - никаких бесполезных драйверов.
- Безопасный - каждый сгенерированный файл кэша имеет заголовок PHP с die, что делает прямой доступ невозможным, даже если кто-то знает путь и ваш сервер не настроен правильно
- Хорошо документированный и протестированный
- Правильно обрабатывает параллелизм через flock
- Поддерживает PHP 7.4+
- Бесплатный под лицензией MIT
Этот сайт документации использует эту библиотеку для кэширования каждой из страниц!
Нажмите здесь, чтобы просмотреть код.
Установка
Установите через composer:
composer require flightphp/cache
Использование
Использование довольно прямолинейное. Это сохраняет файл кэша в директории кэша.
use flight\Cache;
$app = Flight::app();
// Вы передаете директорию, в которой будет храниться кэш, в конструктор
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {
// Это гарантирует, что кэш используется только в режиме производства
// ENVIRONMENT - это константа, которая устанавливается в вашем файле bootstrap или в другом месте вашего приложения
$cache->setDevMode(ENVIRONMENT === 'development');
});
Получить значение кэша
Вы используете метод get()
для получения закэшированного значения. Если вы хотите удобный метод, который обновит кэш, если он истек, вы можете использовать refreshIfExpired()
.
// Получить экземпляр кэша
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // return data to be cached
}, 10); // 10 секунд
// или
$data = $cache->get('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->set('simple-cache-test', $data, 10); // 10 секунд
}
Сохранить значение кэша
Вы используете метод set()
для сохранения значения в кэше.
Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10 секунд
Удалить значение кэша
Вы используете метод delete()
для удаления значения из кэша.
Flight::cache()->delete('simple-cache-test');
Проверить, существует ли значение кэша
Вы используете метод exists()
для проверки, существует ли значение в кэше.
if(Flight::cache()->exists('simple-cache-test')) {
// do something
}
Очистить кэш
Вы используете метод flush()
для очистки всего кэша.
Flight::cache()->flush();
Извлечь метаданные с кэшем
Если вы хотите извлечь временные метки и другие метаданные о записи кэша, убедитесь, что вы передаете true
в качестве соответствующего параметра.
$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
echo "Refreshing data!" . PHP_EOL;
return date("H:i:s"); // return data to be cached
}, 10, true); // true = return with metadata
// или
$data = $cache->get("simple-cache-meta-test", true); // true = return with metadata
/*
Example cached item retrieved with metadata:
{
"time":1511667506, <-- save unix timestamp
"expire":10, <-- expire time in seconds
"data":"04:38:26", <-- unserialized data
"permanent":false
}
Using metadata, we can, for example, calculate when item was saved or when it expires
We can also access the data itself with the "data" key
*/
$expiresin = ($data["time"] + $data["expire"]) - time(); // get unix timestamp when data expires and subtract current timestamp from it
$cacheddate = $data["data"]; // we access the data itself with the "data" key
echo "Latest cache save: $cacheddate, expires in $expiresin seconds";
Документация
Посетите https://github.com/flightphp/cache, чтобы просмотреть код. Убедитесь, что вы посмотрите папку examples для дополнительных способов использования кэша.
Awesome-plugins/permissions
FlightPHP/Права доступа
Это модуль разрешений, который можно использовать в ваших проектах, если у вас есть несколько ролей в вашем приложении, и каждая роль имеет немного разную функциональность. Этот модуль позволяет определить разрешения для каждой роли, а затем проверить, имеет ли текущий пользователь разрешение на доступ к определенной странице или выполнение определенного действия.
Нажмите сюда для репозитория на GitHub.
Установка
Запустите composer require flightphp/permissions
и вы готовы к работе!
Использование
Сначала вам нужно настроить ваши разрешения, затем сообщить вашему приложению, что означают эти разрешения. В конечном итоге вы проверите ваши разрешения с помощью $Permissions->has()
, ->can()
или is()
. has()
и can()
имеют одинаковую функциональность, но названы по-разному, чтобы сделать ваш код более читаемым.
Базовый пример
Давайте предположим, что у вас есть функция в вашем приложении, которая проверяет, вошел ли пользователь в систему. Вы можете создать объект разрешений следующим образом:
// index.php
require 'vendor/autoload.php';
// некоторый код
// затем у вас вероятно есть что-то, что говорит вам, какая текущая роль у человека
// скорее всего у вас есть что-то, откуда вы извлекаете текущую роль
// из переменной сеанса, которая определяет это
// после входа в систему у кого-то должна быть роль 'guest' или 'public'.
$current_role = 'admin';
// настройка разрешений
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
return $current_role !== 'guest';
});
// Вам вероятно захочется сохранить этот объект где-то в Flight
Flight::set('permission', $permission);
Затем в контроллере где-то вы можете иметь что-то вроде этого.
<?php
// некоторый контроллер
class SomeController {
public function someAction() {
$permission = Flight::get('permission');
if ($permission->has('loggedIn')) {
// сделать что-то
} else {
// сделать что-то другое
}
}
}
Вы также можете использовать это для отслеживания, есть ли у них разрешение на выполнение определенного действия в вашем приложении. Например, если у вас есть способ, как пользователи могут взаимодействовать с публикацией в вашем программном обеспечении, вы можете проверить, имеют ли они разрешение на выполнение определенных действий.
$current_role = 'admin';
// настройка разрешений
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
if($current_role === 'admin') {
$permissions = ['create', 'read', 'update', 'delete'];
} else if($current_role === 'editor') {
$permissions = ['create', 'read', 'update'];
} else if($current_role === 'author') {
$permissions = ['create', 'read'];
} else if($current_role === 'contributor') {
$permissions = ['create'];
} else {
$permissions = [];
}
return $permissions;
});
Flight::set('permission', $permission);
Затем где-то в контроллере...
class PostController {
public function create() {
$permission = Flight::get('permission');
if ($permission->can('post.create')) {
// сделать что-то
} else {
// сделать что-то еще
}
}
}
Внедрение зависимостей
Вы можете внедрять зависимости в замыкание, которое определяет разрешения. Это полезно, если у вас есть какой-то переключатель, идентификатор или любая другая точка данных, которую вы хотите проверить. То же самое работает для вызовов вида Class->Method, за исключением того, что аргументы определяются в методе.
Замыкания
$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
// ... код
});
// в вашем файле контроллера
public function createOrder() {
$MyDependency = Flight::myDependency();
$permission = Flight::get('permission');
if ($permission->can('order.create', $MyDependency)) {
// сделать что-то
} else {
// сделать что-то еще
}
}
Классы
namespace MyApp;
class Permissions {
public function order(string $current_role, MyDependency $MyDependency = null) {
// ... код
}
}
Сокращение для установки разрешений с использованием классов
Вы также можете использовать классы для определения ваших разрешений. Это полезно, если у вас много разрешений, и вы хотите, чтобы ваш код был чистым. Вы можете сделать что-то вроде этого:
<?php
// код инициализации
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');
// myapp/Permissions.php
namespace MyApp;
class Permissions {
public function order(string $current_role, int $user_id) {
// Предположим, что вы это настроили заранее
/** @var \flight\database\PdoWrapper $db */
$db = Flight::db();
$allowed_permissions = [ 'read' ]; // каждый может просматривать заказ
if($current_role === 'manager') {
$allowed_permissions[] = 'create'; // менеджеры могут создавать заказы
}
$some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
if($some_special_toggle_from_db) {
$allowed_permissions[] = 'update'; // если у пользователя есть особый переключатель, он может обновлять заказы
}
if($current_role === 'admin') {
$allowed_permissions[] = 'delete'; // администраторы могут удалять заказы
}
return $allowed_permissions;
}
}
Здесь примечательно то, что есть также сокращение, которое можно использовать (которое также может быть кешировано!!!), где вы просто говорите классу разрешений сопоставить все методы в классе в разрешения. Поэтому, если у вас есть метод с именем order()
и метод с именем company()
, они будут автоматически сопоставлены, и вы сможете просто выполнить $Permissions->has('order.read')
или $Permissions->has('company.read')
, и это сработает. Определение этого очень сложно, так что держитесь здесь со мной. Просто вам нужно сделать это:
Создайте класс разрешений, которые вы хотите сгруппировать вместе.
class MyPermissions {
public function order(string $current_role, int $order_id = 0): array {
// код определения разрешений
return $permissions_array;
}
public function company(string $current_role, int $company_id): array {
// код определения разрешений
return $permissions_array;
}
}
Затем сделайте разрешения обнаруживаемыми с использованием этой библиотеки.
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);
Наконец, вызовите разрешение в вашей кодовой базе, чтобы проверить, разрешено ли пользователю выполнение заданного разрешения.
class SomeController {
public function createOrder() {
if(Flight::get('permissions')->can('order.create') === false) {
die('Вы не можете создать заказ. Извините!');
}
}
}
Кеширование
Для включения кэширования, см. простую библиотеку wruczak/phpfilecache. Пример включения приведен ниже.
// этот $app может быть частью вашего кода, или
// вы можете просто передать null, и он извлечет из Flight::app() в конструкторе
$app = Flight::app();
// Теперь для этого принимается файловое кэширование. Другие могут легко
// быть добавлены в будущем.
$Cache = new Wruczek\PhpFileCache\PhpFileCache;
$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 - это сколько секунд кэшировать это. Оставьте это, чтобы не использовать кэширование
И впереди!
Awesome-plugins/simple_job_queue
Простой очередь задач
Простой очередь задач - это библиотека, которая может использоваться для обработки задач асинхронно. Она может быть использована с beanstalkd, MySQL/MariaDB, SQLite и PostgreSQL.
Установка
composer require n0nag0n/simple-job-queue
Использование
Чтобы это работало, вам нужен способ добавлять задачи в очередь и способ обрабатывать задачи (рабочий процесс). Ниже приведены примеры того, как добавить задачу в очередь и как обработать задачу.
Добавление в Flight
Добавить это в Flight просто, и это делается с помощью метода register()
. Ниже приведен пример того, как добавить это в Flight.
<?php
require 'vendor/autoload.php';
// Замените ['mysql'] на ['beanstalkd'], если хотите использовать beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
// если у вас уже есть соединение PDO на Flight::db();
$Job_Queue->addQueueConnection(Flight::db());
// или если вы используете beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
});
Добавление новой задачи
Когда вы добавляете задачу, вам нужно указать конвейер (очередь). Это сравнимо с каналом в RabbitMQ или трубой в beanstalkd.
<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));
Запуск рабочего процесса
Вот пример файла того, как запустить рабочего процесса.
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Соединение PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);
// или если вы используете beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
$Job_Queue->watchPipeline('send_important_emails');
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
// настройте это так, как вам будет спокойнее (только для очередей базы данных, beanstalkd не нуждается в этом условии)
if(empty($job)) {
usleep(500000);
continue;
}
echo "Обработка {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
} else {
// это убирает его из очереди готовых и помещает в другую очередь, которую можно будет забрать и «ударить» позже.
$Job_Queue->buryJob($job);
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
}
}
Обработка длительных процессов с помощью Supervisord
Supervisord - это система управления процессами, которая обеспечивает постоянную работу ваших процессов рабочих. Вот более полное руководство по настройке его с вашим рабочим процессом Простой очереди задач:
Установка Supervisord
# На Ubuntu/Debian
sudo apt-get install supervisor
# На CentOS/RHEL
sudo yum install supervisor
# На macOS с Homebrew
brew install supervisor
Создание скрипта рабочего процесса
Сначала сохраните ваш код рабочего процесса в отдельный файл PHP:
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Соединение PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);
// Установите конвейер для наблюдения
$Job_Queue->watchPipeline('send_important_emails');
// Запись начала работы рабочего процесса
echo date('Y-m-d H:i:s') . " - Рабочий процесс запущен\n";
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
if(empty($job)) {
usleep(500000); // Спите 0,5 секунды
continue;
}
echo date('Y-m-d H:i:s') . " - Обработка задачи {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
echo date('Y-m-d H:i:s') . " - Задача {$job['id']} успешно завершена\n";
} else {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Задача {$job['id']} не удалась, похоронена\n";
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Исключение при обработке задачи {$job['id']}: {$e->getMessage()}\n";
}
}
Настройка Supervisord
Создайте файл конфигурации для вашего рабочего процесса:
[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d
Основные параметры конфигурации:
command
: Команда для запуска вашего рабочего процессаdirectory
: Рабочая директория для рабочего процессаautostart
: Автоматически запускать при запуске supervisordautorestart
: Автоматически перезапускать, если процесс выходитstartretries
: Количество попыток перезапуска, если он завершится с ошибкойstderr_logfile
/stdout_logfile
: Пути к файлам журналовuser
: Системный пользователь, от имени которого будет запущен процессnumprocs
: Количество экземпляров рабочего процесса для запускаprocess_name
: Форматирование имен для нескольких процессов рабочих
Управление рабочими процессами с помощью Supervisorctl
После создания или изменения конфигурации:
# Перезагрузить конфигурацию супервайзера
sudo supervisorctl reread
sudo supervisorctl update
# Управление конкретными процессами рабочих
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*
Запуск нескольких конвейеров
Для нескольких конвейеров создайте отдельные файлы рабочих процессов и конфигурации:
[program:email_worker]
command=php /path/to/email_worker.php
# ... другие настройки ...
[program:notification_worker]
command=php /path/to/notification_worker.php
# ... другие настройки ...
Мониторинг и журналы
Проверьте журналы для мониторинга активности рабочих процессов:
# Просмотр журналов
sudo tail -f /var/log/simple_job_queue.log
# Проверка состояния
sudo supervisorctl status
Эта настройка гарантирует, что ваши рабочие процессы задач продолжают работать даже после сбоев, перезагрузок сервера или других проблем, делая вашу систему очередей надежной для производственных сред.
Awesome-plugins/n0nag0n_wordpress
Интеграция с WordPress: n0nag0n/wordpress-integration-for-flight-framework
Хотите использовать Flight PHP внутри вашего сайта WordPress? Этот плагин делает это очень простым! С n0nag0n/wordpress-integration-for-flight-framework
вы можете запустить полноценное приложение Flight прямо рядом с вашей установкой WordPress — идеально для создания пользовательских API, микросервисов или даже полноценных приложений, не выходя из комфорта WordPress.
Что он делает?
- Безупречно интегрирует Flight PHP с WordPress
- Направляет запросы либо на Flight, либо на WordPress в зависимости от шаблонов URL
- Организует ваш код с контроллерами, моделями и представлениями (MVC)
- Легко настраивает рекомендуемую структуру папок Flight
- Использует соединение с базой данных WordPress или свою собственную
- Тонко настраивает взаимодействие между Flight и WordPress
- Простой интерфейс администрирования для конфигурации
Установка
- Загрузите папку
flight-integration
в ваш каталог/wp-content/plugins/
. - Активируйте плагин в админ-панели WordPress (меню Плагины).
- Перейдите в Настройки > Flight Framework, чтобы настроить плагин.
- Укажите путь к установке Flight (или используйте Composer для установки Flight).
- Настройте путь к папке вашего приложения и создайте структуру папок (плагин может помочь с этим!).
- Начните создавать ваше приложение Flight!
Примеры использования
Пример базового маршрута
В вашем файле app/config/routes.php
:
Flight::route('GET /api/hello', function() {
Flight::json(['message' => 'Hello World!']);
});
Пример контроллера
Создайте контроллер в app/controllers/ApiController.php
:
namespace app\controllers;
use Flight;
class ApiController {
public function getUsers() {
// Вы можете использовать функции WordPress внутри Flight!
$users = get_users();
$result = [];
foreach($users as $user) {
$result[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email
];
}
Flight::json($result);
}
}
Затем, в вашем routes.php
:
Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);
ЧАВО
В: Мне нужно знать Flight, чтобы использовать этот плагин?
О: Да, это для разработчиков, которые хотят использовать Flight в WordPress. Рекомендуется базовое знание маршрутизации и обработки запросов Flight.
В: Это замедлит мой сайт WordPress?
О: Нет! Плагин обрабатывает только запросы, которые соответствуют вашим маршрутам Flight. Все остальные запросы идут в WordPress как обычно.
В: Могу ли я использовать функции WordPress в своем приложении Flight?
О: Абсолютно! У вас есть полный доступ ко всем функциям, хукам и глобальным переменным WordPress из ваших маршрутов и контроллеров Flight.
В: Как создать пользовательские маршруты?
О: Определите свои маршруты в файле config/routes.php
в папке вашего приложения. Посмотрите образец файла, созданный генератором структуры папок, для примеров.
Журнал изменений
1.0.0
Первоначальный релиз.
Для получения дополнительной информации посетите GitHub repo.
Awesome-plugins/ghost_session
Ghostff/Session
PHP Менеджер сессий (неблокирующий, flash, segment, шифрование сессий). Использует PHP open_ssl для необязательного шифрования/дешифрования данных сессий. Поддерживает File, MySQL, Redis и Memcached.
Нажмите здесь, чтобы просмотреть код.
Установка
Установите с помощью composer.
composer require ghostff/session
Основная конфигурация
Вам не обязательно передавать что-либо для использования настроек по умолчанию для вашей сессии. Вы можете прочитать о дополнительных настройках в Github Readme.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
// одна вещь, которую следует помнить, это то, что вы должны фиксировать свою сессию на каждой загрузке страницы
// или вам нужно будет запустить auto_commit в вашей конфигурации.
Простой пример
Вот простой пример того, как вы можете использовать это.
Flight::route('POST /login', function() {
$session = Flight::session();
// выполните здесь вашу логику входа
// проверьте пароль и т.д.
// если вход успешен
$session->set('is_logged_in', true);
$session->set('user', $user);
// каждый раз, когда вы пишете в сессию, вы должны явно зафиксировать её.
$session->commit();
});
// Эта проверка может быть в логике ограниченной страницы или обернута в промежуточное ПО.
Flight::route('/some-restricted-page', function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
// выполните здесь логику ограниченной страницы
});
// версия с промежуточным ПО
Flight::route('/some-restricted-page', function() {
// обычная логика страницы
})->addMiddleware(function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
});
Более сложный пример
Вот более сложный пример того, как вы можете использовать это.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
// установите пользовательский путь к файлу конфигурации сессии в качестве первого аргумента
// или передайте ему пользовательский массив
$app->register('session', Session::class, [
[
// если вы хотите хранить данные сессии в базе данных (хорошо, если вы хотите что-то вроде функциональности "выйти из всех устройств")
Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class,
Session::CONFIG_ENCRYPT_DATA => true,
Session::CONFIG_SALT_KEY => hash('sha256', 'my-super-S3CR3T-salt'), // пожалуйста, измените это на что-то другое
Session::CONFIG_AUTO_COMMIT => true, // делайте это только если это требует и/или трудно вызвать commit() для вашей сессии.
// кроме того, вы могли бы сделать Flight::after('start', function() { Flight::session()->commit(); });
Session::CONFIG_MYSQL_DS => [
'driver' => 'mysql', # Драйвер базы данных для PDO dns, например (mysql:host=...;dbname=...)
'host' => '127.0.0.1', # Хост базы данных
'db_name' => 'my_app_database', # Имя базы данных
'db_table' => 'sessions', # Таблица базы данных
'db_user' => 'root', # Имя пользователя базы данных
'db_pass' => '', # Пароль базы данных
'persistent_conn'=> false, # Избегайте накладных расходов на установку нового соединения каждый раз, когда скрипту нужно общаться с базой данных, что приводит к более быстрому веб-приложению. НАЙДИТЕ ОБРАТНУЮ СТОРОНУ САМИ
]
]
]);
Помощь! Мои данные сессии не сохраняются!
Вы устанавливаете данные сессии, и они не сохраняются между запросами? Вы, возможно, забыли зафиксировать данные сессии. Вы можете сделать это, вызвав $session->commit()
после установки данных сессии.
Flight::route('POST /login', function() {
$session = Flight::session();
// выполните здесь вашу логику входа
// проверьте пароль и т.д.
// если вход успешен
$session->set('is_logged_in', true);
$session->set('user', $user);
// каждый раз, когда вы пишете в сессию, вы должны явно зафиксировать её.
$session->commit();
});
Другой способ обойти это — при настройке службы сессии установить auto_commit
в true
в вашей конфигурации. Это автоматически зафиксирует данные сессии после каждого запроса.
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
$session->updateConfiguration([
Session::CONFIG_AUTO_COMMIT => true,
]);
}
);
Кроме того, вы могли бы сделать Flight::after('start', function() { Flight::session()->commit(); });
, чтобы зафиксировать данные сессии после каждого запроса.
Документация
Посетите Github Readme для полной документации. Варианты конфигурации хорошо задокументированы в файле default_config.php самом по себе. Код прост в понимании, если вы захотите просмотреть этот пакет самостоятельно.
Awesome-plugins/async
Async
Async — это небольшой пакет для фреймворка Flight, который позволяет запускать приложения Flight внутри асинхронных серверов и рантаймов, таких как Swoole, AdapterMan, ReactPHP, Amp, RoadRunner, Workerman и т.д. Из коробки он включает адаптеры для Swoole и AdapterMan.
Цель: разрабатывать и отлаживать с PHP-FPM (или встроенным сервером) и переключаться на Swoole (или другой асинхронный драйвер) для продакшена с минимальными изменениями.
Требования
- PHP 7.4 или выше
- Фреймворк Flight 3.16.1 или выше
- Расширение Swoole
Установка
Установите через composer:
composer require flightphp/async
Если вы планируете запускать с Swoole, установите расширение:
# используя pecl
pecl install swoole
# или openswoole
pecl install openswoole
# или с помощью менеджера пакетов (пример для Debian/Ubuntu)
sudo apt-get install php-swoole
Быстрый пример Swoole
Ниже приведена минимальная настройка, которая показывает, как поддерживать как PHP-FPM (или встроенный сервер), так и Swoole, используя один и тот же код.
Файлы, которые вам понадобятся в проекте:
- index.php
- swoole_server.php
- SwooleServerDriver.php
index.php
Этот файл представляет собой простой переключатель, который заставляет приложение работать в режиме PHP для разработки.
// index.php
<?php
define('NOT_SWOOLE', true);
include 'swoole_server.php';
swoole_server.php
Этот файл инициализирует ваше приложение Flight и запустит драйвер Swoole, когда NOT_SWOOLE не определен.
// swoole_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = Flight::app();
$app->route('/', function() use ($app) {
$app->json(['hello' => 'world']);
});
if (!defined('NOT_SWOOLE')) {
// Require the SwooleServerDriver class when running in Swoole mode.
require_once __DIR__ . '/SwooleServerDriver.php';
Swoole\Runtime::enableCoroutine();
$Swoole_Server = new SwooleServerDriver('127.0.0.1', 9501, $app);
$Swoole_Server->start();
} else {
$app->start();
}
SwooleServerDriver.php
Краткий драйвер, показывающий, как связывать запросы Swoole с Flight с использованием AsyncBridge и адаптеров Swoole.
// SwooleServerDriver.php
<?php
use flight\adapter\SwooleAsyncRequest;
use flight\adapter\SwooleAsyncResponse;
use flight\AsyncBridge;
use flight\Engine;
use Swoole\HTTP\Server as SwooleServer;
use Swoole\HTTP\Request as SwooleRequest;
use Swoole\HTTP\Response as SwooleResponse;
class SwooleServerDriver {
protected $Swoole;
protected $app;
public function __construct(string $host, int $port, Engine $app) {
$this->Swoole = new SwooleServer($host, $port);
$this->app = $app;
$this->setDefault();
$this->bindWorkerEvents();
$this->bindHttpEvent();
}
protected function setDefault() {
$this->Swoole->set([
'daemonize' => false,
'dispatch_mode' => 1,
'max_request' => 8000,
'open_tcp_nodelay' => true,
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,
'enable_coroutine' => true,
'http_compression' => false,
'enable_static_handler' => true,
'document_root' => __DIR__,
'static_handler_locations' => ['/css', '/js', '/images', '/.well-known'],
'buffer_output_size' => 4 * 1024 * 1024,
'worker_num' => 4,
]);
$app = $this->app;
$app->map('stop', function (?int $code = null) use ($app) {
if ($code !== null) {
$app->response()->status($code);
}
});
}
protected function bindHttpEvent() {
$app = $this->app;
$AsyncBridge = new AsyncBridge($app);
$this->Swoole->on('Start', function(SwooleServer $server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$this->Swoole->on('Request', function (SwooleRequest $request, SwooleResponse $response) use ($AsyncBridge) {
$SwooleAsyncRequest = new SwooleAsyncRequest($request);
$SwooleAsyncResponse = new SwooleAsyncResponse($response);
$AsyncBridge->processRequest($SwooleAsyncRequest, $SwooleAsyncResponse);
$response->end();
gc_collect_cycles();
});
}
protected function bindWorkerEvents() {
$createPools = function() {
// create worker-specific connection pools here
};
$closePools = function() {
// close pools / cleanup here
};
$this->Swoole->on('WorkerStart', $createPools);
$this->Swoole->on('WorkerStop', $closePools);
$this->Swoole->on('WorkerError', $closePools);
}
public function start() {
$this->Swoole->start();
}
}
Запуск сервера
- Разработка (встроенный сервер PHP / PHP-FPM):
- php -S localhost:8000 (или добавьте -t public/, если ваш index находится в public/)
- Продакшен (Swoole):
- php swoole_server.php
Совет: Для продакшена используйте обратный прокси (Nginx) перед Swoole для обработки TLS, статических файлов и балансировки нагрузки.
Заметки по конфигурации
Драйвер Swoole предоставляет несколько опций конфигурации:
- worker_num: количество рабочих процессов
- max_request: запросов на работника перед перезапуском
- enable_coroutine: использование корутин для параллелизма
- buffer_output_size: размер буфера вывода
Настройте эти параметры в соответствии с ресурсами хоста и шаблонами трафика.
Обработка ошибок
AsyncBridge преобразует ошибки Flight в правильные HTTP-ответы. Вы также можете добавить обработку ошибок на уровне маршрута:
$app->route('/*', function() use ($app) {
try {
// route logic
} catch (Exception $e) {
$app->response()->status(500);
$app->json(['error' => $e->getMessage()]);
}
});
AdapterMan и другие рантаймы
AdapterMan поддерживается как альтернативный адаптер рантайма. Пакет спроектирован для адаптивности — добавление или использование других адаптеров в целом следует тому же шаблону: преобразование запроса/ответа сервера в запрос/ответ Flight через AsyncBridge и адаптеры, специфичные для рантайма.
Awesome-plugins/migrations
Миграции
Миграция для вашего проекта отслеживает все изменения базы данных, связанные с вашим проектом. byjg/php-migration — это действительно полезная основная библиотека, с которой вы можете начать.
Установка
PHP библиотека
Если вы хотите использовать только PHP библиотеку в вашем проекте:
composer require "byjg/migration"
Интерфейс командной строки
Интерфейс командной строки является отдельным и не требует установки вместе с вашим проектом.
Вы можете установить его глобально и создать символическую ссылку.
composer require "byjg/migration-cli"
Пожалуйста, посетите byjg/migration-cli, чтобы получить больше информации о Migration CLI.
Поддерживаемые базы данных
База данных | Драйвер | Строка соединения |
---|---|---|
Sqlite | pdo_sqlite | sqlite:///path/to/file |
MySql/MariaDb | pdo_mysql | mysql://username:password@hostname:port/database |
Postgres | pdo_pgsql | pgsql://username:password@hostname:port/database |
Sql Server | pdo_dblib, pdo_sysbase Linux | dblib://username:password@hostname:port/database |
Sql Server | pdo_sqlsrv Windows | sqlsrv://username:password@hostname:port/database |
Как это работает?
Миграция базы данных использует ЧИСТЫЙ SQL для управления версионностью базы данных. Чтобы это заработало, вам необходимо:
- Создать SQL скрипты
- Управлять, используя командную строку или API.
SQL скрипты
Скрипты делятся на три группы:
- БАЗОВЫЙ скрипт содержит ВСЕ sql команды для создания новой базы данных;
- UP скрипты содержат все sql миграционные команды для "повышения" версии базы данных;
- DOWN скрипты содержат все sql миграционные команды для "понижения" или возврата версии базы данных;
Директория скриптов:
<root dir>
|
+-- base.sql
|
+-- /migrations
|
+-- /up
|
+-- 00001.sql
+-- 00002.sql
+-- /down
|
+-- 00000.sql
+-- 00001.sql
- "base.sql" — это базовый скрипт
- Папка "up" содержит скрипты для миграции вверх. Например: 00002.sql — это скрипт для перемещения базы данных с версии '1' на '2'.
- Папка "down" содержит скрипты для миграции вниз. Например: 00001.sql — это скрипт для перемещения базы данных с версии '2' на '1'. Папка "down" является необязательной.
Многоразвивающая среда
Если вы работаете с несколькими разработчиками и несколькими ветками, будет сложно определить, какое число следующее.
В этом случае вы добавляете суффикс "-dev" после номера версии.
Смотрите сценарий:
- Разработчик 1 создает ветку, и самая последняя версия, например, 42.
- Разработчик 2 создаёт ветку одновременно и имеет тот же номер версии базы данных.
В обоих случаях разработчики создадут файл под названием 43-dev.sql. Оба разработчика будут мигрировать UP и DOWN без проблем, и ваша локальная версия будет 43.
Но разработчик 1 объединил ваши изменения и создал окончательную версию 43.sql (git mv 43-dev.sql 43.sql
). Если разработчик 2 обновит свою локальную ветку, он получит файл 43.sql (от dev 1) и ваш файл 43-dev.sql. Если он попытается мигрировать UP или DOWN, скрипт миграции упадет и предупредит его, что есть ДВЕ версии 43. В этом случае разработчик 2 должен будет обновить ваш файл до 44-dev.sql и продолжить работать, пока не объединит ваши изменения и не сгенерирует окончательную версию.
Использование PHP API и интеграция его в ваши проекты
Основное использование:
- Создайте объект ConnectionManagement для соединения. Для получения дополнительной информации смотрите компонент "byjg/anydataset".
- Создайте объект миграции с этим соединением и папкой, в которой находятся sql-скрипты.
- Используйте соответствующую команду для "сброса", "up" или "down" миграционных скриптов.
Смотрите пример:
<?php
// Создайте URI соединения
// Подробнее: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');
// Зарегистрируйте Базу данных или Базы данных, которые могут обрабатывать этот URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Создайте экземпляр миграции
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Добавьте функцию обратного вызова прогресса для получения информации о выполнении
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
echo "$action, $currentVersion, ${fileInfo['description']}\n";
});
// Восстановите базу данных с использованием скрипта "base.sql"
// и выполните ВСЕ существующие скрипты для повышения версии базы данных до последней версии
$migration->reset();
// Выполните ВСЕ существующие скрипты для вверх или вниз версии базы данных
// от текущей версии до номера $version;
// Если номер версии не указан, мигрируйте до последней версии базы данных
$migration->update($version = null);
Объект миграции контролирует версию базы данных.
Создание контроля версий в вашем проекте
<?php
// Зарегистрируйте Базу данных или Базы данных, которые могут обрабатывать этот URI:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Создайте экземпляр миграции
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Эта команда создаст таблицу версий в вашей базе данных
$migration->createVersion();
Получение текущей версии
<?php
$migration->getCurrentVersion();
Добавить обратный вызов для контроля прогресса
<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
echo "Выполняем команду: $command на версии $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});
Получение экземпляра драйвера Db
<?php
$migration->getDbDriver();
Чтобы использовать это, пожалуйста, посетите: https://github.com/byjg/anydataset-db
Избежание частичной миграции (недоступно для MySQL)
Частичная миграция возникает, когда скрипт миграции прерывается в середине процесса из-за ошибки или ручного прерывания.
Таблица миграции будет иметь статус partial up
или partial down
, и ее необходимо исправить вручную перед тем, как снова мигрировать.
Чтобы избежать этой ситуации, вы можете указать, что миграция будет выполняться в транзакционном контексте. Если скрипт миграции не удастся выполнить, транзакция будет отменена, а таблица миграции будет отмечена как complete
, и версия будет сразу же предыдущей версией перед скриптом, который вызвал ошибку.
Чтобы включить эту функцию, вам необходимо вызвать метод withTransactionEnabled
, передав true
как параметр:
<?php
$migration->withTransactionEnabled(true);
ПРИМЕЧАНИЕ: Эта функция недоступна для MySQL, так как она не поддерживает DDL команды внутри транзакции. Если вы используете этот метод с MySQL, миграция проигнорирует его без уведомления. Дополнительная информация: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html
Советы по написанию SQL миграций для Postgres
О создании триггеров и SQL функций
-- ДЕЛАЙТЕ
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Проверьте, что empname и зарплата указаны
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname не может быть пустым'; -- не имеет значения, пустые ли эти комментарии
END IF; --
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% не может иметь пустую зарплату', NEW.empname; --
END IF; --
-- Кто работает на нас, когда они должны за это платить?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% не может иметь отрицательную зарплату', NEW.empname; --
END IF; --
-- Запомните, кто изменял зарплату, когда
NEW.last_date := current_timestamp; --
NEW.last_user := current_user; --
RETURN NEW; --
END; --
$emp_stamp$ LANGUAGE plpgsql;
-- НЕ ДЕЛАЙТЕ
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Проверьте, что empname и зарплата указаны
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname не может быть пустым';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% не может иметь пустую зарплату', NEW.empname;
END IF;
-- Кто работает на нас, когда они должны за это платить?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% не может иметь отрицательную зарплату', NEW.empname;
END IF;
-- Запомните, кто изменял зарплату, когда
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
Поскольку уровень абстракции базы данных PDO
не может выполнять пачки SQL операторов, когда byjg/migration
читает файл миграции, он должен разбить все содержимое SQL файла по точкам с запятой и выполнять операторы один за другим. Однако существует один вид оператора, который может содержать несколько точек с запятой между его телом: функции.
Чтобы иметь возможность корректно парсить функции, byjg/migration
2.1.0 начал разбивать файлы миграции по последовательности точка с запятой + EOL
вместо обычной точки с запятой. Таким образом, если вы добавите пустой комментарий после каждой внутренней точки с запятой в определении функции, byjg/migration
сможет корректно его разобрать.
К сожалению, если вы забудете добавить хотя бы один из этих комментариев, библиотека разобьет оператор CREATE FUNCTION
на несколько частей, и миграция потерпит неудачу.
Избегайте символа двоеточия (:
)
-- ДЕЛАЙТЕ
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
check_in DATE NOT NULL
);
-- НЕ ДЕЛАЙТЕ
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
check_in DATE NOT NULL
);
Поскольку PDO
использует двоеточие, чтобы обозначать именованные параметры в подготовленных операторов, его использование вызовет проблемы в других контекстах.
Например, операторы PostgreSQL могут использовать ::
для приведения значений между типами. С другой стороны, PDO
воспримет это как недопустимый именованный параметр в недопустимом контексте и выдаст ошибку, когда попытается его выполнить.
Единственный способ исправить это несоответствие — это полностью избегать двоеточий (в этом случае у PostgreSQL также есть альтернативный синтаксис: CAST(value AS type)
).
Используйте SQL редактор
Наконец, написание ручных SQL миграций может быть утомительным, но это значительно проще, если вы используете редактор, способный понимать синтаксис SQL, предоставляющий автозавершение, интуитивно исследующий вашу текущую схему базы данных и/или автоматически форматирующий ваш код.
Обработка различных миграций внутри одной схемы
Если вам нужно создать различные миграционные скрипты и версии в одной схеме, это возможно, но слишком рискованно, и я категорически не рекомендую это.
Для этого вам нужно создать разные "миграционные таблицы", передавая параметр в конструктор.
<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");
По соображениям безопасности эта функция недоступна в командной строке, но вы можете использовать переменную среды MIGRATION_VERSION
, чтобы сохранить имя.
Мы действительно рекомендуем не использовать эту функцию. Рекомендация — одна миграция для одной схемы.
Запуск модульных тестов
Основные модульные тесты можно запустить с помощью:
vendor/bin/phpunit
Запуск тестов базы данных
Запуск интеграционных тестов требует, чтобы базы данных были запущены и работали. Мы предоставили базовый docker-compose.yml
, и вы можете использовать его для запуска баз данных для тестов.
Запуск баз данных
docker-compose up -d postgres mysql mssql
Запуск тестов
vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*
Опционально вы можете установить хост и пароль, используемые модульными тестами.
export MYSQL_TEST_HOST=localhost # по умолчанию localhost
export MYSQL_PASSWORD=newpassword # используйте '.' если хотите иметь пустой пароль
export PSQL_TEST_HOST=localhost # по умолчанию localhost
export PSQL_PASSWORD=newpassword # используйте '.' если хотите иметь пустой пароль
export MSSQL_TEST_HOST=localhost # по умолчанию localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db # по умолчанию /tmp/test.db
Awesome-plugins/session
FlightPHP Session - Легковесный обработчик сессий на основе файлов
Это легковесный плагин для обработки сессий на основе файлов для Flight PHP Framework. Он предоставляет простое, но мощное решение для управления сессиями, с функциями, такими как неблокирующее чтение сессий, необязательное шифрование, автоматическая фиксация изменений и режим тестирования для разработки. Данные сессий хранятся в файлах, что идеально подходит для приложений, не требующих базы данных.
Если вы хотите использовать базу данных, ознакомьтесь с плагином ghostff/session, который имеет многие из этих функций, но с использованием базы данных.
Посетите репозиторий на Github для полного исходного кода и деталей.
Установка
Установите плагин через Composer:
composer require flightphp/session
Основное использование
Вот простой пример использования плагина flightphp/session
в вашем приложении Flight:
require 'vendor/autoload.php';
use flight\Session;
$app = Flight::app();
// Регистрация сервиса сессий
$app->register('session', Session::class);
// Пример маршрута с использованием сессий
Flight::route('/login', function() {
$session = Flight::session();
$session->set('user_id', 123);
$session->set('username', 'johndoe');
$session->set('is_admin', false);
echo $session->get('username'); // Выводит: johndoe
echo $session->get('preferences', 'default_theme'); // Выводит: default_theme
if ($session->get('user_id')) {
Flight::json(['message' => 'Пользователь вошел в систему!', 'user_id' => $session->get('user_id')]);
}
});
Flight::route('/logout', function() {
$session = Flight::session();
$session->clear(); // Очистить все данные сессии
Flight::json(['message' => 'Выход выполнен успешно']);
});
Flight::start();
Ключевые аспекты
- Неблокирующее: По умолчанию использует
read_and_close
для запуска сессии, предотвращая проблемы с блокировкой сессий. - Автоматическая фиксация: Включена по умолчанию, поэтому изменения сохраняются автоматически при завершении, если не отключена.
- Хранение в файлах: Сессии хранятся в каталоге временных файлов системы в
/flight_sessions
по умолчанию.
Конфигурация
Вы можете настроить обработчик сессий, передавая массив опций при регистрации:
// Да, это двойной массив :)
$app->register('session', Session::class, [ [
'save_path' => '/custom/path/to/sessions', // Каталог для файлов сессий
'prefix' => 'myapp_', // Префикс для файлов сессий
'encryption_key' => 'a-secure-32-byte-key-here', // Включить шифрование (рекомендуется 32 байта для AES-256-CBC)
'auto_commit' => false, // Отключить автоматическую фиксацию для ручного контроля
'start_session' => true, // Автоматически запускать сессию (по умолчанию: true)
'test_mode' => false, // Включить режим тестирования для разработки
'serialization' => 'json', // Метод сериализации: 'json' (по умолчанию) или 'php' (устаревший)
] ]);
Опции конфигурации
Опция | Описание | Значение по умолчанию |
---|---|---|
save_path |
Каталог, где хранятся файлы сессий | sys_get_temp_dir() . '/flight_sessions' |
prefix |
Префикс для сохраненного файла сессии | sess_ |
encryption_key |
Ключ для шифрования AES-256-CBC (необязательно) | null (без шифрования) |
auto_commit |
Автоматическое сохранение данных сессии при завершении | true |
start_session |
Автоматически запускать сессию | true |
test_mode |
Запуск в режиме тестирования без влияния на сессии PHP | false |
test_session_id |
Кастомный ID сессии для режима тестирования (необязательно) | Генерируется случайно, если не указано |
serialization |
Метод сериализации: 'json' (по умолчанию, безопасный) или 'php' (устаревший, позволяет объекты) | 'json' |
Режимы сериализации
По умолчанию эта библиотека использует сериализацию JSON для данных сессий, что безопасно и предотвращает уязвимости, связанные с внедрением объектов PHP. Если вам нужно хранить объекты PHP в сессии (не рекомендуется для большинства приложений), вы можете выбрать устаревшую сериализацию PHP:
'serialization' => 'json'
(по умолчанию):- Допускаются только массивы и примитивы в данных сессий.
- Безопаснее: устойчив к внедрению объектов PHP.
- Файлы имеют префикс
J
(простой JSON) илиF
(зашифрованный JSON).
'serialization' => 'php'
:- Позволяет хранить объекты PHP (используйте с осторожностью).
- Файлы имеют префикс
P
(простая сериализация PHP) илиE
(зашифрованная сериализация PHP).
Примечание: Если вы используете сериализацию JSON, попытка сохранить объект вызовет исключение.
Продвинутое использование
Ручная фиксация
Если вы отключите автоматическую фиксацию, вам нужно вручную фиксировать изменения:
$app->register('session', Session::class, ['auto_commit' => false]);
Flight::route('/update', function() {
$session = Flight::session();
$session->set('key', 'value');
$session->commit(); // Явно сохранить изменения
});
Безопасность сессий с шифрованием
Включите шифрование для конфиденциальных данных:
$app->register('session', Session::class, [
'encryption_key' => 'your-32-byte-secret-key-here'
]);
Flight::route('/secure', function() {
$session = Flight::session();
$session->set('credit_card', '4111-1111-1111-1111'); // Шифруется автоматически
echo $session->get('credit_card'); // Дешифруется при извлечении
});
Регенерация сессии
Регенерируйте ID сессии для безопасности (например, после входа):
Flight::route('/post-login', function() {
$session = Flight::session();
$session->regenerate(); // Новый ID, сохранить данные
// ИЛИ
$session->regenerate(true); // Новый ID, удалить старые данные
});
Пример промежуточного ПО
Защитите маршруты с помощью аутентификации на основе сессий:
Flight::route('/admin', function() {
Flight::json(['message' => 'Добро пожаловать в панель администратора']);
})->addMiddleware(function() {
$session = Flight::session();
if (!$session->get('is_admin')) {
Flight::halt(403, 'Доступ запрещен');
}
});
Это простой пример использования в промежуточном ПО. Для более подробного примера см. документацию по middleware.
Методы
Класс Session
предоставляет эти методы:
set(string $key, $value)
: Сохраняет значение в сессии.get(string $key, $default = null)
: Извлекает значение, с необязательным значением по умолчанию, если ключ не существует.delete(string $key)
: Удаляет конкретный ключ из сессии.clear()
: Удаляет все данные сессии, но сохраняет то же имя файла для сессии.commit()
: Сохраняет текущие данные сессии в файловую систему.id()
: Возвращает текущий ID сессии.regenerate(bool $deleteOldFile = false)
: Регенерирует ID сессии, включая создание нового файла сессии, сохраняя все старые данные, а старый файл остается в системе. Если$deleteOldFile
равенtrue
, старый файл сессии удаляется.destroy(string $id)
: Уничтожает сессию по ID и удаляет файл сессии из системы. Это частьSessionHandlerInterface
, и$id
обязателен. Типичное использование:$session->destroy($session->id())
.getAll()
: Возвращает все данные из текущей сессии.
Все методы, кроме get()
и id()
, возвращают экземпляр Session
для цепочки вызовов.
Почему использовать этот плагин?
- Легковесный: Нет внешних зависимостей — только файлы.
- Неблокирующее: Избегает блокировки сессий с
read_and_close
по умолчанию. - Безопасный: Поддерживает шифрование AES-256-CBC для конфиденциальных данных.
- Гибкий: Опции автоматической фиксации, режима тестирования и ручного контроля.
- Flight-Native: Разработан специально для фреймворка Flight.
Технические детали
- Формат хранения: Файлы сессий имеют префикс
sess_
и хранятся в настроенномsave_path
. Префиксы содержимого файлов:J
: Простой JSON (по умолчанию, без шифрования)F
: Зашифрованный JSON (по умолчанию с шифрованием)P
: Простая сериализация PHP (устаревшая, без шифрования)E
: Зашифрованная сериализация PHP (устаревшая с шифрованием)
- Шифрование: Использует AES-256-CBC с случайным IV на каждую запись сессии, когда предоставлен
encryption_key
. Шифрование работает как для режимов JSON, так и для PHP. - Сериализация: JSON — это режим по умолчанию и самый безопасный метод. Сериализация PHP доступна для устаревшего/продвинутого использования, но менее безопасна.
- Сбор мусора: Реализует
SessionHandlerInterface::gc()
для очистки истекших сессий.
Вклад
Вклад приветствуется! Создайте форк репозитория, внесите изменения и отправьте пул-реквест. Сообщайте об ошибках или предлагайте функции через трекер задач на Github.
Лицензия
Этот плагин лицензирован по лицензии MIT. См. репозиторий на Github для деталей.
Awesome-plugins/runway
Взлетная полоса
Взлетная полоса — это приложение CLI, которое помогает управлять приложениями Flight. Он может генерировать контроллеры, отображать все маршруты и многое другое. Он основан на отличной библиотеке adhocore/php-cli.
Нажмите здесь, чтобы просмотреть код.
Установка
Установите через composer.
composer require flightphp/runway
Основная настройка
Первый раз, когда вы запускаете Взлетную полосу, она проведет вас через процесс настройки и создаст файл конфигурации .runway.json
в корне вашего проекта. Этот файл будет содержать несколько необходимых конфигураций для работы Взлетной полосы должным образом.
Использование
У Взлетной полосы есть несколько команд, которые вы можете использовать для управления вашим приложением Flight. Есть два простых способа использования Взлетной полосы.
- Если вы используете каркас проекта, вы можете запустить
php runway [команда]
из корня вашего проекта. - Если вы используете Взлетную полосу как пакет, установленный через composer, вы можете запустить
vendor/bin/runway [команда]
из корня вашего проекта.
Для любой команды вы можете передать флаг --help
, чтобы получить больше информации о том, как использовать команду.
php runway routes --help
Вот несколько примеров:
Создание контроллера
На основе конфигурации в вашем файле .runway.json
по умолчанию контроллер будет создан для вас в каталоге app/controllers/
.
php runway make:controller MyController
Создание модели Active Record
На основе конфигурации в вашем файле .runway.json
по умолчанию модель будет создана для вас в каталоге app/records/
.
php runway make:record users
Если у вас, например, есть таблица users
с такой схемой: id
, name
, email
, created_at
, updated_at
, будет создан файл, подобный следующему, в файле app/records/UserRecord.php
:
<?php
declare(strict_types=1);
namespace app\records;
/**
* Класс Active Record для таблицы пользователей.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $name
* @property string $email
* @property string $created_at
* @property string $updated_at
* // здесь вы также можете добавить отношения после их определения в массиве $relations
* @property CompanyRecord $company Пример отношения
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Установите отношения для модели
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [];
/**
* Конструктор
* @param mixed $databaseConnection Соединение с базой данных
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
Отображение всех маршрутов
Это отобразит все маршруты, которые в настоящее время зарегистрированы в Flight.
php runway routes
Если вы хотите просмотреть только определенные маршруты, вы можете передать флаг для фильтрации маршрутов.
# Отобразить только GET маршруты
php runway routes --get
# Отобразить только POST маршруты
php runway routes --post
# и т.д.
Настройка Взлетной полосы
Если вы создаете пакет для Flight или хотите добавить свои собственные команды в свой проект, вы можете сделать это, создав каталог src/commands/
, flight/commands/
, app/commands/
или commands/
для вашего проекта/пакета.
Для создания команды просто расширьте класс AbstractBaseCommand
и реализуйте, как минимум, метод __construct
и метод execute
.
<?php
declare(strict_types=1);
namespace flight\commands;
class ExampleCommand extends AbstractBaseCommand
{
/**
* Конструктор
*
* @param array<string,mixed> $config JSON конфигурация из .runway-config.json
*/
public function __construct(array $config)
{
parent::__construct('make:example', 'Создать пример для документации', $config);
$this->argument('<funny-gif>', 'Имя смешной гифки');
}
/**
* Выполняет функцию
*
* @return void
*/
public function execute(string $controller)
{
$io = $this->app()->io();
$io->info('Создание примера...');
// Сделайте здесь что-то
$io->ok('Пример создан!');
}
}
Смотрите Документация adhocore/php-cli для получения дополнительной информации о том, как создавать свои собственные команды в вашем приложении Flight!
Awesome-plugins/tracy_extensions
Расширения панели Tracy для Flight
=====
Это набор расширений, чтобы сделать работу с Flight немного богаче.
- Flight - Анализ всех переменных Flight.
- Database - Анализ всех запросов, выполненных на странице (если вы правильно инициализируете подключение к базе данных)
- Request - Анализ всех переменных
$_SERVER
и осмотр всех глобальных полезных нагрузок ($_GET
,$_POST
,$_FILES
) - Session - Анализ всех переменных
$_SESSION
, если сессии активны.
Это панель
И каждая панель отображает очень полезную информацию о вашем приложении!
Нажмите здесь, чтобы просмотреть код.
Установка
Выполните composer require flightphp/tracy-extensions --dev
, и вы на пути!
Конфигурация
Вам нужно выполнить очень мало конфигурации, чтобы начать. Вы должны инициализировать отладчик Tracy перед использованием этого https://tracy.nette.org/en/guide:
<?php
use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;
// bootstrap code
require __DIR__ . '/vendor/autoload.php';
Debugger::enable();
// Возможно, вам нужно указать вашу среду с помощью Debugger::enable(Debugger::DEVELOPMENT)
// если вы используете подключения к базе данных в вашем приложении, есть
// обязательная обертка PDO, которую нужно использовать ТОЛЬКО В РАЗРАБОТКЕ (не в продакшене, пожалуйста!)
// Она имеет те же параметры, что и обычное подключение PDO
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// или если вы подключаете это к фреймворку Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// теперь каждый раз, когда вы выполняете запрос, он захватит время, запрос и параметры
// Это соединяет точки
if(Debugger::$showBar === true) {
// Это должно быть false, иначе Tracy не сможет отобразиться :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
// more code
Flight::start();
Дополнительная конфигурация
Данные сессии
Если у вас есть пользовательский обработчик сессий (например, ghostff/session), вы можете передать любой массив данных сессии в Tracy, и он автоматически выведет его для вас. Вы передаете его с помощью ключа session_data
во втором параметре конструктора TracyExtensionLoader
.
use Ghostff\Session\Session;
// или используйте flight\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() ]);
}
// routes and other things...
Flight::start();
Latte
Требуется PHP 8.1+ для этого раздела.
Если у вас установлен Latte в вашем проекте, Tracy имеет нативную интеграцию с Latte для анализа ваших шаблонов. Вы просто регистрируете расширение с вашим экземпляром Latte.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function($template, $data, $block = null) {
$latte = new Latte\Engine;
// other configurations...
// добавляйте расширение только если панель отладки Tracy включена
if(Debugger::$showBar === true) {
// здесь вы добавляете панель Latte в Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($template, $data, $block);
});
Awesome-plugins/apm
Документация FlightPHP APM
Добро пожаловать в FlightPHP APM — ваш личный тренер по производительности приложения! Это руководство — ваша дорожная карта по настройке, использованию и освоению мониторинга производительности приложений (APM) с FlightPHP. Будь вы охотником за медленными запросами или просто хотите погрузиться в графики задержек, мы вас прикроем. Давайте сделаем ваше приложение быстрее, ваших пользователей счастливее, а сессии отладки — легкими как ветер!
Посмотрите демо панели управления для сайта Flight Docs.
Почему APM важен
Представьте: ваше приложение — это оживленный ресторан. Без способа отслеживать, сколько времени занимают заказы или где кухня тормозит, вы угадываете, почему клиенты уходят недовольными. APM — ваш су-шеф: он следит за каждым шагом, от входящих запросов до запросов к базе данных, и отмечает всё, что вас замедляет. Медленные страницы отпугивают пользователей (исследования говорят, что 53% уходят, если сайт загружается дольше 3 секунд!), а APM помогает поймать эти проблемы до того, как они укусят. Это проактивное спокойствие — меньше моментов «почему это сломано?», больше побед «смотрите, как круто это работает!».
Установка
Начните с Composer:
composer require flightphp/apm
Вам понадобится:
- PHP 7.4+: Обеспечивает совместимость с LTS-дистрибутивами Linux, поддерживая современный PHP.
- FlightPHP Core v3.15+: Легковесный фреймворк, который мы усиливаем.
Поддерживаемые базы данных
FlightPHP APM в настоящее время поддерживает следующие базы данных для хранения метрик:
- SQLite3: Простая, на основе файлов, идеальна для локальной разработки или небольших приложений. Вариант по умолчанию в большинстве настроек.
- MySQL/MariaDB: Идеально для крупных проектов или производственных сред, где нужна надежная, масштабируемая хранилище.
Вы можете выбрать тип базы данных на шаге конфигурации (см. ниже). Убедитесь, что в вашей среде PHP установлены необходимые расширения (например, pdo_sqlite
или pdo_mysql
).
Начало работы
Вот ваш пошаговый путь к крутости APM:
1. Регистрация APM
Вставьте это в ваш index.php
или файл services.php
, чтобы начать отслеживание:
use flight\apm\logger\LoggerFactory;
use flight\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// Если вы добавляете подключение к базе данных
// Должно быть PdoWrapper или PdoQueryCapture из расширений Tracy
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True обязательно для включения отслеживания в APM.
$Apm->addPdoConnection($pdo);
Что здесь происходит?
LoggerFactory::create()
берет вашу конфигурацию (подробнее об этом скоро) и настраивает логгер — по умолчанию SQLite.Apm
— звезда: он слушает события Flight (запросы, маршруты, ошибки и т.д.) и собирает метрики.bindEventsToFlightInstance($app)
связывает всё с вашим приложением Flight.
Про-совет: Сэмплинг Если ваше приложение загружено, логирование каждого запроса может перегрузить систему. Используйте коэффициент сэмплинга (от 0.0 до 1.0):
$Apm = new Apm($ApmLogger, 0.1); // Логирует 10% запросов
Это сохраняет производительность высокой, при этом давая надежные данные.
2. Настройка
Запустите это, чтобы создать .runway-config.json
:
php vendor/bin/runway apm:init
Что это делает?
- Запускает мастер, спрашивающий, откуда берутся сырые метрики (источник) и куда идут обработанные данные (назначение).
- По умолчанию SQLite — например,
sqlite:/tmp/apm_metrics.sqlite
для источника, другой для назначения. - В итоге вы получите конфигурацию вроде:
{ "apm": { "source_type": "sqlite", "source_db_dsn": "sqlite:/tmp/apm_metrics.sqlite", "storage_type": "sqlite", "dest_db_dsn": "sqlite:/tmp/apm_metrics_processed.sqlite" } }
Этот процесс также спросит, хотите ли вы запустить миграции для этой настройки. Если вы настраиваете это впервые, ответ — да.
Почему два места? Сырые метрики накапливаются быстро (представьте нефильтрованные логи). Воркер обрабатывает их в структурированное назначение для панели. Держит всё в порядке!
3. Обработка метрик с помощью воркера
Воркер превращает сырые метрики в данные, готовые для панели. Запустите его один раз:
php vendor/bin/runway apm:worker
Что он делает?
- Читает из вашего источника (например,
apm_metrics.sqlite
). - Обрабатывает до 100 метрик (размер пакета по умолчанию) в ваше назначение.
- Останавливается, когда закончит или если метрик больше нет.
Держите его запущенным Для живых приложений вам понадобится непрерывная обработка. Вот ваши варианты:
-
Режим демона:
php vendor/bin/runway apm:worker --daemon
Работает вечно, обрабатывая метрики по мере поступления. Отлично для разработки или небольших настроек.
-
Crontab: Добавьте это в ваш crontab (
crontab -e
):* * * * * php /path/to/project/vendor/bin/runway apm:worker
Запускается каждую минуту — идеально для производства.
-
Tmux/Screen: Начните отсоединяемую сессию:
tmux new -s apm-worker php vendor/bin/runway apm:worker --daemon # Ctrl+B, затем D для отсоединения; `tmux attach -t apm-worker` для переподключения
Держит его запущенным даже при выходе из системы.
-
Пользовательские настройки:
php vendor/bin/runway apm:worker --batch_size 50 --max_messages 1000 --timeout 300
--batch_size 50
: Обрабатывает 50 метрик за раз.--max_messages 1000
: Останавливается после 1000 метрик.--timeout 300
: Выходит через 5 минут.
Зачем это нужно? Без воркера ваша панель пуста. Это мост между сырыми логами и полезными инсайтами.
4. Запуск панели
Посмотрите жизненные показатели вашего приложения:
php vendor/bin/runway apm:dashboard
Что это?
- Запускает PHP-сервер на
http://localhost:8001/apm/dashboard
. - Показывает логи запросов, медленные маршруты, уровень ошибок и многое другое.
Настройте это:
php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php
--host 0.0.0.0
: Доступно с любого IP (удобно для удаленного просмотра).--port 8080
: Используйте другой порт, если 8001 занят.--php-path
: Укажите путь к PHP, если он не в PATH.
Откройте URL в браузере и исследуйте!
Режим производства
Для производства вам может потребоваться несколько техник, чтобы запустить панель, поскольку, вероятно, есть файрволы и другие меры безопасности. Вот несколько вариантов:
- Используйте обратный прокси: Настройте Nginx или Apache для перенаправления запросов на панель.
- SSH-туннель: Если вы можете SSH на сервер, используйте
ssh -L 8080:localhost:8001 youruser@yourserver
, чтобы туннелировать панель на вашу локальную машину. - VPN: Если ваш сервер за VPN, подключитесь к нему и получите доступ к панели напрямую.
- Настройте файрвол: Откройте порт 8001 для вашего IP или сети сервера (или любого порта, который вы установили).
- Настройте Apache/Nginx: Если у вас есть веб-сервер перед приложением, вы можете настроить его на домен или поддомен. Если вы это сделаете, установите корень документов в
/path/to/your/project/vendor/flightphp/apm/dashboard
.
Хотите другую панель?
Вы можете построить свою собственную панель, если хотите! Посмотрите директорию vendor/flightphp/apm/src/apm/presenter для идей, как представить данные для вашей собственной панели!
Возможности панели
Панель — ваш штаб APM: вот что вы увидите:
- Журнал запросов: Каждый запрос с временной меткой, URL, кодом ответа и общим временем. Нажмите «Детали» для middleware, запросов и ошибок.
- Самые медленные запросы: Топ-5 запросов, поглощающих время (например, «/api/heavy» за 2.5с).
- Самые медленные маршруты: Топ-5 маршрутов по среднему времени — отлично для выявления паттернов.
- Уровень ошибок: Процент неудачных запросов (например, 2.3% 500-х).
- Перцентили задержек: 95-й (p95) и 99-й (p99) времена ответа — знайте худшие сценарии.
- График кодов ответа: Визуализируйте 200-е, 404-е, 500-е со временем.
- Длинные запросы/Middleware: Топ-5 медленных вызовов базы данных и слоев middleware.
- Попадания/промахи кэша: Как часто кэш спасает день.
Дополнительно:
- Фильтруйте по «Последний час», «Последний день» или «Последняя неделя».
- Переключайте темный режим для поздних ночных сессий.
Пример:
Запрос к /users
может показать:
- Общее время: 150мс
- Middleware:
AuthMiddleware->handle
(50мс) - Запрос:
SELECT * FROM users
(80мс) - Кэш: Попадание в
user_list
(5мс)
Добавление пользовательских событий
Отслеживайте что угодно — например, вызов API или процесс оплаты:
use flight\apm\CustomEvent;
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('api_call', [
'endpoint' => 'https://api.example.com/users',
'response_time' => 0.25,
'status' => 200
]));
Где это появится? В деталях запроса панели под «Пользовательские события» — расширяемо с красивым форматированием JSON.
Пример использования:
$start = microtime(true);
$apiResponse = file_get_contents('https://api.example.com/data');
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('external_api', [
'url' => 'https://api.example.com/data',
'time' => microtime(true) - $start,
'success' => $apiResponse !== false
]));
Теперь вы увидите, тянет ли этот API ваше приложение вниз!
Мониторинг базы данных
Отслеживайте PDO-запросы так:
use flight\database\PdoWrapper;
$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True обязательно для включения отслеживания в APM.
$Apm->addPdoConnection($pdo);
Что вы получаете:
- Текст запроса (например,
SELECT * FROM users WHERE id = ?
) - Время выполнения (например, 0.015с)
- Количество строк (например, 42)
Внимание:
- Опционально: Пропустите это, если не нужно отслеживание БД.
- Только PdoWrapper: Основной PDO еще не подключен — следите за обновлениями!
- Предупреждение о производительности: Логирование каждого запроса на сайте с тяжелой БД может замедлить вещи. Используйте сэмплинг (
$Apm = new Apm($ApmLogger, 0.1)
), чтобы облегчить нагрузку.
Пример вывода:
- Запрос:
SELECT name FROM products WHERE price > 100
- Время: 0.023с
- Строки: 15
Опции воркера
Настройте воркер по вкусу:
--timeout 300
: Останавливается через 5 минут — хорошо для тестирования.--max_messages 500
: Ограничивает 500 метриками — держит конечным.--batch_size 200
: Обрабатывает 200 за раз — балансирует скорость и память.--daemon
: Работает без остановки — идеально для живого мониторинга.
Пример:
php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600
Работает час, обрабатывая 100 метрик за раз.
ID запроса в приложении
Каждый запрос имеет уникальный ID запроса для отслеживания. Вы можете использовать этот ID в вашем приложении для корреляции логов и метрик. Например, вы можете добавить ID запроса на страницу ошибок:
Flight::map('error', function($message) {
// Получите ID запроса из заголовка ответа X-Flight-Request-Id
$requestId = Flight::response()->getHeader('X-Flight-Request-Id');
// Кроме того, вы могли бы получить его из переменной Flight
// Этот метод не будет работать хорошо в swoole или других асинхронных платформах.
// $requestId = Flight::get('apm.request_id');
echo "Error: $message (Request ID: $requestId)";
});
Обновление
Если вы обновляетесь до новой версии APM, возможно, потребуется запустить миграции базы данных. Вы можете сделать это, запустив следующую команду:
php vendor/bin/runway apm:migrate
Это запустит любые необходимые миграции для обновления схемы базы данных до последней версии.
Примечание: Если ваша база данных APM большая по размеру, эти миграции могут занять некоторое время. Вы можете захотеть запустить эту команду в непиковые часы.
Очистка старых данных
Чтобы держать базу данных в порядке, вы можете очистить старые данные. Это особенно полезно, если вы запускаете загруженное приложение и хотите держать размер базы управляемым. Вы можете сделать это, запустив следующую команду:
php vendor/bin/runway apm:purge
Это удалит все данные старше 30 дней из базы данных. Вы можете скорректировать количество дней, передав другое значение опции --days
:
php vendor/bin/runway apm:purge --days 7
Это удалит все данные старше 7 дней из базы данных.
Устранение неисправностей
Застряли? Попробуйте эти:
-
Нет данных в панели?
- Запущен ли воркер? Проверьте
ps aux | grep apm:worker
. - Пути конфигурации совпадают? Проверьте, что DSN в
.runway-config.json
указывают на реальные файлы. - Запустите
php vendor/bin/runway apm:worker
вручную для обработки ожидающих метрик.
- Запущен ли воркер? Проверьте
-
Ошибки воркера?
- Загляните в ваши файлы SQLite (например,
sqlite3 /tmp/apm_metrics.sqlite "SELECT * FROM apm_metrics_log LIMIT 5"
). - Проверьте логи PHP на наличие стек-трейсов.
- Загляните в ваши файлы SQLite (например,
-
Панель не запускается?
- Порт 8001 занят? Используйте
--port 8080
. - PHP не найден? Используйте
--php-path /usr/bin/php
. - Файрвол блокирует? Откройте порт или используйте
--host localhost
.
- Порт 8001 занят? Используйте
-
Слишком медленно?
- Уменьшите коэффициент сэмплинга:
$Apm = new Apm($ApmLogger, 0.05)
(5%). - Уменьшите размер пакета:
--batch_size 20
.
- Уменьшите коэффициент сэмплинга:
-
Не отслеживает исключения/ошибки?
- Если у вас включен Tracy для проекта, он переопределит обработку ошибок Flight. Вам нужно отключить Tracy и убедиться, что
Flight::set('flight.handle_errors', true);
установлено.
- Если у вас включен Tracy для проекта, он переопределит обработку ошибок Flight. Вам нужно отключить Tracy и убедиться, что
-
Не отслеживает запросы к базе данных?
- Убедитесь, что вы используете
PdoWrapper
для подключений к базе данных. - Убедитесь, что последний аргумент в конструкторе
true
.
- Убедитесь, что вы используете
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);
}
Полезные советы
Когда вы отлаживаете свой код, есть несколько очень полезных функций для вывода данных для вас.
bdump($var)
- Это выведет переменную на панель Трейси в отдельной панели.dumpe($var)
- Это выведет переменную, а затем немедленно завершит работу.
Awesome-plugins/active_record
Flight Active Record
Активная запись – это отображение сущности базы данных на объект PHP. Проще говоря, если у вас есть таблица пользователей в вашей базе данных, вы можете "перевести" строку в этой таблице на класс User
и объект $user
в вашем коде. См. основной пример.
Нажмите здесь для просмотра репозитория на GitHub.
Основной пример
Предположим, у вас есть следующая таблица:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
password TEXT
);
Теперь вы можете настроить новый класс для представления этой таблицы:
/**
* Класс ActiveRecord обычно единственное число
*
* Настоятельно рекомендуется добавлять свойства таблицы в виде комментариев здесь
*
* @property int $id
* @property string $name
* @property string $password
*/
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
// вы можете установить это таким образом
parent::__construct($database_connection, 'users');
// или таким образом
parent::__construct($database_connection, null, [ 'table' => 'users']);
}
}
Теперь смотрите, как происходит магия!
// для sqlite
$database_connection = new PDO('sqlite:test.db'); // это просто для примера, вы, вероятно, используете реальное подключение к базе данных
// для mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');
// или mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// или mysqli с созданием без объекта
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');
$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('некоторый классный пароль');
$user->insert();
// или $user->save();
echo $user->id; // 1
$user->name = 'Joseph Mamma';
$user->password = password_hash('некоторый классный пароль снова!!!');
$user->insert();
// нельзя использовать $user->save() здесь, иначе он подумает, что это обновление!
echo $user->id; // 2
И добавление нового пользователя было так же просто! Теперь, когда в базе данных есть строка пользователя, как вы можете ее извлечь?
$user->find(1); // найти id = 1 в базе данных и вернуть его.
echo $user->name; // 'Bobby Tables'
А что если вы хотите найти всех пользователей?
$users = $user->findAll();
Что насчет определенного условия?
$users = $user->like('name', '%mamma%')->findAll();
Видите, как это весело? Давайте установим это и начнем!
Установка
Просто установите с помощью Composer
composer require flightphp/active-record
Использование
Это можно использовать как отдельную библиотеку или с PHP-фреймворком Flight. Полностью вам решать.
Отдельно
Просто убедитесь, что вы передаете подключение PDO в конструктор.
$pdo_connection = new PDO('sqlite:test.db'); // это просто для примера, вы, вероятно, используете реальное подключение к базе данных
$User = new User($pdo_connection);
Не хотите всегда устанавливать ваше подключение к базе данных в конструкторе? См. Управление подключением к базе данных для других идей!
Зарегистрировать как метод в Flight
Если вы используете PHP-фреймворк Flight, вы можете зарегистрировать класс ActiveRecord как сервис, но вам честно не обязательно это делать.
Flight::register('user', 'User', [ $pdo_connection ]);
// тогда вы можете использовать это так в контроллере, функции и т.д.
Flight::user()->find(1);
Методы runway
runway – это CLI инструмент для Flight, который имеет специальную команду для этой библиотеки.
# Использование
php runway make:record database_table_name [class_name]
# Пример
php runway make:record users
Это создаст новый класс в директории app/records/
как UserRecord.php
со следующим содержимым:
<?php
declare(strict_types=1);
namespace app\records;
/**
* Класс ActiveRecord для таблицы пользователей.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $username
* @property string $email
* @property string $password_hash
* @property string $created_dt
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Установите отношения для модели
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [
// 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
];
/**
* Конструктор
* @param mixed $databaseConnection Подключение к базе данных
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
Функции CRUD
find($id = null) : boolean|ActiveRecord
Находит одну запись и присваивает ее текущему объекту. Если вы передаете $id
какого-либо рода, это будет выполнять поиск по первичному ключу с этим значением. Если ничего не передается, это просто найдет первую запись в таблице.
Кроме того, вы можете передать ему другие вспомогательные методы для запроса к вашей таблице.
// найти запись с некоторыми условиями заранее
$user->notNull('password')->orderBy('id DESC')->find();
// найти запись по конкретному id
$id = 123;
$user->find($id);
findAll(): array<int,ActiveRecord>
Находит все записи в таблице, которую вы указываете.
$user->findAll();
isHydrated(): boolean
(v0.4.0)
Возвращает true
, если текущая запись была гидратирована (извлечена из базы данных).
$user->find(1);
// если запись найдена с данными...
$user->isHydrated(); // true
insert(): boolean|ActiveRecord
Вставляет текущую запись в базу данных.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Первичные ключи на текстовой основе
Если у вас есть первичный ключ на текстовой основе (например, UUID), вы можете установить значение первичного ключа перед вставкой одним из двух способов.
$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // или $user->save();
или вы можете позволить первичному ключу автоматически генерироваться для вас через события.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
// вы также можете установить первичный ключ таким образом вместо массива выше.
$this->primaryKey = 'uuid';
}
protected function beforeInsert(self $self) {
$self->uuid = uniqid(); // или как вам нужно сгенерировать ваши уникальные идентификаторы
}
}
Если вы не установите первичный ключ перед вставкой, он будет установлен на rowid
, и база данных сгенерирует его для вас, но он не будет храниться, потому что это поле может не существовать в вашей таблице. Вот почему рекомендуется использовать событие для автоматического управления этим.
update(): boolean|ActiveRecord
Обновляет текущую запись в базе данных.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();
save(): boolean|ActiveRecord
Вставляет или обновляет текущую запись в базе данных. Если у записи есть id, она будет обновлена, в противном случае будет вставлена.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();
Примечание: Если у вас есть определенные отношения в классе, они также будут рекурсивно сохранены, если они были определены, созданы и имеют измененные данные для обновления. (v0.4.0 и выше)
delete(): boolean
Удаляет текущую запись из базы данных.
$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();
Вы также можете удалить несколько записей, исполняя поиск заранее.
$user->like('name', 'Bob%')->delete();
dirty(array $dirty = []): ActiveRecord
Грязные данные относятся к данным, которые были изменены в записи.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
// на данный момент ничего не "грязное".
$user->email = 'test@example.com'; // теперь email считается "грязным", поскольку он был изменен.
$user->update();
// теперь нет грязных данных, так как они были обновлены и сохранены в базе данных
$user->password = password_hash('newpassword'); // теперь это грязное
$user->dirty(); // ничего не передав, вы очистите все грязные записи.
$user->update(); // ничего не обновится, потому что ничего не было захвачено как грязное.
$user->dirty([ 'name' => 'что-то', 'password' => password_hash('другой пароль') ]);
$user->update(); // обновлены как имя, так и пароль.
copyFrom(array $data): ActiveRecord
(v0.4.0)
Это псевдоним для метода dirty()
. Немного более понятно, что вы делаете.
$user->copyFrom([ 'name' => 'что-то', 'password' => password_hash('другой пароль') ]);
$user->update(); // обновлены как имя, так и пароль.
isDirty(): boolean
(v0.4.0)
Возвращает true
, если текущая запись была изменена.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
Сбрасывает текущую запись в ее начальное состояние. Это действительно полезно использовать в поведениях типа цикла.
Если вы передадите true
, он также сбросит данные запроса, которые использовались для поиска текущего объекта (поведение по умолчанию).
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);
foreach($users as $user) {
$user_company->reset(); // начинаем с чистого листа
$user_company->user_id = $user->id;
$user_company->company_id = $some_company_id;
$user_company->insert();
}
getBuiltSql(): string
(v0.4.1)
После выполнения метода find()
, findAll()
, insert()
, update()
или save()
вы можете получить SQL, который был построен, и использовать его для отладки.
Методы SQL Запросов
select(string $field1 [, string $field2 ... ])
Вы можете выбрать только несколько столбцов в таблице, если хотите (это более производительно на действительно широких таблицах с большим количеством столбцов)
$user->select('id', 'name')->find();
from(string $table)
Вы вполне можете выбрать другую таблицу! Почему бы и нет?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
Вы даже можете присоединиться к другой таблице в базе данных.
$user->join('contacts', 'contacts.user_id = users.id')->find();
where(string $where_conditions)
Вы можете установить некоторые пользовательские аргументы where (вы не можете устанавливать параметры в этом условии where)
$user->where('id=1 AND name="demo"')->find();
Примечание по безопасности - Вам может возникнуть желание сделать что-то вроде $user->where("id = '{$id}' AND name = '{$name}'")->find();
. Пожалуйста, НЕ ДЕЛАЙТЕ ЭТОГО!!! Это подвержено тому, что называется атаками SQL-инъекций. В Интернете есть много статей, пожалуйста, поищите "sql injection attacks php", и вы найдёте много статей на эту тему. Правильный способ обработки этого с помощью этой библиотеки вместо этого метода where()
, вы бы сделали что-то более вроде $user->eq('id', $id)->eq('name', $name)->find();
Если вам абсолютно необходимо это делать, библиотека PDO
имеет $pdo->quote($var)
, чтобы экранировать это для вас. Только после того, как вы используете quote()
, вы можете использовать это в операторе where()
.
group(string $group_by_statement)/groupBy(string $group_by_statement)
Группируйте ваши результаты по определенному условию.
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
Сортируйте возвращаемый запрос определённым образом.
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
Ограничьте количество возвращаемых записей. Если задано второе целое число, оно будет сдвинуто, ограничение также как в SQL.
$user->orderby('name DESC')->limit(0, 10)->findAll();
Условия WHERE
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Где field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Где field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Где field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Где field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Где field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Где field < $value
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
Где field >= $value
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Где field <= $value
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
Где field LIKE $value
или field NOT LIKE $value
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Где field IN($value)
или field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Где field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
Условия ИЛИ
Возможно, вы захотите обернуть ваши условия в оператор ИЛИ. Это делается либо с помощью методов startWrap()
и endWrap()
, либо путем заполнения 3-го параметра условия после поля и значения.
// Метод 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Это будет эквивалентно `id = 1 AND (name = 'demo' OR name = 'test')`
// Метод 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Это будет эквивалентно `id = 1 OR name = 'demo'`
Отношения
Вы можете установить несколько видов отношений, используя эту библиотеку. Вы можете установить отношения один->много и один->один между таблицами. Это требует небольшой дополнительной настройки в классе заранее.
Установка массива $relations
не сложна, но угадать правильный синтаксис может быть сложно.
protected array $relations = [
// вы можете назвать ключ как угодно. Имя ActiveRecord, вероятно, хорошее. Например: user, contact, client
'user' => [
// обязательно
// self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
self::HAS_ONE, // это тип отношения
// обязательно
'Some_Class', // это "другой" ActiveRecord, на который это будет ссылаться
// обязательно
// в зависимости от типа отношения
// self::HAS_ONE = внешний ключ, который ссылается на соединение
// self::HAS_MANY = внешний ключ, который ссылается на соединение
// self::BELONGS_TO = локальный ключ, который ссылается на соединение
'local_or_foreign_key',
// просто FYI, это также соединяется только с первичным ключом "другой" модели
// опционально
[ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // дополнительные условия, которые вы хотите при соединении с отношением
// $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))
// опционально
'back_reference_name' // это если вы хотите ссылаться на это отношение обратно на себя, например: $user->contact->user;
];
]
class User extends ActiveRecord{
protected array $relations = [
'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
}
class Contact extends ActiveRecord{
protected array $relations = [
'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'contacts');
}
}
Теперь у нас есть установленные ссылки, так что мы можем использовать их очень легко!
$user = new User($pdo_connection);
// найдите самого последнего пользователя.
$user->notNull('id')->orderBy('id desc')->find();
// получите контакты, используя отношение:
foreach($user->contacts as $contact) {
echo $contact->id;
}
// или мы можем пойти другим путем.
$contact = new Contact();
// найдите один контакт
$contact->find();
// получите пользователя, используя отношение:
echo $contact->user->name; // это имя пользователя
Классно, да?
Установка пользовательских данных
Иногда вам может потребоваться прикрепить что-то уникальное к вашей ActiveRecord, например, пользовательский расчет, который, возможно, будет легче просто прикрепить к объекту, который затем будет передан, скажем, шаблону.
setCustomData(string $field, mixed $value)
Вы прикрепляете пользовательские данные с помощью метода setCustomData()
.
$user->setCustomData('page_view_count', $page_view_count);
А затем вы просто ссылаетесь на это, как на обычное свойство объекта.
echo $user->page_view_count;
События
Одним из супер классных преимуществ этой библиотеки являются события. События срабатывают в определенные моменты на основе определенных методов, которые вы вызываете. Они очень полезны для автоматической настройки данных для вас.
onConstruct(ActiveRecord $ActiveRecord, array &config)
Это действительно полезно, если вам нужно установить подключение по умолчанию или что-то подобное.
// index.php или bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
//
//
//
// User.php
class User extends flight\ActiveRecord {
protected function onConstruct(self $self, array &$config) { // не забывайте о референции &
// вы можете сделать это, чтобы автоматически установить подключение
$config['connection'] = Flight::db();
// или это
$self->transformAndPersistConnection(Flight::db());
// Вы также можете установить имя таблицы таким образом.
$config['table'] = 'users';
}
}
beforeFind(ActiveRecord $ActiveRecord)
Это, вероятно, будет полезно, только если вам нужно манипулировать запросом каждый раз.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFind(self $self) {
// всегда выполняйте id >= 0, если это ваше желание
$self->gte('id', 0);
}
}
afterFind(ActiveRecord $ActiveRecord)
Эта один, вероятно, более полезна, если вам всегда нужно выполнять какую-либо логику каждый раз, когда эта запись извлекается. Нужно ли вам расшифровать что-то? Вам нужно запустить пользовательский запрос на подсчет каждый раз (невыгодно, но что ж)?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFind(self $self) {
// расшифровка чего-то
$self->secret = yourDecryptFunction($self->secret, $some_key);
// возможно, сохранение чего-то пользовательского, как запрос???
$self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']);
}
}
beforeFindAll(ActiveRecord $ActiveRecord)
Это, вероятно, будет полезно, только если вам нужно манипулировать запросом каждый раз.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFindAll(self $self) {
// всегда выполняйте id >= 0, если это ваше желание
$self->gte('id', 0);
}
}
afterFindAll(array<int,ActiveRecord> $results)
Похоже на afterFind()
, но вы можете делать это со всеми записями сразу!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFindAll(array $results) {
foreach($results as $self) {
// сделайте что-то классное, как и в afterFind()
}
}
}
beforeInsert(ActiveRecord $ActiveRecord)
Действительно полезно, если вам нужно установить некоторые значения по умолчанию каждый раз.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// задайте некоторые надежные значения по умолчанию
if(!$self->created_date) {
$self->created_date = gmdate('Y-m-d');
}
if(!$self->password) {
$self->password = password_hash((string) microtime(true));
}
}
}
afterInsert(ActiveRecord $ActiveRecord)
Возможно, у вас есть случай использования для изменения данных послеINSERT?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// делайте что хотите
Flight::cache()->set('most_recent_insert_id', $self->id);
// или что-то еще...
}
}
beforeUpdate(ActiveRecord $ActiveRecord)
Действительно полезно, если вам нужно установить некоторые значения по умолчанию каждый раз при обновлении.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeUpdate(self $self) {
// задайте некоторые надежные значения по умолчанию
if(!$self->updated_date) {
$self->updated_date = gmdate('Y-m-d');
}
}
}
afterUpdate(ActiveRecord $ActiveRecord)
Возможно, у вас есть случай использования для изменения данных после его обновления?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterUpdate(self $self) {
// делайте что хотите
Flight::cache()->set('most_recently_updated_user_id', $self->id);
// или что-то еще...
}
}
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
Это полезно, если вы хотите, чтобы события происходили как при вставках, так и при обновлениях. Я сэкономлю вам длинное объяснение, но я уверен, что вы можете догадаться, что это такое.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeSave(self $self) {
$self->last_updated = gmdate('Y-m-d H:i:s');
}
}
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
Не уверен, что вы хотите сделать здесь, но никаких предвзятостей! Дерзайте!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeDelete(self $self) {
echo 'Он был смелым солдатом... :cry-face:';
}
}
Управление подключением к базе данных
Когда вы используете эту библиотеку, вы можете установить подключение к базе данных несколькими способами. Вы можете установить подключение в конструкторе, вы можете установить его через переменную конфигурации $config['connection']
или вы можете установить его через setDatabaseConnection()
(v0.4.1).
$pdo_connection = new PDO('sqlite:test.db'); // например
$user = new User($pdo_connection);
// или
$user = new User(null, [ 'connection' => $pdo_connection ]);
// или
$user = new User();
$user->setDatabaseConnection($pdo_connection);
Если вы хотите избежать постоянного указания $database_connection
каждый раз, когда вы вызываете активную запись, есть способы обойти это!
// index.php или bootstrap.php
// Установите это как зарегистрированный класс в Flight
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
// User.php
class User extends flight\ActiveRecord {
public function __construct(array $config = [])
{
$database_connection = $config['connection'] ?? Flight::db();
parent::__construct($database_connection, 'users', $config);
}
}
// И теперь, без аргументов не требуется!
$user = new User();
Примечание: Если вы планируете unit-тестирование, делать это может создать некоторые проблемы с unit-тестами, но в целом, потому что вы можете инъектировать ваше подключение с помощью
setDatabaseConnection()
или$config['connection']
, это не слишком плохо.
Если вам нужно обновить подключение к базе данных, например, если вы запускаете долгое CLI-скрипт и вам нужно периодически обновлять подключение, вы можете переустановить соединение с помощью $your_record->setDatabaseConnection($pdo_connection)
.
Участие
Пожалуйста, сделайте это. :D
Настройка
Когда вы участвуете, убедитесь, что вы выполняете команду composer test-coverage
, чтобы поддерживать 100% покрытие тестами (это не истинное покрытие юнит-тестами, больше похоже на интеграционное тестирование).
Также убедитесь, что вы выполняете composer beautify
и composer phpcs
, чтобы исправить любые ошибки линтинга.
Лицензия
MIT
Awesome-plugins/latte
Latte
Latte — это полнофункциональный шаблонизатор, который очень прост в использовании и ближе к синтаксису PHP, чем Twig или Smarty. Его также очень легко расширять и добавлять собственные фильтры и функции.
Установка
Установите с помощью composer.
composer require latte/latte
Базовая настройка
Есть несколько базовых опций настройки для начала работы. Подробнее о них можно прочитать в Документации Latte.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Где latte специально хранит свой кэш
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Простой пример макета
Вот простой пример файла макета. Это файл, который будет использоваться для обертки всех ваших других представлений.
<!-- 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>
<!-- your nav elements here -->
</nav>
</header>
<div id="content">
<!-- This is the magic right here -->
{block content}{/block}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
А теперь у нас есть ваш файл, который будет отображаться внутри этого блока content:
<!-- app/views/home.latte -->
<!-- This tells Latte that this file is "inside" the layout.latte file -->
{extends layout.latte}
<!-- This is the content that will be rendered inside the layout inside the content block -->
{block content}
<h1>Home Page</h1>
<p>Welcome to my app!</p>
{/block}
Затем, когда вы будете отображать это внутри своей функции или контроллера, вы сделаете что-то вроде этого:
// simple route
Flight::route('/', function () {
Flight::render('home.latte', [
'title' => 'Home Page'
]);
});
// or if you're using a controller
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.latte', [
'title' => 'Home Page'
]);
}
}
Смотрите Документацию Latte для получения дополнительной информации о том, как использовать Latte на полную мощность!
Отладка с Tracy
Требуется PHP 8.1+ для этого раздела.
Вы также можете использовать Tracy для помощи в отладке ваших файлов шаблонов Latte прямо из коробки! Если у вас уже установлен Tracy, вам нужно добавить расширение Latte к Tracy.
// services.php
use Tracy\Debugger;
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Где latte специально хранит свой кэш
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
// This will only add the extension if the Tracy Debug Bar is enabled
if (Debugger::$showBar === true) {
// this is where you add the Latte Panel to Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($finalPath, $data, $block);
});
Awesome-plugins/awesome_plugins
Крутые плагины
Flight невероятно расширяем. Существует множество плагинов, которые можно использовать для добавления функциональности в ваше приложение Flight. Некоторые из них официально поддерживаются командой Flight, а другие — это микро/лайт-библиотеки, чтобы помочь вам начать.
Документация API
Документация API крайне важна для любого API. Она помогает разработчикам понять, как взаимодействовать с вашим API и чего ожидать в ответ. Есть несколько инструментов, которые помогут вам генерировать документацию API для ваших проектов Flight.
- FlightPHP OpenAPI Generator - Пост в блоге, написанный Даниэлем Шрайбером, о том, как использовать спецификацию OpenAPI с FlightPHP для построения вашего API с использованием подхода "API first".
- SwaggerUI - Swagger UI — отличный инструмент для генерации документации API для ваших проектов Flight. Его очень легко использовать, и его можно настроить под ваши нужды. Это PHP-библиотека, которая поможет вам генерировать документацию Swagger.
Мониторинг производительности приложений (APM)
Мониторинг производительности приложений (APM) крайне важен для любого приложения. Он помогает понять, как работает ваше приложение и где находятся узкие места. Существует множество инструментов APM, которые можно использовать с Flight.
- official flightphp/apm - Flight APM — это простая библиотека APM, которая может использоваться для мониторинга приложений Flight. Она может использоваться для мониторинга производительности вашего приложения и помощи в выявлении узких мест.
Асинхронность
Flight уже является быстрым фреймворком, но добавление к нему турбо-двигателя делает всё ещё веселее (и сложнее)!
- flightphp/async - Официальная библиотека Flight Async. Эта библиотека — простой способ добавить асинхронную обработку в ваше приложение. Она использует Swoole/Openswoole под капотом, чтобы предоставить простой и эффективный способ запуска задач асинхронно.
Авторизация/Разрешения
Авторизация и разрешения крайне важны для любого приложения, которое требует контроля над тем, кто может получить доступ к чему.
- official flightphp/permissions - Официальная библиотека Flight Permissions. Эта библиотека — простой способ добавить разрешения на уровне пользователя и приложения в ваше приложение.
Кэширование
Кэширование — отличный способ ускорить ваше приложение. Существует множество библиотек кэширования, которые можно использовать с Flight.
- official flightphp/cache - Лёгкий, простой и автономный класс PHP для кэширования в файлах
CLI
CLI-приложения — отличный способ взаимодействовать с вашим приложением. Вы можете использовать их для генерации контроллеров, отображения всех маршрутов и многого другого.
- official flightphp/runway - Runway — это CLI-приложение, которое помогает управлять вашими приложениями Flight.
Куки
Куки — отличный способ хранить небольшие объёмы данных на стороне клиента. Их можно использовать для хранения предпочтений пользователя, настроек приложения и многого другого.
- overclokk/cookie - PHP Cookie — это PHP-библиотека, которая предоставляет простой и эффективный способ управления куки.
Отладка
Отладка крайне важна при разработке в локальной среде. Есть несколько плагинов, которые могут улучшить ваш опыт отладки.
- tracy/tracy - Это полнофункциональный обработчик ошибок, который можно использовать с Flight. У него есть несколько панелей, которые помогут вам отлаживать ваше приложение. Его также очень легко расширять и добавлять собственные панели.
- official flightphp/tracy-extensions - Используется с обработчиком ошибок Tracy, этот плагин добавляет несколько дополнительных панелей для помощи в отладке специально для проектов Flight.
Базы данных
Базы данных — основа большинства приложений. Это способ хранения и извлечения данных. Некоторые библиотеки баз данных — просто обёртки для написания запросов, а некоторые — полноценные ORM.
- official flightphp/core PdoWrapper - Официальная обёртка Flight PDO, которая является частью ядра. Это простая обёртка, которая помогает упростить процесс написания и выполнения запросов. Это не ORM.
- official flightphp/active-record - Официальный ORM/Mapper Flight ActiveRecord. Отличная маленькая библиотека для лёгкого извлечения и хранения данных в вашей базе данных.
- byjg/php-migration - Плагин для отслеживания всех изменений базы данных в вашем проекте.
Шифрование
Шифрование крайне важно для любого приложения, которое хранит конфиденциальные данные. Шифрование и дешифрование данных не так уж сложно, но правильное хранение ключа шифрования может быть сложным. Самое важное — никогда не хранить ключ шифрования в публичной директории или коммитить его в репозиторий кода.
- defuse/php-encryption - Это библиотека, которую можно использовать для шифрования и дешифрования данных. Запуск и работа с ней довольно просты для начала шифрования и дешифрования данных.
Очередь заданий
Очереди заданий очень полезны для асинхронной обработки задач. Это может быть отправка email, обработка изображений или что-то, что не требует выполнения в реальном времени.
- n0nag0n/simple-job-queue - Simple Job Queue — это библиотека, которую можно использовать для асинхронной обработки заданий. Её можно использовать с beanstalkd, MySQL/MariaDB, SQLite и PostgreSQL.
Сессии
Сессии не очень полезны для API, но для построения веб-приложения сессии могут быть крайне важны для поддержания состояния и информации о входе.
- official flightphp/session - Официальная библиотека Flight Session. Это простая библиотека сессий, которую можно использовать для хранения и извлечения данных сессии. Она использует встроенную обработку сессий PHP.
- Ghostff/Session - Менеджер сессий PHP (неблокирующий, flash, сегмент, шифрование сессий). Использует PHP open_ssl для опционального шифрования/дешифрования данных сессии.
Шаблонизация
Шаблонизация — основа любого веб-приложения с UI. Существует множество шаблонизаторов, которые можно использовать с Flight.
- deprecated flightphp/core View - Это очень базовый шаблонизатор, который является частью ядра. Не рекомендуется использовать, если в вашем проекте больше нескольких страниц.
- latte/latte - Latte — полнофункциональный шаблонизатор, который очень легко использовать и ближе к синтаксису PHP, чем Twig или Smarty. Его также очень легко расширять и добавлять собственные фильтры и функции.
Интеграция с WordPress
Хотите использовать Flight в вашем проекте WordPress? Для этого есть удобный плагин!
- n0nag0n/wordpress-integration-for-flight-framework - Этот плагин WordPress позволяет запускать Flight прямо рядом с WordPress. Он идеален для добавления пользовательских API, микросервисов или даже полноценных приложений на ваш сайт WordPress с использованием фреймворка Flight. Супер полезно, если вы хотите лучшее из двух миров!
Вклад
Есть плагин, которым вы хотите поделиться? Отправьте pull request, чтобы добавить его в список!
Media
Медиа
Мы постарались отследить то, что смогли, различных типов медиа в интернете, связанных с Flight. См. ниже различные ресурсы, которые вы можете использовать, чтобы узнать больше о Flight.
Статьи и обзоры
- Unit Testing and SOLID Principles by Brian Fenton (2015?)
- PHP Web Framework Flight by ojambo (2025)
- Define, Generate, and Implement: An API-First Approach with OpenAPI Generator and FlightPHP by Daniel Schreiber (2025)
- Best PHP Micro Frameworks for 2024 by n0nag0n (2024)
- Creating a RESTful API with Flight Framework by n0nag0n (2024)
- Building a Simple Blog with Flight Part 2 by n0nag0n (2024)
- Building a Simple Blog with Flight Part 1 by n0nag0n (2024)
- 🚀 Build a Simple CRUD API in PHP with the Flight Framework by soheil-khaledabadi (2024)
- Building a PHP Web Application with the Flight Micro-framework by Arthur C. Codex (2023)
- Best PHP Frameworks for Web Development in 2024 by Ravikiran A S (2023)
- Top 12 PHP Frameworks: A Comprehensive Guide for 2023 by marketing kbk (2023)
- 5 PHP Frameworks You've (Probably) Never Heard of by n0nag0n (2022)
- 12 top PHP frameworks for web developers to consider in 2023 by Anna Monus (2022)
- The Best PHP Microframeworks on a Cloud Server by Shahzeb Ahmed (2021)
- PHP framework: Top 15 powerful ones for your web development by AHT Tech (2020)
- Easy PHP Routing with FlightPHP by Lucas Conceição (2019)
- Trying Out New PHP Framework (Flight) by Leon (2017)
- Setting up FlightPHP to work with Backbonejs by Timothy Tocci (2015)
Видео и уроки
- Build a Flight PHP App with MVC & MariaDB in 10 Minutes! (Beginner Friendly) by ojamboshop (2025)
- Create a REST API for IoT Devices Using PHP & FlightPHP - ESP32 API by IoT Craft Hub (2024)
- PHP Flight Framework Simple Introductory Video by n0nag0n (2024)
- Set header HTTP code in Flightphp (3 Solutions!!) by Roel Van de Paar (2024)
- PHP Flight Framework Tutorial. Super easy API Project! by n0nag0n (2022)
- Aplicación web CRUD con php y mysql y bootstrap usando flight by Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Lighttpd rewrite rule for Flight PHP microframework by Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (Tagalog) by Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (Tagalog) by Info Singkat Official (2020)
- How To Create JSON REST API IN PHP - Part 2 by Codewife (2018)
- How To Create JSON REST API IN PHP - Part 1 by Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel by Codemarket (2016)
- Tutorial 1 Flight PHP - Instalación by absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 by absagg (2014)
Что-то упущено?
Мы пропустили что-то, что вы написали или записали? Дайте нам знать с помощью issue или pull request!
Examples
Нужен быстрый старт?
У вас есть два варианта для начала работы с новым проектом Flight:
- Full Skeleton Boilerplate: Более полный пример с контроллерами и представлениями.
- Single File Skeleton Boilerplate: Один файл, который включает всё необходимое для запуска вашего приложения в одном простом файле.
Примеры, внесённые сообществом:
- flightravel: FlightPHP с директориями Laravel, с инструментами PHP + GH Actions
- fleact - Стартовый набор FlightPHP с интеграцией ReactJS.
- flastro - Стартовый набор FlightPHP с интеграцией Astro.
- velt - Velt — это быстрый и простой шаблон Svelte для старта с бэкендом на FlightPHP.
Нужен ли вам вдохновение?
Хотя эти примеры не спонсируются официально командой Flight, они могут дать вам идеи о том, как структурировать свои собственные проекты, построенные на Flight!
- Ivox Car Rental - Ivox Car Rental — это одностраничное, мобильно-дружественное веб-приложение для аренды автомобилей, построенное на PHP (FlightPHP), JavaScript и MySQL. Оно поддерживает регистрацию пользователей, просмотр и бронирование автомобилей, в то время как администраторы могут управлять автомобилями, пользователями и бронированиями. Приложение включает REST API, аутентификацию JWT и адаптивный дизайн для современного опыта аренды.
- Decay - Flight v3 с HTMX и SleekDB, всё о зомби! (Demo)
- Flight Example Blog - Flight v3 с Middleware, Controllers, Active Record и Latte.
- Flight CRUD RESTful API - Простой проект CRUD API с использованием фреймворка Flight, который предоставляет базовую структуру для новых пользователей, чтобы быстро настроить PHP-приложение с операциями CRUD и подключением к базе данных. Проект демонстрирует, как использовать Flight для разработки RESTful API, делая его идеальным инструментом для обучения новичков и полезным стартовым набором для более опытных разработчиков.
- Flight School Management System - Flight v3
- Paste Bin with Comments - Flight v3
- Basic Skeleton App
- Example Wiki
- The IT-Innovator PHP Framework Application
- LittleEducationalCMS (Spanish)
- Italian Yellow Pages API
- Generic Content Management System (with....very little documentation)
- A tiny php framework based on Flight and medoo.
- Example MVC Application
- Production ready Flight Boilerplate - Готовый к производству фреймворк аутентификации, который сэкономит вам недели разработки. Функции корпоративного уровня безопасности: 2FA/TOTP, интеграция LDAP, Azure SSO, интеллектуальное ограничение скорости, отпечатки сессий, защита от brute-force, панель аналитики безопасности, всестороннее логирование аудита и гранулярный контроль доступа на основе ролей.
Хотите поделиться своим примером?
Если у вас есть проект, которым вы хотите поделиться, пожалуйста, отправьте pull request, чтобы добавить его в этот список!
Install/install
Инструкции по установке
Есть некоторые базовые предварительные требования перед тем, как вы сможете установить Flight. В частности, вам потребуется:
- Установить PHP на вашу систему
- Установить Composer для наилучшего опыта разработчика.
Базовая установка
Если вы используете Composer, вы можете выполнить следующую команду:
composer require flightphp/core
Это разместит только основные файлы Flight на вашей системе. Вам нужно будет определить структуру проекта, макет, зависимости, конфигурации, автозагрузку и т.д. Этот метод гарантирует, что не будут установлены другие зависимости, кроме Flight.
Вы также можете скачать файлы напрямую и извлечь их в вашу веб-директорию.
Рекомендуемая установка
Настоятельно рекомендуется начинать с приложения flightphp/skeleton для любых новых проектов. Установка очень простая.
composer create-project flightphp/skeleton my-project/
Это настроит структуру вашего проекта, настроит автозагрузку с пространствами имен, настроит конфигурацию и предоставит другие инструменты, такие как Tracy, Расширения Tracy и Runway.
Настройка вашего веб-сервера
Встроенный сервер разработки PHP
Это, безусловно, самый простой способ запустить и запустить. Вы можете использовать встроенный сервер для запуска вашего приложения и даже использовать SQLite для базы данных (при условии, что sqlite3 установлен на вашей системе) и не требовать ничего особенного! Просто выполните следующую команду после установки PHP:
php -S localhost:8000
# или с приложением skeleton
composer start
Затем откройте браузер и перейдите на http://localhost:8000
.
Если вы хотите сделать корневую директорию документа вашего проекта другой директорией (Например: ваш проект — ~/myproject
, но корневая директория документа — ~/myproject/public/
), вы можете выполнить следующую команду, находясь в директории ~/myproject
:
php -S localhost:8000 -t public/
# с приложением skeleton это уже настроено
composer start
Затем откройте браузер и перейдите на http://localhost:8000
.
Apache
Убедитесь, что Apache уже установлен на вашей системе. Если нет, погуглите, как установить Apache на вашу систему.
Для Apache отредактируйте ваш файл .htaccess
со следующим содержимым:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Примечание: Если вам нужно использовать flight в поддиректории, добавьте строку
RewriteBase /subdir/
сразу послеRewriteEngine On
.Примечание: Если вы хотите защитить все файлы сервера, такие как файл базы данных или env. Поместите это в ваш файл
.htaccess
:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
Убедитесь, что Nginx уже установлен на вашей системе. Если нет, погуглите, как установить 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();
С приложением skeleton это уже настроено и обрабатывается в вашем файле app/config/routes.php
. Службы настроены в app/config/services.php
.
Установка PHP
Если у вас уже установлен php
на вашей системе, переходите к разделу загрузки и пропустите эти инструкции.
macOS
Установка PHP с помощью Homebrew
-
Установите Homebrew (если еще не установлен):
- Откройте Терминал и выполните:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Откройте Терминал и выполните:
-
Установите PHP:
- Установите последнюю версию:
brew install php
- Чтобы установить конкретную версию, например, PHP 8.1:
brew tap shivammathur/php brew install shivammathur/php/php@8.1
- Установите последнюю версию:
-
Переключайтесь между версиями PHP:
- Отсоедините текущую версию и подключите желаемую:
brew unlink php brew link --overwrite --force php@8.1
- Проверьте установленную версию:
php -v
- Отсоедините текущую версию и подключите желаемую:
Windows 10/11
Ручная установка PHP
-
Скачайте PHP:
- Посетите PHP для Windows и скачайте последнюю или конкретную версию (например, 7.4, 8.0) в виде zip-файла, не требующего потокобезопасности.
-
Извлеките PHP:
- Извлеките скачанный zip-файл в
C:\php
.
- Извлеките скачанный zip-файл в
-
Добавьте PHP в системный PATH:
- Перейдите в Свойства системы > Переменные среды.
- В разделе Системные переменные найдите Path и нажмите Изменить.
- Добавьте путь
C:\php
(или куда вы извлекли PHP). - Нажмите OK, чтобы закрыть все окна.
-
Настройте PHP:
- Скопируйте
php.ini-development
вphp.ini
. - Отредактируйте
php.ini
для настройки PHP по необходимости (например, установкаextension_dir
, включение расширений).
- Скопируйте
-
Проверьте установку PHP:
- Откройте Командную строку и выполните:
php -v
- Откройте Командную строку и выполните:
Установка нескольких версий PHP
-
Повторите указанные выше шаги для каждой версии, размещая каждую в отдельной директории (например,
C:\php7
,C:\php8
). -
Переключайтесь между версиями, регулируя системную переменную PATH, чтобы указать на директорию желаемой версии.
Ubuntu (20.04, 22.04 и т.д.)
Установка PHP с помощью apt
-
Обновите списки пакетов:
- Откройте Терминал и выполните:
sudo apt update
- Откройте Терминал и выполните:
-
Установите PHP:
- Установите последнюю версию PHP:
sudo apt install php
- Чтобы установить конкретную версию, например, PHP 8.1:
sudo apt install php8.1
- Установите последнюю версию PHP:
-
Установите дополнительные модули (опционально):
- Например, для установки поддержки MySQL:
sudo apt install php8.1-mysql
- Например, для установки поддержки MySQL:
-
Переключайтесь между версиями PHP:
- Используйте
update-alternatives
:sudo update-alternatives --set php /usr/bin/php8.1
- Используйте
-
Проверьте установленную версию:
- Выполните:
php -v
- Выполните:
Rocky Linux
Установка PHP с помощью yum/dnf
-
Включите репозиторий EPEL:
- Откройте Терминал и выполните:
sudo dnf install epel-release
- Откройте Терминал и выполните:
-
Установите репозиторий Remi's:
- Выполните:
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm sudo dnf module reset php
- Выполните:
-
Установите PHP:
- Чтобы установить версию по умолчанию:
sudo dnf install php
- Чтобы установить конкретную версию, например, PHP 7.4:
sudo dnf module install php:remi-7.4
- Чтобы установить версию по умолчанию:
-
Переключайтесь между версиями PHP:
- Используйте команду модуля
dnf
:sudo dnf module reset php sudo dnf module enable php:remi-8.0 sudo dnf install php
- Используйте команду модуля
-
Проверьте установленную версию:
- Выполните:
php -v
- Выполните:
Общие замечания
- Для сред разработки важно настроить параметры PHP в соответствии с требованиями вашего проекта.
- При переключении версий PHP убедитесь, что все соответствующие расширения PHP установлены для конкретной версии, которую вы планируете использовать.
- Перезапустите ваш веб-сервер (Apache, Nginx и т.д.) после переключения версий PHP или обновления конфигураций, чтобы применить изменения.
Guides
Руководства
Flight PHP предназначен для того, чтобы быть простым, но мощным, и наши руководства помогут вам создавать реальные приложения шаг за шагом. Эти практические уроки проведут вас через полные проекты, чтобы продемонстрировать, как Flight можно использовать эффективно.
Официальные руководства
Создание блога
Узнайте, как создать функциональное приложение для блога с помощью Flight PHP. Это руководство проведет вас через:
- Настройку структуры проекта
- Работа с шаблонами с использованием Latte
- Реализация маршрутов для постов
- Хранение и извлечение данных
- Обработка отправки форм
- Основная обработка ошибок
Этот учебник идеален для начинающих, которые хотят увидеть, как все элементы собираются вместе в реальном приложении.
Юнит-тестирование и принципы SOLID
Это руководство охватывает основы юнит-тестирования в приложениях Flight PHP. В него входит:
- Настройка PHPUnit
- Написание тестируемого кода с использованием принципов SOLID
- Мокирование зависимостей
- Распространенные ошибки, которых следует избегать
- Масштабирование тестов по мере роста вашего приложения Этот учебник идеален для разработчиков, желающих улучшить качество кода и его поддерживаемость.
Неофициальные руководства
Хотя эти руководства не поддерживаются официально командой Flight, они являются ценными ресурсами, созданными сообществом. Они охватывают различные темы и сценарии использования, предоставляя дополнительные insights по использованию Flight PHP.
Создание RESTful API с Flight Framework
Это руководство проведет вас через создание RESTful API с использованием Flight PHP. В нем рассматриваются основы настройки API, определения маршрутов и возврата ответов в формате JSON.
Создание простого блога
Это руководство проведет вас через создание базового блога с использованием Flight PHP. Оно состоит из 2 частей: одна охватывает основы, а другая — более продвинутые темы и доработки для блога, готового к производству.
- Создание простого блога с Flight — Часть 1 — Начало работы с простым блогом.
- Создание простого блога с Flight — Часть 2 — Доработка блога для производства.
Создание API для Pokémon в PHP: Руководство для начинающих
Это забавное руководство проведет вас через создание простого API для Pokémon с использованием Flight PHP. В нем рассматриваются основы настройки API, определения маршрутов и возврата ответов в формате JSON.
Вклад в проект
У вас есть идея для руководства? Вы нашли ошибку? Мы приветствуем вклад! Наши руководства поддерживаются в репозитории документации FlightPHP.
Если вы создали что-то интересное с помощью Flight и хотите поделиться этим в виде руководства, пожалуйста, отправьте pull request. Обмен знаниями помогает сообществу Flight расти.
Ищете документацию по API?
Если вы ищете конкретную информацию о основных функциях и методах Flight, загляните в раздел Learn нашей документации.