2

Currently we need to interface with a REST API. The only problem we have implementing this is choosing the right approach.

We’ve done this by using Resources – for example a Photo Resource that has certain methods to interface with the REST API. The only problem with this approach is that each time we use a certain Resource we need to inject an Http Client that is being used to perform that task.

So every part of the application that wants to use a certain Resource need to have an extended knowledge of the Http Client. Is there a certain way to solve this problem without using a Singleton – as there is only one instance of the Http Client? Maybe a certain Design Pattern?

For now we make usage of PHP to accomplish this task in combination with Guzzle Http; but this problem is more about Software Architecture.

This is an example of our current implementation:

class Photo
{
    /**
      * @var Client $httpClient
      */
    private $httpClient;

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

    public function getByName($photoName)
    {
        # Use $httpClient
    }
}
2

The solution to your problem is actually quite simple, factories. The recommended approach would be at least 2 factory methods, one for the Guzzle client, the other one for the Photo class.

class GuzzleFactory
{
    protected $config;

    protected $guzzleClient = null;

    public function __construct(GuzzleConfiguration $config)
    {
        $this->config = $config;
    }

    public function createGuzzleHttpClientWeakSingleton()
    {
        if ($this->guzzleClient === null) {
            $this->guzzleClient = new \Guzzle\Client($this->config->asArray());
        }

        return $this->guzzleClient;
    }
}

class PhotoFactory
{
    protected $guzzleFactory;

    public function __construct(GuzzleFactory $guzzleFactory)
    {
        $this->guzzleFactory = $guzzleFactory;
    }

    public function createPhotoService()
    {
        return new Photo($this->guzzleFactory->createGuzzleHttpClientWeakSingleton());
    }
}

And you're done. Whenever you need an instance of the Photo class, create it using the PhotoFactory and you will never have more than one instance of http client during a single request (additional configuration is up to you).

Splitting your code into two parts, logic and creational design patterns, is a great way to ensure dependencies are only created as many times as needed.

In case you're wondering why I chose to go the way of a weak singleton instead of the classic static path, the reason is unit testing, but if strong singleton with static accessor suits your needs more, you can use that one instead and not being forced to pass the GuzzleFactory, as it will be globally available.

| improve this answer | |
  • Your Guzzle example is completely opaque to me (as are most foobar examples). However, I upvoted your answer anyway as I, too, believe that some sort of Factory is the correct solution here. – Robert Harvey Mar 14 '16 at 22:00
0

The nicest way is to use Dependency Injection. Once set up, your code will look almost the same, just need to add type hint in your Photo constructor:

public function __construct(Client $client) {
    $this->client = $client;
}

Then in your controller where you want to show the photo, you will have something like this:

class Controller {

    public function __construct() {
        // photo here will already have HttpClient injected to it.
        this->photo = DI::get('Photo');
    }

    public function get(id) {
        return $this->photo->get(id);
    }
}

DI will pass the client to your Photo constructor. How to configure DI to set up the objects depends on the implementation you use. Here Zend\DI introduction is one example of DI container.

| improve this answer | |
  • 1
    It's unclear to me how DI would improve the op's specific situation. – Robert Harvey Mar 14 '16 at 21:59
  • @RobertHarvey I added example controller to show how it'd be useful. – imel96 Mar 14 '16 at 23:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy