4

New to Symfony2, I'm building an app that uses an external API to get data. I created a lot of client classes to retrieve and transform each entity from the API, and I defined those classes as services - e.g., I have a FooClient with methods like getAll() or getThoseThatInterestMe($me), which return data from the API.

Now I wanted to create a ApiClientFacade class, which acts as an interface in front of all the XxxClient classes, following the Facade Pattern - e.g., this facade class would have a method getAllFoo(), which in turn would call FooClient::getAll(), and so on...

I could define my facade class as a service as well, but it'd have too many dependencies - I have around 30 client classes. Also, afaik with this approach I'd be loading all 30 dependencies every time, while most of the times I'd only need one dependency...

So, is there a better way to do this?

AntonioCS
  • 8,335
  • 18
  • 63
  • 92
MikO
  • 18,243
  • 12
  • 77
  • 109
  • 1
    While generally discouraged, injecting the complete container is acceptable sometimes. With 30+ potential dependencies, just inject the container and pull out the dependencies as necessary. – Cerad Mar 29 '15 at 23:02
  • I achieved something similar to what you are asking for in Symfony2. I used the **Factory pattern**, an **abstract** base service and many **tagged** child services. Then used a **CompilerPass** that gets added to the container in the **bundle's build method** in order to add an **argument** to the class' definition of the tagged services. This argument is the class name, so the factory can instantiate it. In my case all the child services have the same dependencies, declared in the parent. I'm not sure if this would (have) help(ed) you. – Quiquetas Dec 15 '20 at 03:37

1 Answers1

4

Use additional ApiClientFactory to move responsibility about "instantiation of ApiClient objects" from your ApiFacade class (which is your initial idea, as I understood).

In some pseudo-php code my idea is:

$api = new ApiFacade(new ApiClientFactory);
$api->sendNotificationAboutUserLogin('username', time());

An example of method:

class ApiFacade {
  private $apiFactory;

  public function __construct(ApiClientFactory $factory)
  {
      $this->apiFactory = $factory;
  }    
  public function sendNotificationAboutUserLogin($username, $timeOfOperation)
    {
      return $this->apiFactory
          ->createApi('User')
          ->post(
              'notifications',
              array('operation' => 'login', 'username' => $username, 'timeOfOperation' => $timeOfOperation)
          );
    }
}

In this case your Facade class stays injectable (testable), but also becomes simpler instantiatable (you don't need to pass all dependencies into it anymore).

The ApiClientFactory should look like that:

class ApiClientFactory {
    private $apiBaseUrl;
    public function __construct($apiBaseUrl)
    {
      $this->apiBaseUrl = $apiBaseUrl;
    }
    public function createApi($apiName)
    {
      switch ($apiName) {
        case 'User': return new \My\UserApi($this->apiBaseUrl);
        default: // throw an exception?
      }
    }
}
YevKov
  • 66
  • 2
  • As your idea makes sense for a general scenario, that doesn't work for my scenario with Symfony2 services - basically if I try to do something like that, I'd need the dependencies in the factory class, so I have the same problem in a different place... – MikO Mar 29 '15 at 23:52
  • But my solution is to do not pass your "Api" classes to constructor of factory, instead of that: instantiate them upon request (when ->createApi() call comes to factory). In this case you don't have this problem. – YevKov Mar 30 '15 at 08:21
  • Problem is a Symfony service wouldn't be instantiate with `new \My\UserApi($this->apiBaseUrl);`! but injecting the `UserApi` into the class, or getting it from the `ServiceContainer`... – MikO Mar 30 '15 at 08:42
  • Can you please show an example of such approach? I don't see the point, despite I would like to help you find the solution in the middle. – YevKov Mar 30 '15 at 18:40
  • 1
    Take a look at this: http://symfony.com/doc/current/book/service_container.html Basically in Symfony you can define classes as services, which are "registered" in a service container, and you get them form the container with `$container->get('service.name')`. You can also set services as dependencies for other services, and in that case the dependencies get "injected" as constructor parameters in the dependant class. So, when you do `new \My\UserApi()`, that's not the way you'd create a Symfony service... Anyway, thanks for your help, I upvoted your answer ;) – MikO Mar 30 '15 at 19:34