Привет, Хабр. Недавно получил в руки интересный проект, который, несмотря на свою простоту требовал не использовать какой-либо фреймворк. О пакетах речи не шло, поэтому было принято решение использовать привычные компоненты Laravel. Если есть необходимость в использовании очередей, Eloquent или контейнера — добро пожаловать под кат.
О простоте деления фреймворка на компоненты
Начиная с Laravel 4, все компоненты — отдельные пакеты, которые в теории можно заставить работать в любом PHP проекте. Однако, некоторые компоненты требуют дополнительной настройки, которая в основном фреймворке спрятана под капот.
Компоненты
Контейнер
Из всех рассматриваемых компонентов illuminate/container
— самый простой в установке и использовании.
<?php
use Illuminate\Container\Container;
$container = Container::getInstance();
return $container->make(Foo::class);
Впрочем, вызов статичного метода на классе Container
при каждом использовании контейнера — не самая лучшая идея. В фреймворке доступен хелпер app()
, который вернёт нам инстанс глобального контейнера, однако, нам такой нужно создать вручную.
Создадим файл helpers.php
для таких хелперов, и добавим его в автозагрузку.
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"YourApp\\": "src/"
}
}
Добавляем хелпер app()
в файл.
<?php
use Illuminate\Container\Container;
if (! function_exists('app')) {
/**
* Get the available container instance.
*
* @param string|null $abstract
* @param array $parameters
* @return mixed|\Illuminate\Container\Container
*/
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Container::getInstance();
}
return Container::getInstance()->make($abstract, $parameters);
}
}
Готово. Можно пробовать использовать хелпер.
<?php
return app(Foo::class);
Query Builder и Eloquent
Для использования компонента illuminate/database
мы будем использовать Capsule\Manager
— класс, предназначенный для работы с построителем запросов вне Laravel.
Начнём с настройки подключения к БД. Желательно эту настройку проводить на этапе запуска вашего приложения, сразу после подключения autoload.php
.
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Illuminate\Database\Capsule\Manager;
$manager = new Manager;
$manager->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'database',
'username' => 'root',
'password' => 'password',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
// Позволяет использовать статичные вызовы при работе с Capsule.
$manager->setAsGlobal();
Можно начинать работу с построителем запросов.
<?php
use Illuminate\Database\Capsule\Manager;
return Manager::table('orders')->get();
Что насчёт Eloquent?
<?php
namespace YourApp\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model {
protected $fillable = [
'name',
];
}
<?php
use YourApp\Models\Order;
Order::first();
С миграциями ситуация сложнее — с одной стороны, в комплекте есть Schema Builder. С другой — автоматический механизм запуска миграций отсутствует.
<?php
use Illuminate\Database\Capsule\Manager;
Manager::schema()->create('orders', function ($table) {
$table->bigIncrements('id');
$table->string('name');
$table->timestamps();
});
Для запуска достаточно выполнить файл с миграцией: php migration.php
Очереди
У очередей тоже есть свой Capsule
, однако аналоги queue:work
и queue:listen
необходимо писать вручную.
Начнём с класса Application
. В Laravel этот класс используется как глобальный инстанс контейнера, который помимо методов Illuminate\Container\Container
содержит вспомогательные методы для работы с фреймворком (текущая версия, пути к хранилищу, ресурсам). Однако, наш класс будет содержать лишь один метод — isDownForMaintenance
. Он необходим для работы воркера, так как определяет состояние работы приложения в данный момент.
<?php
namespace YourApp;
use Illuminate\Container\Container;
class Application extends Container {
public function isDownForMaintenance()
{
return false;
}
}
Далее, нам необходимо зарегистрировать провайдер illuminate/events
, и забиндить контракт Illuminate\Contracts\Events\Dispatcher
к алиасу events
.
<?php
use YourApp\Application;
use Illuminate\Contracts\Events\Dispatcher;
$application = Application::getInstance();
$application->bind(Dispatcher::class, 'events');
Теперь необходимо создать инстанс Capsule
, и добавить туда конфигурации соединений.
Пример конфигурации для БД
<?php
use YourApp\Application;
use Illuminate\Queue\Capsule\Manager;
use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Database\ConnectionResolver;
$container = Application::getInstance();
$queue = new Manager($container);
$queue->addConnection([
'driver' => 'database',
'table' => 'jobs',
'connection' => 'default',
'queue' => 'default',
], 'default');
// Также, как и с Illuminate\Database\Capsule\Manager позволяет использовать статичные вызовы для очередей
$queue->setAsGlobal();
$connection = Capsule::schema()->getConnection();
$resolver = new ConnectionResolver(['default' => $connection]);
$queue->getQueueManager()->addConnector('database', function () use ($resolver) {
return new DatabaseConnector($resolver);
});
Пример конфигурации для Redis (требует установленного illuminate/redis
)
<?php
use Illuminate\Redis\RedisManager;
use Illuminate\Queue\Capsule\Manager;
$container->bind('redis', function () use ($container) {
return new RedisManager($container, 'predis', [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0,
]
]);
});
$queue = new Manager($container);
$queue->addConnection([
'driver' => 'redis',
'connection' => 'default',
'queue' => 'default',
], 'default');
$queue->setAsGlobal();
Последний этап в конфигурации — добавление аналога Exception Handler.
<?php
use Illuminate\Contracts\Debug\ExceptionHandler;
class Handler implements ExceptionHandler {
public function report(Exception $e)
{
// Действие, если Exception подпадает под критерии об уведомлении.
// Пример: отправка в Sentry, отправка сообщения на почту.
}
public function render($request, Exception $e)
{
// Отображение ошибки
}
public function renderForConsole($output, Exception $e)
{
// Отображение ошибки, если среда запуска приложения - терминал
}
public function shouldReport(\Exception $e)
{
// Необходимо ли уведомлять об ошибке
}
}
app()->bind('exception.handler', function () {
return new Handler;
});
Конфигурация очередей завершена. Теперь можно приступать к написанию queue:work
.
<?php
require_once __DIR__.'/bootstrap/bootstrap.php';
use Illuminate\Queue\Worker;
use Illuminate\Queue\Capsule\Manager;
use Illuminate\Queue\WorkerOptions;
$queueManager = Manager::getQueueManager();
$worker = new Worker($queueManager, app('events'), app('exception.handler'));
$worker->daemon('default', 'default', new WorkerOptions);
Теперь для запуска воркера очередей достаточно написать php worker.php
.
Если же есть необходимость в использовании queue:listen
, то нужно создавать два отдельных файла. Один — демон, который слушает очередь, и запускает второй файл на каждый новый job. Второй файл, в свою очередь, выступает в роли исполнителя задачи.
worker.php
<?php
require_once __DIR__.'/bootstrap/bootstrap.php';
use Illuminate\Queue\Listener;
use Illuminate\Queue\ListenerOptions;
// По умолчанию, в качестве "второго файла", в Laravel выступает artisan, однако в нашем случае это будет файл work.php.
// https://github.com/laravel/framework/blob/6.x/src/Illuminate/Queue/Listener.php#L72
define('ARTISAN_BINARY', 'work.php');
$worker = app(Listener::class, ['commandPath' => __DIR__]);
$worker->setOutputHandler(function ($type, $line) {
echo $line;
});
$worker->listen('default', 'default', new ListenerOptions);
work.php
<?php
require_once 'bootstrap/bootstrap.php';
use Illuminate\Queue\Worker;
use Illuminate\Queue\WorkerOptions;
use Illuminate\Queue\Capsule\Manager;
$queueManager = Manager::getQueueManager();
$worker = new Worker($queueManager, app('events'), app('exception.handler'));
$worker->runNextJob('default', 'default', new WorkerOptions);
Переходим к использованию. Все методы можно просмотреть в API
<?php
use Illuminate\Queue\Capsule\Manager;
Manager::push(SomeJob::class);
Комментарии (14)
symbix
09.11.2019 22:09несмотря на свою простоту требовал не использовать какой-либо фреймворк. О пакетах речи не шло, поэтому было принято решение использовать привычные компоненты Laravel.
Прям русская народная сказка "каша из топора". :-)
Вообще Lumen есть же, я не представляю себе, какие могут быть аргументы против него, если уж все равно illuminate-компоненты используются.
ivan77011 Автор
09.11.2019 22:15Lumen был первым что я предложил взамен целой ларе, однако боязнь «медленной работы» как фреймворков, так и микрофреймворков вылилась в использование пакетов, взятых из этих же фреймворков. Работа была простейшая, поэтому отказываться от неё из-за таких ситуаций не имело смысла.
Lachezis
09.11.2019 23:13Что такое «медленная работа»? Скорость разработки низкая?
ivan77011 Автор
09.11.2019 23:30Скорее всего он имел ввиду именно медленную скорость работы самого кода. Его можно понять — бутстрап у лары далеко не самый быстрый, да и задача не настолько объемная, чтобы применять фреймворки.
Lachezis
09.11.2019 23:50Как страшный сон вспоминаю время когда за бутстрап нужно было платить. Сразу все вопросы про микро отпадают.
dady_KK
09.11.2019 23:20Вот честно, не до конца понял, проблема у заказчика была с медленностью фреймворков (которая обычно кешированием решается) или тем, что задача простая и тащить даже микрофрймворк толку не было. Если первое, то ещё понятно (не все кейсы можно закрыть кешированием), но если второе, то непонятно как использование компонентов фреймворка согласовывается с решением
be_a_dancer
10.11.2019 14:12Не понимаю позиции заказчика — отказ от фреймворков. Ведь ты в любом случае будешь использовать (микро)фреймворк, только самописный. Убедить не пытались? Если пытались, то как?
Расскажите, пожалуйста, подробнее.ivan77011 Автор
10.11.2019 14:28Попытки переубедить не предпринимались, и на то было несколько причин:
1. Проект сам по себе был простой.
2. Было желание полазить в ядре лары именно с целью использования компонентов вне фреймворка.
Если бы проект был посложнее небольшого микросервиса, или у меня было отсутствие желания собирать свой недо-фреймворк, то отказался бы от выполнения заказа.be_a_dancer
10.11.2019 14:30Спасибо за ответ. А расскажите, почему именно ларовские компоненты использовались, а не симфоневые?
arku
Вот в этом репозитории — github.com/mattstauffer/Torch/tree/master/components
есть быстрый старт для каждого компонента, реально помогает, когда нужно что-то не попсовое, но хочется ларавельное :)