Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як розширити Str, Stringable й Arr власними методами через макроси і які в цього ризики?

Багато класів Laravel використовують трейт Macroable: Str, Stringable, Arr, Collection, Request, Builder, Http, Number, Uri та інші. Макрос додає метод «ззовні» без успадкування.

// AppServiceProvider::boot()
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

Str::macro('phone', function (string $value): string {
    return preg_replace('/\D+/', '', $value);
});

Stringable::macro('phone', function (): Stringable {
    return new Stringable(Str::phone($this->value));
});

Str::phone('+38 (067) 123-45-67');        // '380671234567'
str('+38 (067) 123-45-67')->phone();      // Stringable

Важливі деталі:

  • Str і Stringable - різні класи: макрос на Str не з'являється у fluent-ланцюжку. Для обох стилів потрібні два макроси;
  • $this у замиканні прив'язується до екземпляра (для Stringable - до об'єкта з властивістю value), а в статичному виклику - ні;
  • mixin реєструє кілька макросів з класу, кожен публічний чи захищений метод якого повертає замикання:
Str::mixin(new StrMixin);

Ризики:

  1. Конфлікт з майбутніми методами фреймворку. Макроси викликаються через __call / __callStatic, тобто лише якщо справжнього методу немає. Якщо в наступній версії Laravel з'явиться Str::phone() з іншою поведінкою, ваш макрос мовчки перестане викликатися. Захист - префікси (Str::appPhone) і тести на поведінку макросів;
  2. Невидимість для інструментів: IDE й PHPStan не бачать макросів без додаткових анотацій чи ide-helper; автодоповнення зникає, а аналізатор лається на «невідомий метод»;
  3. Глобальний стан: макрос зареєстрований для всього процесу. У пакеті він може зіткнутися з макросом іншого пакета чи застосунку з тим самим ім'ям - перемагає той, хто зареєструвався останнім;
  4. Прихована логіка: бізнес-правила в макросах (Str::orderNumber()) важче знайти й тестувати, ніж у звичайному класі.

Коли макрос доречний: невелика загальна утиліта, що природно продовжує API класу (форматування телефону, нормалізація пробілів) і використовується в багатьох місцях.

Коли краще звичайний клас чи value object: доменна логіка, щось із залежностями, чи метод, потрібний в одному модулі. PhoneNumber::fromString($raw)->normalized() явніший, тестується окремо й підтримується IDE без підказок.

Докладніше в документації: Колекції: розширення колекцій

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання