ZF2 config from module with no controllers

2019-03-31 08:54发布

问题:

I'm currently in the process of building a module to serve as a re-usable library throughout multiple projects, however due to it being a library there isn't a need for a controller. What I'm trying to do for instance is create a zf2 module for Marketo soap API for instance. User adds their keys and wsdl location in /ROOT/config/autoload/local.php. The configuration would include something like 'marketo'=>array(),

Now the problem that I'm having is I want to give myself and others using the module the ability to do something like...

$marketo = new \Marketo\Client\Client();

and inside the \Marketo\Client\Client() class have the constructor read the array key of $config['marketo'];

I could however put all of this in an ini file, but I would prefer to keep it similar to how everything else in zf2 is configuration wise.

So to summarize I would like to get an array key of the merged zend configuration to use inside the class something like...

class Marketo{
    private $key;
    private $pass;
    public function __construct(){
        $c = \Zend\Config\Config('marketo);
        $this->key = $c['key'];
        $this->pass = $c['pass'];
    }
}

============ Fully working solution as of ZF 2.1.1 per the answers below =============

Module structure looks as follows (Using a new example so I could start fresh) + indicates directory name - indicates filename

modules
  - Application /* Standard setup with an IndexController */
  - Cybersource /* The new module to be added */
      + config
         - module.config.php
      + src
         + Cybersource
            + Client
               - Client.php
            + ServiceFactory
               - ClientServiceFactory.php
      - Module.php
      - autoload_classmap.php

module.config.php

return array(
    'service_manager' => array(
        'factories' => array(
            'Cybersource\Client\Client' => 'Cybersource\ServiceFactory\ClientServiceFactory',
        )
    ),
    'cybersource' => array(
        'Endpoint' => 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor', // test environment
        'WSDL' => 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.80.wsdl',
        'TXKey' => '',
        'MerchID' => '',
    ),
);

Client.php

namespace Cybersource\Client;

class Client {

    private $config;

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

    public function getConfig() {
        return $this->config;
    }

}

ClientServiceFactory.php

namespace Cybersource\ServiceFactory;

use Cybersource\Client\Client;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ClientServiceFactory implements FactoryInterface {

    public function createService(ServiceLocatorInterface $serviceLocator) {
        $config = $serviceLocator->get('Config');

        return new Client($config['cybersource']);
    }

}

Module.php

namespace Cybersource;
use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface {

    public function getAutoloaderConfig() {
        return array(
            'Zend\Loader\ClassMapAutoloader' => array(
                __DIR__ . '/autoload_classmap.php',
            )
        );
    }

    public function getConfig() {
        return include __DIR__ . '/config/module.config.php';
    }

}

autoload_classmap.php

<?php
// Generated by ZF2's ./bin/classmap_generator.php
return array(
    'Cybersource\Module'                              => __DIR__ . '/Module.php',
    'Cybersource\Client\Client'                       => __DIR__ . '/src/Cybersource/Client/Client.php',
    'Cybersource\ServiceFactory\ClientServiceFactory' => __DIR__ . '/src/ServiceFactory/ClientServiceFactory.php',
);

Once the module has been activated in the application.config.php I could then use it in my IndexController on my Application Module by using:

<?php


namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController {

    public function indexAction() {
        $c = $this->getServiceLocator()->get('Cybersource\Client\Client');
        $conf = $c->getConfig();
        var_dump($conf);
        return new ViewModel();
    }

}

The above controller output would dump the output of the configuration as I added a function called getConfig() to the Client class for display / testing purposes.

Thanks again for all the help.

回答1:

You would probably define a Module like following:

<?php

namespace Marketo;

use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface
{
    public function getConfig()
    {
        return array(
            'service_manager' => array(
                'factories' => array(
                    'Marketo\Client\Client' => 'Marketo\ServiceFactory\ClientServiceFactory',
                ),
            ),
            'marketo' => array(
                'key'  => 'DEFAULT',
                'pass' => 'DEFAULT',
            ),
        );
    }
}

Note: I preferred to use getConfig over getServiceConfig since it is more flexible (overrideable) and the method call is cached when you setup your application to do so.

Then the Marketo\ServiceFactory\ClientServiceFactory:

<?php

namespace Marketo\ServiceFactory;

use Marketo\Client\Client;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ClientServiceFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $config = $serviceLocator->get('Config');

        return new Client($config['marketo']['key'], $value['marketo']['pass']);
    }
}

After that, you will be able to pull the Marketo client from the service locator by calling following (for example in controllers):

$marketoClient = $this->getServiceLocator()->get('Marketo\Client\Client');

At this point, your Marketo\Client\Client is anyway built with key and pass both set to DEFAULT.

Let's go on and override this by creating a config/autoload/marketo.local.php file (in your application root, not in the module!):

<?php

return array(
    'marketo' => array(
        'key'  => 'MarketoAdmin',
        'pass' => 'Pa$$w0rd',
    ),
);

This is VERY important since you should never redistribute your key and pass, so put this file into .gitignore or svn:ignore!

So basically what we did here is:

  1. Setup service manager configuration to use a service factory to instantiate our Marketo client
  2. Setup the service factory to use merged configuration (service name: 'config') to instantiate the Marketo client
  3. Add local configuration for the actual application instance
  4. Retrieve the Marketo service via service locator.


回答2:

You should define a ServiceFactory to create your Client. The ServiceFactory can get the merged module configuration and set it on your Client. You have clean seperation now and your class is even reusable without Zend\Config at all. If you have a lot of configuration options you could create a seperate configuration class extending \Zend\StdLib\AbstractOptions and pass this to your client.

namespace Marketo\Service;

class ClientServiceFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        //Get the merged config of all modules
        $configuration = $serviceLocator->get('Config');
        $configuration = $configuration['marketo'];

        $client = new \Marketo\Client\Client($configuration['key'], $configuration['pass']);
    }
}

Now register your client factory in the service locator. Do this in the module.config.php or Module.php

public function getServiceConfig()
{
    return array('factories' => array(
        'marketo_client' => 'Marketo\Service\ClientServiceFactory'
    );
}

Users can now get your client from the ServiceManager. All configuration is neatly setup.

$sm->get('marketo_client');