Mauro Mauro - 7 months ago 13
PHP Question

Inject Provider Service or Container. Good practice

My question is more a doubt about what's the best solution in terms of good practice.

Imagine we need to implement a handler depending in one parameter coming from an entity.
Let me explain

OPTION1


  • Service/Provider/HSBCProvider

  • Service/Provider/BarclaysProvider

  • Service/BankHandler

  • Controller/BankController



BankHandler

class BankHandler {
private $container;
function __contructor($container) {
$this->container = $container;
}
function create($account) {
$provider = $this->container->get('service.provider.'.$account->getName());
$provider->do();
}
}


BankController Action

public function createAction($id) {
$account = $repository->getAccount($id);
$bankHandler = $this->get('service.bank_handler');
$bankHandler->create($account);
}


OPTION 2


  • Service/Provider/HSBCProvider

  • Service/Provider/BarclaysProvider

  • Controller/BankController



BankController Action

public function createAction($id) {
$account = $repository->getAccount($id);
$bankProvider = $this->get('service.provider.'.account->getName());
$bankProvider->do();
}


With this option the BankHandler class is not longer needed

I'm simplifying all the logic inside the create and the action.

I don't like Option 1 cos I'm injecting the container.
I don't like Option 2 cos action controller have too much logic (fat controller?).

Any other better solution?

Answer

Create a registry service with all of your providers, inject that into the handler and get the provider from that. Something along the lines of...

class BankProviderRegistry
{
    /**
     * @var array|BankProviderInterface[]
     */
    private $providers = array();

    public function __construct(array $providers = array())
    {
        foreach($providers as $name => $provider) {
            $this->addProvider($name, $provider);
        }
    }

    /**
     * Add provider to registry
     *
     * @param string $name
     * @param BankproviderInterface $provider
     */
    public function addProvider($name, BankProviderInterface $provider)
    {
        $this->provider[$name] = $provider;
    }

    /**
     * Get provider by name
     *
     * @param string $name
     * @return BankproviderInterface
     */
    public function getProvider($name)
    {
        if (!isset($this->providers[$name])) {
            throw new \Exception(sprintf('Provider "%s" is not registered', $name));
        }

        return $this->providers[$name]; 
    }
}

You could either add these in your services like..

app.registry.bank_provider:
    class: AppBundle\Registry\BankProviderRegistry
    arguments:
        - 
            'a name': '@app.provider.a_name' 
            'another name': '@app.provider.another_name' 

.., in your DI Extension, in a compiler pass, or maybe somewhere else I haven't thought of.

Then you pass this into your handler and get the provider like..

class BankHandler
{
    private $registry;

    public function __contruct(BankProviderRegistry $registry) {
        $this->registry = $registry;
    }

    public function create($account) {
        $provider = $this->registry->getProvider($account->getName());
        $provider->do();
    }
}