symfony: загружать много служб в одну службу?

#symfony #design-patterns

#symfony #шаблоны проектирования

Вопрос:

В будущем веб-приложении мне нужно извлекать данные из разных API (Soap / Rest / Custom …), А иногда смешивать результаты из 2, 3 или всех API одновременно.

Для каждого API я буду выполнять одинаковые действия, например: getLastDatas, setOneData, putDatas2externalDB и т.д…

В контроллере я мог бы сделать что-то вроде:

 $someFreshResults = $this->get('app.products')->getFreshResults($para1, $para2, $arrayOfAPI);
  

с помощью $arrayOfAPI создается список из 1, 2 или всех имен API.

Поскольку все API разные (и иногда нетривиальные), я думаю, было бы неплохо объявить сервис по API, но очень плохая и уродливая идея внедрить все эти сервисы в службы app.products и выполнить для них цикл с помощью чего-то вроде :

 public function getFreshResults($para1, $para2, $API) {
    foreach($API as $oneApi) {
        oneResult = $this->get($oneAPI)->getLastDatas($para1);
        ... work with oneResult ...
    }
}
  

Как правильно это сделать в приложении symfony?

Комментарии:

1. Знаете ли вы, что вы можете использовать объявление аргументов в своих службах. yml-файл для внедрения сервиса в другой с аннотацией «@MyService» ?

2. Это не столько вопрос Symfony, сколько вопрос о правильных шаблонах проектирования, и поэтому, возможно, вполне основан на мнениях.

Ответ №1:

Я бы предложил написать отдельные сервисы (но реализующие общий интерфейс) для каждого API, который вы хотите использовать, а затем написать сервис, который объединяет эти отдельные клиенты API. Ваша служба агрегации может иметь такой API:

 <?php

class MyAggregrator
{
    /** @var ApiClientInterface[] */
    private $clients = [];

    public function registerClient(string $name, ApiClientInterface $client)
    {
        $this->clients[$name] = $client;
    }

    public function getProducts()
    {
        foreach ($this->clients as $name => $client) {
            ...
        }
    }
}
  

Регистрация вашей службы агрегации в контейнере может выглядеть примерно так:

 services:
  my_aggregator:
    class: MyAggregrator
    calls:
      - [registerClient, ["foo", "@api1"]]
      - [registerClient, ["bar", "@api2"]]
  

Вы могли бы даже немного упростить эту конфигурацию, пометив своих клиентов API, а затем написать компиляторный проход, который извлекает каждую помеченную службу и регистрирует их в службе агрегации. Для получения дополнительной информации о помеченных службах см. http://symfony.com/doc/current/service_container/tags.html

Комментарии:

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

2. Нет, проход компилятора выполняется один раз при сборке контейнера, а не для каждого запроса. Вы могли бы передать API, которые вы хотите использовать в качестве аргумента метода или чего-то еще.

Ответ №2:

Может быть, вы могли бы создать класс с помощью api. И объявляйте каждый класс api как сервис.

В ваших службах.yml-файл

 services:
    app.products:
        class:        AppBundleProducts
        arguments:    ["@api1", "@api2", ...]
  

А затем в вашем Products.php класс обслуживания:

 class Products
{
    private $api1;
    private $api2;

    public function __construct($api1, $api2) //you must declare in the constructor the arguments declared in services.yml
    {
        $this->api1 = $api1;
        $this->api2 = $api2;
    }
  

Комментарии:

1. Это то, что я сделал в первой версии. Но что, если у меня 20 или 30 разных API, но иногда действительно нужно вызывать только 1 или 2?