Как отрефакторить классы с неоднородными конструкторами?

Отрефакторинг классов с неоднородными конструкторами в Symfony может быть достаточно сложной задачей, но важно для поддержки кодовой базы на должном уровне и обеспечения чистоты кода. Такие классы часто называются "классами с зависимостями". В этом ответе я расскажу о нескольких распространенных практиках, которые помогут вам в этом процессе.

1. Внедрение зависимостей с использованием контейнера Symfony:

Одним из основных преимуществ Symfony является его контейнер зависимостей. Контейнер позволяет определить зависимости в конструкторе класса с помощью типизированных аргументов и использовать автоматическое разрешение зависимостей. Это позволяет увязать классы, определяющие зависимости, с классами, которые их используют. При этом все экземпляры классов будут создаваться через контейнер.

Например, рассмотрим следующий конструктор класса Foo:

   public function __construct(Bar $bar, Baz $baz, string $qux)
   {
       // ...
   }

С помощью контейнера можно определить, какие классы будут внедряться в конструктор Foo:

   # services.yaml
   services:
       AppBar:
           # ... определение зависимостей
       AppBaz:
           # ... определение зависимостей
       AppFoo:
           arguments:
               $bar: '@AppBar'
               $baz: '@AppBaz'
               $qux: 'some value'

После этого можно получить экземпляр класса Foo из контейнера:

   $foo = $container->get(Foo::class);

При этом контейнер автоматически разрешит зависимости и создаст экземпляры классов Bar и Baz, передавая их в конструктор Foo. Параметр $qux также будет передан без изменений.

Такой подход позволит создать однородные конструкторы, что значительно облегчит работу с классами и их поддержку.

2. Разделение классов:

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

Например, рассмотрим следующий класс Foo с неоднородными конструкторами:

   class Foo
   {
       public function __construct(string $type, Bar $bar = null, Baz $baz = null)
       {
           // ...
       }
   }

Мы можем разделить этот класс на два класса, каждый из которых отвечает за свой конструктор:

   class FooWithBar implements FooInterface
   {
       private $bar;

       public function __construct(Bar $bar)
       {
           $this->bar = $bar;
       }
   }

   class FooWithBaz implements FooInterface
   {
       private $baz;

       public function __construct(Baz $baz)
       {
           $this->baz = $baz;
       }
   }

После этого мы можем решить, какие объекты Bar или Baz будут передаваться в конструктор Foo, и использовать только соответствующий класс.

3. Использование сборщиков зависимостей:

Еще одним способом работы с классами с неоднородными конструкторами является использование сборщиков зависимостей, таких как PHP-DI или Symfony DI. Эти инструменты позволяют внедрять зависимости в конструкторы классов с помощью аннотаций или конфигурационных файлов. В результате у нас получаются однородные конструкторы, которые легко управлять и изменять.

Например, с использованием PHP-DI мы можем переписать класс Foo следующим образом:

   class Foo
   {
       private $bar;
       private $baz;
       private $qux;

       #[Inject]
       public function __construct(Bar $bar, Baz $baz, #[SomeAnnotation] string $qux)
       {
           $this->bar = $bar;
           $this->baz = $baz;
           $this->qux = $qux;
       }
   }

В этом примере мы используем аннотацию #[Inject], чтобы указать сборщику зависимостей, что класс Foo должен быть создан с указанными зависимостями. Параметр $qux также имеет аннотацию #[SomeAnnotation], чтобы указать, что его значение должно быть взято из соответствующего источника.

При этом в настройках сборщика зависимостей мы можем определить, какие зависимости должны быть внедрены в конструктор класса Foo.

Это позволяет нам иметь однородные конструкторы и гибкость в управлении зависимостями.

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