«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
Аноним 2021/03/13 12:44:40  №1965889 1
ОП, или кто тоже разбирается. подскажите вот в чем:
Есть приложение, просто роутер который вызывает обработчики.
В обработчике/контроллере я хочу что то сделать, допустим записать лог в файл.
Пусть у меня нет сервис локатора, или DI контейнера.
Как я реализую подобное:
допустим метод класса обрабатывает запрос
...
public function anyMethod(){

//создаю фабрику сервисов Логгирования
//которая принимает аргументом конкретный сервис ( MonologLoggerService ), который имплементирует общий для всех логгинг-сервисов интерфейс, что бы методы в контроллерах были одни и те же, и я смог легко поменять один сервис логгирования на другой.

$serviceFabric = new ServiceFabric ( new MonologLoggerService );

$serviceLogger = $serviceFabric->getInstanseLogger();

$serviceLogger->warning('что нибудь пишу в лог');

}

}

Если абстрагироваться от контейнеров и сервис локатора - такой подход адекватен?
Это фактически мои первые потуги в рамках ООП.
Получается в моем варианте фабрика - это не совсем то что должно быть?
Фабрика возвращать должна один и тот же экземпляр одного конкретного класса - просто с разными настройками?
Или фабрика может возвращать экземпляры разных классов , объединенных общим интерфейсом или абстрактным классом?
Ответы: >>1965892 >>1965963 >>1966063
Аноним 2021/03/13 15:49:24  №1966063 2
>>1965889

Если ты решил спроектировать что-то, то ты должен ответить на вопрос: зачем нужен тот или иной класс? Не проще ли без него?

Вот например, зачем ты сделал фабрику логгеров? Что тебе мешает сразу создать логгер через new MonlogLoggerService?

> Если абстрагироваться от контейнеров и сервис локатора - такой подход адекватен?
Я не понимаю, зачем ты сделал фабрику.

> Фабрика возвращать должна один и тот же экземпляр одного конкретного класса - просто с разными настройками?
> Или фабрика может возвращать экземпляры разных классов , объединенных общим интерфейсом или абстрактным классом?
Бывает и так, и так. Фабрику обычно добавляют для решения таких проблем:

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

Ну и вопрос: чем тебе не подходит DI? То есть, просто передавать логгер в конструктор твоего класса? Зачем ты начал изобретать какие-то странные решения?
Ответы: >>1966437
Аноним 2021/03/14 08:58:15  №1966437 3
>>1966063
>>Вот например, зачем ты сделал фабрику логгеров? Что тебе мешает сразу создать логгер через new MonlogLoggerService?

Изначально вообще у был некий общий сервис логгеров, а в нем в конструкторе был захардкоден вызов конкретного допустим MonlogLoggerService. Идея была в том что бы я в общем сервисе логинов мог менять конкретный логгер, а так как в клиентском коде везде вызывается общий сервис - то можно было поменять логер в одном месте. Фактически как в контейнере хранить сервис.
Но прочел что не комильфо в конструкторе определять зависимости и родилась идея передавать конкретный сервис в конструктор.
Более того - если в ходе выполнения программы мне нужно что бы в одном случае один сервис логгинга вызывался, а в другом другой - то вариант захардокоденного вызова конкретного сервиса не даст это сделать в принципе.

>>Бывает и так, и так. Фабрику обычно добавляют для решения таких проблем:
>>Ну и вопрос: чем тебе не подходит DI?
Я в первую очередь решал такую для себя проблему - получить возможность быстрой замены конкретной реализации какого то сервиса во всем клиентском коде в приложении. И я специально не пользуюсь DI - контейнерами, или банальным сервис-локатором. Хочу лучше прочувствовать что дает не правильный подход. Мне как то проще сделать криво, столкнуться со всеми минусами кривого решения, и тогда я по настоящему понимаю реальные плюсы правильного подхода.

>>Не видя кода ServiceFabric, нельзя сказать, зависит он от MonologLoggerService или нет. На первый взгляд, он зависит, так как требует его передачи в конструктор.
А как же в таком случае должен выглядить полностью не зависимый ServiceFabric?

А как поправить мой пример кода так что бы стало более правильно?

И вот вопрос тогда, вот в DI контейнере я определил сервис, LogService::class. И в клиентском коде я получаю этот объект из контейнера. А после я решил что в контейнере LogService::class должен быть иной логгер, не монолог. Я просто руками в контейнере меняю Монолог на что то другое, и все?
Или есть иной вариант.

Сорр если туплю, я Зандстру начал читать , разделы про ООП, пока что все немного в кучу. У него сервис локатор это класс, который через статические методы получает и возвращает какие то объекты - паттерн Регистр примерно так.
Ответы: >>1966507
Аноним 2021/03/14 11:37:01  №1966507 4
>>1966437

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

- определяем интерфейс логгера LoggerInterface
- делаем несколько логгеров, реализующих этот интерфейс

Далее есть несколько вариантов, как это использовать:

1) с помощью DI

В конструкторе объявляем зависимость от LoggerInterface, сохраняем его в $this->logger, и далее в коде используем его:

$this->logger->warning("Yes");

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

Получается, наш класс готов работать с любым логгером, который ему дадут в конструктор.

2) с помощью ServiceLocator

Делаем в ServiceLocator метод getLogger(): LoggerInterface и используем его:

$this->locator->getLogger()->warning("Yes");

Здесь для подмены логгера надо лишь заменить единственную функцию getLogger().

Про минусы сервис локатора (и про плюсы DI) можно прочесть у меня в уроке про DI: https://github.com/codedokode/pasta/blob/master/arch/di.md

3) с помощью Registry - создаем логгер, кладем в Registry, а в других местах берем его оттуда.

Заметь, что ни один из этих подходов не требует писать фабрику. Мне кажется, она в данной ситуации не нужна.

-----

Подход с интерфейсом работает, если ты пишешь сам код логгеров. Если ты используешь сторонние библиотеки, ты не можешь заставить их соответствовать твоему интерфейсу. В таком случае ты пишешь интерфейс LoggerInterface и адаптеры (вроде MonologAdapter), которые соответствуют интерфейсу и которые передают сообщения сторонней библиотеке.

-----

Теперь про минусы твоего кода:

- ты копипастишь код создания фабрики много раз (каждый раз, когда тебе нужен логгер), а копипаста это плохо (если придется что-то менять, то придется менять в куче мест). Нарушаешь DRY = Dont Repeat Yourself.
- у тебя, чтобы поменять тип логгера, надо его менять в куче мест
- у тебя каждый раз с помощью new создается новый объект фабрики и новый объект логгера. Это может вызвать проблемы, например, если логгер открывает соединеие с БД, то каждый новый логгер будет открывать новое соединение и ты упрешься в лимит на число соединений. Правильнее использовать один объект логгера везде, а не создавать каждый раз новый. Или другой пример: ты хочешь не печатать в лог сообщение, если оно совпадает с предыдущим. Ты не сможешь это сделать, так как каждый раз ты создаешь новый объект логгера, который ничего не знает о предыдущем логгере и предыдущем сообщении.

> И вот вопрос тогда, вот в DI контейнере я определил сервис, LogService::class. И в клиентском коде я получаю этот объект из контейнера. А после я решил что в контейнере LogService::class должен быть иной логгер, не монолог. Я просто руками в контейнере меняю Монолог на что то другое, и все?

Поменять так просто ты не можешь, так как ты в куче месте в коде прописал LogService и код ждет именно этот класс.

Надо делать так: ты объявляешь логгер в контейнере под именем LoggerInterface, и везде в коде просишь LoggerInterface::class. А в контейнере задаешь, какой именно класс будет использован.

----

На самом деле, тут есть еще второй вариант, без интерфейсов. Ты можешь сделать класс-посредник, LoggerService, вся суть которого в том, что он передает сообщения конкретной бибилиотеке (например, Monolog). Везде ты внедряешь объект этого класса в качестве логгера. Тогда для замены логгера тебе будет достаточно поменять код в LoggerService.