Атрибуты 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] |
|---|---|---|
$page | withPage($value) | — |
$starting_after | withStartingAfter($value) | — |
$tags | withTags($array) | withTagsItem($item) |
$labels | — | withLabelsItem($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 цель WithItem | LogicException |
| Плохой набор дескрипторов / пустое объединение / неверные типы ключей | LogicException |
| Повтор атрибута / столкновение виртуального метода | LogicException |
| Неинициализированный array при добавлении элемента | LogicException |
| Неверное число аргументов | ArgumentCountError |
| Значение не проходит проверку дескриптора | TypeError |
Неизвестный метод через WithHandler::call | BadMethodCallException |
Неверные аргументы проверяются до изменения массива — неудачный вызов не оставляет «половину» записи.
Кэш метаданных
WithHandler один раз собирает и кэширует описание операций на runtime-класс. В кэше — замыкания, привязанные к классу объявления свойства, а не к экземпляру.
Наследование
Атрибуты на private/protected свойствах родителей видны на дочерних экземплярах. Запись идёт через declaring class, поэтому private-свойства родителя остаются доступны через виртуальные with*.