Danyloff

PHP Annotations улучшают ваш Laravel проект


  • ·

· ·

С развитием PHP потребность в аннотациях стремительно падает. Раньше типы возвращаемых значений, свойств, параметров и других неочевидных вещей объявлялись аннотациями в комментарии стиля PHPDoc.

На сегодняшний день в текущей версии PHP(8.2) уже можно объявлять типы свойств, параметров, возвращаемых значений методов/функций, есть поддержка типа Enum и других полезные улучшения но, в PHP все так же много осталось «магии» которую с ловкостью используют различные фреймворки. Laravel не исключение. Так как Laravel использует слишком много «магии» порог вхождения в проект для новых разработчиков повышается. По этому в данной статье, мы, с помощью аннотаций, будем понижать тот самый порог.

Для тех кто не знаком что такое аннотации, читайте статью ниже

Модели(Model)

В Laravel для аттрибутов модели используется магические методы PHP __get(), __set(). Взглянем на типичную модель User

class User extends Model
{
    protected $table = 'users';
    protected $guarded = ['id'];
}

В данной модели User мы видим… ничего… абсолютно никакой информации по атрибутам этой модели. Чтобы узнать атрибуты новому разработчику приходится идти в таблицу, смотреть схему и держать в голове эти знания. Все это занимает время, и не мало, особенно когда моделей много. Но что же делать?! Давайте попробуем применить аннотации

/**
 * @property int $id
 * @property string $first_name
 * @property string $surname
 * @property int $age
 * @property string $password
 * @property bool $active
 * @property string $role admin|user
 * @property DateTime|null $created_at
 * @property DateTime|null $updated_at
 */
class User extends Model
{
    protected $table = 'users';
    protected $guarded = ['id'];
}

Вот теперь стало лучше. Главное не забывать редактировать аннотации если модифицируете схему. А что на счет методов?! И для методов есть свои аннотации

/**
 * @property int $id
 * @property string $first_name
 * @property string $surname
 * @property int $age
 * @property string $password
 * @property bool $active
 * @property string $role admin|user
 * @property DateTime|null $created_at
 * @property DateTime|null $updated_at
 * 
 * @method static Builder byAge(int $age)
 * @method static Builder byActive(bool $active = true)
 */
class User extends Model
{
    protected $table = 'users';
    protected $guarded = ['id'];

    public function scopeByAge(Builder $query, int $age): void
    {
        $query->where('age', $age);
    }

    public function scopeActive(Builder $query, bool $active = true): void
    {
        $query->where('active', $active);
    }
}

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

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

/**
 * @property int $id
 * @property string $first_name
 * @property string $surname
 * @property int $age
 * @property string $password
 * @property bool $active
 * @property string $role admin|user
 * @property DateTime|null $created_at
 * @property DateTime|null $updated_at
 *
 * @property Collection<Photo> $photos
 * @property Collection<File> $files
 * @property Document|null $document
 *
 * @method static Builder byAge(int $age)
 * @method static Builder byActive(bool $active = true)
 */
class User extends Model
{
    protected $table = 'users';
    protected $guarded = ['id'];

    public function photos(): HasMany
    {
        return $this->hasMany(Photo::class);
    }

    public function files(): HasMany
    {
        return $this->hasMany(File::class);
    }

    public function document(): HasOne
    {
        return $this->hasOne(Document::class);
    }

    public function scopeByAge(Builder $query, int $age): void
    {
        $query->where('age', $age);
    }

    public function scopeActive(Builder $query, bool $active = true): void
    {
        $query->where('active', $active);
    }
}

Давайте теперь рассмотрим что может быть еще не очевидно в вашем коде что можно улучшить с помощью аннотаций. Допустим у вас есть UserService, с методом getAll() который возвращает массив пользователей.

final class UserService
{
    public function getAll(): array
    {
        return User::all()->toArray();
    }
}

Для массивов есть специальный синтасис

final class UserService
{
    /**
     * @return User[]
     */
    public function getAll(): array
    {
        return User::all()->toArray();
    }
}

Теперь станет понятно клиентам вашего сервиса и вашей IDE что находится внутри возвращаемого массива. Для итераторов применяется синтаксис обобщения.

final class UserService
{
    /**
     * @return Iterator<int, User>
     */
    public function getAll(): Iterator
    {
        return User::all()->getIterator();
    }
}

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

final class UserService
{
    /**
     * @return array<array{
     *     id: int,
     *     name: string
     * }>
     */
    public function getAll(): array
    {
        return User::all()->map(fn (User $user) => [
            'id' => $user->getKey(),
            'name' => $user->name
        ])->toArray();
    }

    /**
     * @return array{
     *     id: int,
     *     name: string,
     *     age: int
     * }
     */
    public function find(int $id): array
    {
        return User::find($id)->only('id', 'name', 'age');
    }
}

В этом примере мы описали схему возвращаемого массива для методов getAll() и find().

Заключение

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


Так же интересно

Шаблон Identity Map с примерами на PHP

Шаблон Identity Map - это паттерн проектирования, который используется для управления объектами в памяти приложения. Он позволяет избежать повторного создания...