22 677 Монеток
Атрибуты в PHP — это отличный способ добавлять метаданные к вашим классам, методам и свойствам. Laravel предоставляет множество готовых атрибутов, которые можно использовать для улучшения структуры и читаемости вашего кода.
(ObservedBy)
Атрибут ObservedBy
позволяет указать наблюдателя для модели. Это помогает держать код модели чистым, перенося логику наблюдателя в отдельный класс.
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
// ...
}
(ScopedBy)
Атрибут ScopedBy
позволяет назначить глобальные условия для модели. Это полезно, когда нужно применять общие ограничения к запросам для конкретной модели.
namespace App\Models;
use App\Models\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([ActiveScope::class])]
class User extends Model
{
// ...
}
Laravel упрощает процесс внедрения зависимостей в зависимости от контекста, например, для внедрения драйверов или настроек. Вместо того чтобы вручную настраивать такие зависимости в сервис-провайдерах, Laravel предлагает ряд контекстных атрибутов, которые облегчают эту задачу.
Пример:
namespace App\Http\Controllers;
use Illuminate\Container\Attributes\Auth;
use Illuminate\Container\Attributes\Cache;
use Illuminate\Container\Attributes\Config;
use Illuminate\Container\Attributes\DB;
use Illuminate\Container\Attributes\Log;
use Illuminate\Container\Attributes\Tag;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Database\Connection;
use Psr\Log\LoggerInterface;
class PhotoController extends Controller
{
public function __construct(
#[Auth('web')] protected Guard $auth,
#[Cache('redis')] protected Repository $cache,
#[Config('app.timezone')] protected string $timezone,
#[DB('mysql')] protected Connection $connection,
#[Log('daily')] protected LoggerInterface $log,
#[Tag('reports')] protected iterable $reports,
)
{
// ...
}
}
Laravel также поддерживает автоматическое внедрение текущего пользователя в маршруты:
use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;
Route::get('/user', function (#[CurrentUser] User $user) {
return $user;
})->middleware('auth');
(DeleteWhenMissingModels)
При использовании моделей в заданиях вы можете добавить атрибут DeleteWhenMissingModels
, чтобы задание автоматически удалялось, если указанная модель отсутствует в базе данных.
namespace Acme;
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;
#[DeleteWhenMissingModels]
class ProcessPodcastJob
{
public function __construct(
public Podcast $podcast,
) {}
}
(WithoutRelations)
Атрибут WithoutRelations
позволяет загрузить модель без её связей. Это удобно, если вам не нужны связанные данные в фоновых задачах.
class ProcessPodcastJob
{
public function __construct(
#[WithoutRelations]
public Podcast $podcast,
) {}
}
Новые атрибуты постепенно появляются, что бы упростить работу с кодом.
А какие атрибуты хотели бы видеть вы? 💡
22 677 Монеток
Утро понедельника, вы приходите в офис, наливаете себе первую чашку кофе и готовитесь к очередному погружению в код. У вас стоит задача: отправка запроса на сервер и сохранение результата в базу данных. Всё идёт по плану, и вы начинаете с простого кода:
foreach ($documents as $document) {
$response = Http::post('https://api.example.com/print/', [
'document' => $document->content,
]);
$document->update($response->json());
}
Что произойдет, если нам нужно остановить выполнение этого сценария, например, для обновления кода на сервере? Ведь он может остановиться когда запрос будет отправлен, но еще не был сохранен в базе данных. Как безопасно прервать выполнение, чтобы избежать дублирования данных при повторном запуске сценария?
В таких случаях на помощь приходят сигналы.
Сигналы — это события, которые процесс может прослушивать и на которые он может реагировать. В Laravel, когда вы нажимаете Ctrl+C
в терминале для завершения команды artisan serve
, вы отправляете сигнал SIGINT
текущему процессу, что приводит к его остановке.
В PHP сигналы обрабатываются с помощью функции pcntl_signal
, куда передаются номер сигнала и функция-обработчик. При получении сигнала будет вызван обработчик:
$signalHandler = function () {
// Действия при получении сигнала
};
pcntl_signal(SIGINT, $signalHandler);
Все константы сигналов можно найти на официальном сайте PHP.
Допустим, у нас есть команда, выполняющая длительную операцию:
class ProcessDocumentsCommand extends Command
{
public function handle()
{
$this->info(now()->format('H:i:s') . ' - Начало обработки...');
// Длительная операция
$end = now()->addSeconds(10)->format('s');
while (now()->format('s') !== $end) {
// Эмуляция длительного процесса
}
$this->info(now()->format('H:i:s') . ' - Обработка завершена.');
}
}
Обратите внимание в примере не используется функция
sleep(10)
, так использование сигналов на самом деле сокращает его, так что если ваш код полагается на это, это может быть проблемой!
Если мы запустим эту команду и попробуем прервать её выполнение, нажав Ctrl+C
, то процесс будет остановлен немедленно, и, если в этот момент шёл запрос к серверу, его результаты могут не сохраниться.
Добавим обработку сигналов, используя интерфейс SignalableCommandInterface
из пакета symfony/console
, который доступен в Laravel:
class ProcessDocumentsCommand extends Command implements SignalableCommandInterface
{
/**
* Execute the console command.
*/
public function handle()
{
$this->info(now()->format('H:i:s') . ' - Начало обработки...');
// Длительная операция
$end = now()->addSeconds(10)->format('s');
while (now()->format('s') !== $end) {
// Эмуляция длительного процесса
}
$this->info(now()->format('H:i:s') . ' - Обработка завершена.');
}
/**
* @return array
*/
public function getSubscribedSignals(): array
{
return [SIGINT, SIGTERM];
}
/**
* @param int $signal
* @param false|int $previousExitCode
*
* @return int|false
*/
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
{
$this->info(now()->format('H:i:s') . ' - Завершение работы...');
return false;
}
}
Этот интерфейс добавляет два метода: getSubscribedSignals
и handleSignal
. В первом методе возвращаем массив сигналов, на которые хотим реагировать, а во втором методе обрабатываем сигналы. Возвращая false
в методе handleSignal
, мы позволяем команде завершиться самостоятельно без принудительного прерывания через exit($exitCode)
.
Теперь, запустив команду и нажав Ctrl+C
, вы увидите следующее:
00:07:02 - Начало обработки...
00:07:03 - Завершение работы...
00:07:10 - Обработка завершена.
Добавим флаг, указывающий на необходимость завершения процесса:
class ProcessDocumentsCommand extends Command implements SignalableCommandInterface
{
/**
* @var bool Флаг завершения работы
*/
private bool $shouldExit = false;
public function handle()
{
$documents = Document::limit(10)->get();
foreach ($documents as $document) {
// Если был сигнал завершить работу, то выходим
if ($this->shouldExit) {
exit();
}
$response = Http::post('https://api.example.com/print/', [
'document' => $document->content,
]);
$document->update($response->json());
}
}
public function getSubscribedSignals(): array
{
return [SIGINT, SIGTERM];
}
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
{
$this->shouldExit = true;
return false;
}
}
Laravel предоставляет еще более удобный способ обработки сигналов. Вместо добавления интерфейса SignalableCommandInterface
и реализации его методов, можно воспользоваться методом trap
:
class ProcessDocumentsCommand extends Command
{
/**
* @var bool Флаг завершения работы
*/
private bool $shouldExit = false;
public function handle()
{
$this->trap([SIGINT, SIGTERM], function (int $signal) {
$this->shouldExit = true;
});
$documents = Document::limit(10)->get();
foreach ($documents as $document) {
// Если был сигнал завершить работу, то выходим
if ($this->shouldExit) {
exit();
}
$response = Http::post('https://api.example.com/print/', [
'document' => $document->content,
]);
$document->update($response->json());
}
}
}
Выбор способа обработки сигналов зависит от ваших предпочтений и конкретных требований задачи. Главное, что сигналы позволяют контролировать выполнение процессов, что может быть полезно для завершения действий, удаления временных файлов, закрытия соединений и многого другого.
{message}