DevCraft Документации
РазработкиDevCraft Dev ToolsGuides

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

Fluent-сеттеры через #[With], #[WithItem], AbstractWith и WithHandler.

Атрибуты #[With] и #[WithItem] генерируют fluent-сеттеры для private (или protected) свойств. Наследуйте AbstractWith, чтобы __call направлял виртуальные методы в WithHandler.

Быстрый старт

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');

Канонический пример (с геттерами):

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

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

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

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

    public function page(): ?int
    {
        return $this->page;
    }

    public function tags(): array
    {
        return $this->tags;
    }

    public function labels(): array
    {
        return $this->labels;
    }
}

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

$query->page();   // 1
$query->tags();   // ['proxy']
$query->labels(); // ['status' => 'ready']

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

AbstractWith

Devcraft\Abstracts\AbstractWith — рекомендуемый базовый класс:

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

    throw new BadMethodCallException(
        sprintf('Call to undefined method %s::%s()', $this::class, $methodName)
    );
}

Можно вызывать WithHandler вручную в своём __call, но наследники AbstractWith получают маршрутизацию бесплатно.

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

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

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

#[WithItem] дополнительно требует non-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 (без coercion string→int для typed properties).
  • Возвращает $this.

#[WithItem] — append или map

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

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

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

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

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

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

Ключи map могут быть только int и/или string.

Вместе с #[With]

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

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

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

Строка или список строк (union):

#[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 нельзя комбинировать с другими типами в одном union.
  • float не принимает integers (без numeric coercion).
  • void / never отклоняются.
  • Неизвестные имена классов → LogicException при сборке метаданных.

Ошибки runtime vs конфигурации

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

Неверные аргументы item/map проверяются до мутации — неудачный вызов не частично обновляет массив.

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

WithHandler строит и кэширует метаданные операций на runtime-класс при первом использовании. Кэш хранит closures, привязанные к declaring class, а не к экземплярам.

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

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

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