DevCraft Документации
Руководства

Атрибуты With v1.1.0

Цепочки with* через #[With], #[WithItem], AbstractWith и WithHandler.

Введение

Атрибуты #[With] и #[WithItem] дают методы with… для скрытых свойств (private / protected). Класс наследуете от AbstractWith: вызов неизвестного метода сначала идёт в WithHandler (with*), затем в lombok-php (get* / set* / is*).

Зачем: объект-запрос или билдер собирается цепочкой без ручных сеттеров и без публичных полей.

Предварительные требования

По шагам

Объявить класс

use Devcraft\Abstracts\AbstractWith;
use Devcraft\Attributes\With;
use Devcraft\Attributes\WithItem;

final class Query extends AbstractWith
{
    #[With]
    private ?int $page = null;

    #[With]
    private ?string $starting_after = null;

    #[With, WithItem('string')]
    private array $tags = [];

    #[WithItem(['int', 'string'], ['string', 'null'])]
    private array $labels = [];
}

Собрать цепочку

$query = (new Query())
    ->withPage(2)
    ->withStartingAfter('cursor')
    ->withTags(['a'])
    ->withTagsItem('b')
    ->withLabelsItem('status', 'ready');

Пример с #[Getter]:

use Lombok\Getter;
use Devcraft\Abstracts\AbstractWith;
use Devcraft\Attributes\With;
use Devcraft\Attributes\WithItem;

#[Getter]
final class Query extends AbstractWith
{
    #[With]
    private ?int $page = null;

    #[With, WithItem('string')]
    private array $tags = [];

    #[WithItem('string', ['string', 'null'])]
    private array $labels = [];
}

$query = (new Query())
    ->withPage(1)
    ->withTagsItem('proxy')
    ->withLabelsItem('status', 'ready');

$query->getPage();  // 1
$query->getTags();  // ['proxy']
$query->getLabels(); // ['status' => 'ready']

См. также: AbstractWith, With, WithItem, WithHandler, Getter и Setter.

Как устроен AbstractWith

Devcraft\Abstracts\AbstractWith наследует \Lombok\Helper. Рекомендуемый вид __call:

public function __call(string $methodName, array $arguments): mixed
{
    if (WithHandler::handles($this, $methodName)) {
        return WithHandler::call($this, $methodName, $arguments);
    }

    return parent::__call($methodName, $arguments);
}

with* обрабатывает WithHandler. Остальное — Lombok. Неизвестный метод даёт BadMethodCallException из Helper.

Если у наследника свой __construct(), вызовите parent::__construct(), чтобы Getter/Setter подключились сразу. Методы with* от этого вызова не зависят.

Можно вызывать WithHandler вручную в своём __call, но тогда остаток нужно самим отдать в Lombok.

Ограничения свойств

Атрибуты допустимы только если свойство:

  • не public (private или protected);
  • не static;
  • не readonly.

#[WithItem] дополнительно требует не-nullable свойство типа array.

Неверная настройка бросает LogicException при первой сборке метаданных (первый handles() / call() для класса).

Имена методов

Имя свойства переводится в StudlyCase:

СвойствоМетод #[With]Метод #[WithItem]
$pagewithPage($value)
$starting_afterwithStartingAfter($value)
$tagswithTags($array)withTagsItem($item)
$labelswithLabelsItem($key, $value)

Поиск имени метода без учёта регистра (WITHPAGE сработает). Имена должны быть уникальны по цепочке наследования; столкновения (в том числе только по регистру, $name / $NAME) → LogicException.

#[With] — заменить значение целиком

#[With]
private ?int $page = null;

$query->withPage(3);    // $page = 3
$query->withPage(null); // допустимо, если свойство nullable
  • Ровно один аргумент.
  • Обычная проверка типа PHP (строку в int само не приведёт).
  • Возвращает $this.

#[WithItem] — элемент списка или карты

Список (один дескриптор типа)

#[WithItem('string')]
private array $tags = [];

$query->withTagsItem('proxy'); // $tags[] = 'proxy'

Карта (два дескриптора)

#[WithItem('string', ['string', 'null'])]
private array $labels = [];

$query->withLabelsItem('status', 'ready');
$query->withLabelsItem('status', null); // заменить

Ключи карты — только int и/или string.

Вместе с #[With]

#[With, WithItem('string')]
private array $tags = [];

$query->withTags(['a', 'b']); // заменить весь массив
$query->withTagsItem('c');    // добавить элемент

Дескрипторы типов

Строка или список строк (объединение типов):

#[WithItem('string')]
#[WithItem(['int', 'string'])]
#[WithItem(ItemContract::class)]
#[WithItem(['int', 'string'], ['string', 'null'])]

Встроенные: string, int, float, bool, true, false, null, array, object, iterable, callable, mixed.

Также: имена классов, интерфейсов и enum, существующие в runtime.

Замечания:

  • mixed нельзя комбинировать с другими типами в одном объединении.
  • float не принимает целые числа «как float» без явного приведения.
  • void / never отклоняются.
  • Неизвестные имена классов → LogicException при сборке метаданных.

Ошибки: настройка vs вызов

СитуацияИсключение
Public / static / readonly свойствоLogicException
Nullable или не-array цель WithItemLogicException
Плохой набор дескрипторов / пустое объединение / неверные типы ключейLogicException
Повтор атрибута / столкновение виртуального методаLogicException
Неинициализированный array при добавлении элементаLogicException
Неверное число аргументовArgumentCountError
Значение не проходит проверку дескриптораTypeError
Неизвестный метод через WithHandler::callBadMethodCallException

Неверные аргументы проверяются до изменения массива — неудачный вызов не оставляет «половину» записи.

Кэш метаданных

WithHandler один раз собирает и кэширует описание операций на runtime-класс. В кэше — замыкания, привязанные к классу объявления свойства, а не к экземпляру.

Наследование

Атрибуты на private/protected свойствах родителей видны на дочерних экземплярах. Запись идёт через declaring class, поэтому private-свойства родителя остаются доступны через виртуальные with*.

Связанные разделы

На этой странице