How to use dependency injection in Zend Framework?

前端 未结 5 759
攒了一身酷
攒了一身酷 2021-02-02 14:52

Currently I am trying to learn the Zend Framework and therefore I bought the book \"Zend Framework in Action\".

In chapter 3, a basic model and controller is introduced

相关标签:
5条回答
  • 2021-02-02 15:10

    With the Service Manager at Zend Framework 3.

    Official Documentation:

    https://zendframework.github.io/zend-servicemanager/

    1. Dependencies at your Controller are usually be injected by DI Constructor injector.
    2. I could provide one example, that inject a Factory responsible to create the ViewModel instance into the controller.

    Example:

    Controller `

    class JsonController extends AbstractActionController
    {
        private $_jsonFactory;
        private $_smsRepository;
        public function __construct(JsonFactory $jsonFactory, SmsRepository $smsRepository)
        {
            $this->_jsonFactory = $jsonFactory;
            $this->_smsRepository = $smsRepository;
        }
    ...
    }
    

    Creates the Controller

    class JsonControllerFactory implements FactoryInterface
    {
        /**
         * @param ContainerInterface $serviceManager
         * @param string $requestedName
         * @param array|null $options
         * @return JsonController
         */
        public function __invoke(ContainerInterface $serviceManager, $requestedName, array $options = null)
        {
            //improve using get method and callable
            $jsonModelFactory = new JsonFactory();
            $smsRepositoryClass = $serviceManager->get(SmsRepository::class);
            return new JsonController($jsonModelFactory, $smsRepositoryClass);
        }
    }
    

    ` Complete example at https://github.com/fmacias/SMSDispatcher

    I hope it helps someone

    0 讨论(0)
  • 2021-02-02 15:17

    Logic to models

    First of all, it's worth to mention, that controllers should need only functional tests, though all the logic belongs to models.

    My implementation

    Here is an excerpt from my Action Controller implementation, which solves the following problems:

    • allows inject any dependency to actions
    • validates the action parameters, e.g. you may not pass array in $_GET when integer is expected

    My full code allows also to generate canonical URL (for SEO or unique page hash for stats) based or required or handled action params. For this, I use this abstract Action Controller and custom Request object, but this is not the case we discuss here.

    Obviously, I use Reflections to automatically determine action parameters and dependency objects.

    This is a huge advantage and simplifies the code, but also has an impact in performance (minimal and not important in case of my app and server), but you may implement some caching to speed it up. Calculate the benefits and the drawbacks, then decide.

    DocBlock annotations are becoming a pretty well known industry standard, and parsing it for evaluation purposes becomes more popular (e.g. Doctrine 2). I used this technique for many apps and it worked nicely.

    Writing this class I was inspired by Actions, now with params! and Jani Hartikainen's blog post.

    So, here is the code:

    <?php
    
    /**
     * Enchanced action controller
     *
     * Map request parameters to action method
     *
     * Important:
     * When you declare optional arguments with default parameters, 
     * they may not be perceded by optional arguments,
     * e.g.
     * @example
     * indexAction($username = 'tom', $pageid); // wrong
     * indexAction($pageid, $username = 'tom'); // OK
     * 
     * Each argument must have @param DocBlock
     * Order of @param DocBlocks *is* important
     * 
     * Allows to inject object dependency on actions:
     * @example
     *   * @param int $pageid
     *   * @param Default_Form_Test $form
     *   public function indexAction($pageid, Default_Form_Test $form = null)
     *
     */
    abstract class Your_Controller_Action extends Zend_Controller_Action
    {  
        /**
         *
         * @var array
         */
        protected $_basicTypes = array(
            'int', 'integer', 'bool', 'boolean',
            'string', 'array', 'object',
            'double', 'float'
        );
    
        /**
         * Detect whether dispatched action exists
         * 
         * @param string $action
         * @return bool 
         */
        protected function _hasAction($action)
        {
            if ($this->getInvokeArg('useCaseSensitiveActions')) {
                trigger_error(
                        'Using case sensitive actions without word separators' .
                        'is deprecated; please do not rely on this "feature"'
                );
    
                return true;
            }
    
            if (method_exists($this, $action)) {
    
                return true;
            }
    
            return false;
        }
    
        /**
         *
         * @param string $action
         * @return array of Zend_Reflection_Parameter objects
         */
        protected function _actionReflectionParams($action)
        {
            $reflMethod = new Zend_Reflection_Method($this, $action);
            $parameters = $reflMethod->getParameters();
    
            return $parameters;
        }
    
        /**
         *
         * @param Zend_Reflection_Parameter $parameter
         * @return string
         * @throws Your_Controller_Action_Exception when required @param is missing
         */
        protected function _getParameterType(Zend_Reflection_Parameter $parameter)
        {
            // get parameter type
            $reflClass = $parameter->getClass();
    
            if ($reflClass instanceof Zend_Reflection_Class) {
                $type = $reflClass->getName();
            } else if ($parameter->isArray()) {
                $type = 'array';
            } else {
                $type = $parameter->getType();
            }
    
            if (null === $type) {
                throw new Your_Controller_Action_Exception(
                        sprintf(
                                "Required @param DocBlock not found for '%s'", $parameter->getName()
                        )
                );
            }
    
            return $type;
        }
    
        /**
         *
         * @param Zend_Reflection_Parameter $parameter 
         * @return mixed
         * @throws Your_Controller_Action_Exception when required argument is missing
         */
        protected function _getParameterValue(Zend_Reflection_Parameter $parameter)
        {
            $name = $parameter->getName();
            $requestValue = $this->getRequest()->getParam($name);
    
            if (null !== $requestValue) {
                $value = $requestValue;
            } else if ($parameter->isDefaultValueAvailable()) {
                $value = $parameter->getDefaultValue();
            } else {
                if (!$parameter->isOptional()) {
                    throw new Your_Controller_Action_Exception(
                            sprintf("Missing required value for argument: '%s'", $name));
                }
    
                $value = null;
            }
    
            return $value;
        }
    
        /**
         *
         * @param mixed $value 
         */
        protected function _fixValueType($value, $type)
        {
            if (in_array($type, $this->_basicTypes)) {
                settype($value, $type);
            }
    
            return $value;
        }
    
        /**
         * Dispatch the requested action
         *
         * @param   string $action Method name of action
         * @return  void
         */
        public function dispatch($action)
        {
            $request = $this->getRequest();
    
            // Notify helpers of action preDispatch state
            $this->_helper->notifyPreDispatch();
    
            $this->preDispatch();
            if ($request->isDispatched()) {
                // preDispatch() didn't change the action, so we can continue
                if ($this->_hasAction($action)) {
    
                    $requestArgs = array();
                    $dependencyObjects = array();
                    $requiredArgs = array();
    
                    foreach ($this->_actionReflectionParams($action) as $parameter) {
                        $type = $this->_getParameterType($parameter);
                        $name = $parameter->getName();
                        $value = $this->_getParameterValue($parameter);
    
                        if (!in_array($type, $this->_basicTypes)) {
                            if (!is_object($value)) {
                                $value = new $type($value);
                            }
                            $dependencyObjects[$name] = $value;
                        } else {
                            $value = $this->_fixValueType($value, $type);
                            $requestArgs[$name] = $value;
                        }
    
                        if (!$parameter->isOptional()) {
                            $requiredArgs[$name] = $value;
                        }
                    }
    
                    // handle canonical URLs here
    
                    $allArgs = array_merge($requestArgs, $dependencyObjects);
                    // dispatch the action with arguments
                    call_user_func_array(array($this, $action), $allArgs);
                } else {
                    $this->__call($action, array());
                }
                $this->postDispatch();
            }
    
            $this->_helper->notifyPostDispatch();
        }
    
    }
    

    To use this, just:

    Your_FineController extends Your_Controller_Action {}
    

    and provide annotations to actions, as usual (at least you already should ;).

    e.g.

    /**
     * @param int $id Mandatory parameter
     * @param string $sorting Not required parameter
     * @param Your_Model_Name $model Optional dependency object 
     */
    public function indexAction($id, $sorting = null, Your_Model_Name $model = null) 
    {
        // model has been already automatically instantiated if null
        $entry = $model->getOneById($id, $sorting);
    }
    

    (DocBlock is required, however I use Netbeans IDE, so the DocBlock is automatically generated based on action arguments)

    0 讨论(0)
  • 2021-02-02 15:18

    You could also just use the PHP-DI ZF bridge: http://php-di.org/doc/frameworks/zf1.html

    I know this question is really old but it pops up rather high in search engines when looking for DI in ZF1 so I thought I'd add a solution that doesn't require you to write it all by yourself.

    0 讨论(0)
  • 2021-02-02 15:26

    I'm currently working on the same question, and after deep research I've decide to use Symfony Dependency Injection component. You can get good info from official website http://symfony.com/doc/current/book/service_container.html.

    I've build custom getContainer() method in bootstrap, which resturns now service container, and it simply can be used in controllers like

    public function init()
    {
        $sc = $this->getInvokeArg('bootstrap')->getContainer();
        $this->placesService = $sc->get('PlacesService');
    }
    

    Here you can find how to do that http://blog.starreveld.com/2009/11/using-symfony-di-container-with.html. But I changed ContainerFactory, because of using Symfony2 component, instead of first version.

    0 讨论(0)
  • 2021-02-02 15:37

    Ok, this is how I did it:

    As IoC Framework I used this component of the symfony framework (but I didnt download the latest version, I used an older one I used on projects before... keep that in mind!). I added its classes under /library/ioc/lib/.

    I added these init function in my Bootstrap.php in order to register the autoloader of the IoC framework:

    protected function _initIocFrameworkAutoloader()
    {
        require_once(APPLICATION_PATH . '/../library/Ioc/lib/sfServiceContainerAutoloader.php');
    
        sfServiceContainerAutoloader::register();
    }
    

    Next, I made some settings in application.ini which set the path to the wiring xml and allow to disable automatic dependency injection e. g. in unit tests:

    ioc.controllers.wiringXml = APPLICATION_PATH "/objectconfiguration/controllers.xml"
    ioc.controllers.enableIoc = 1
    

    Then, I created a custom builder class, which extends sfServiceContainerBuilder and put it under /library/MyStuff/Ioc/Builder.php. In this test project I keep all my classes under /library/MyStuff/.

    class MyStuff_Ioc_Builder extends sfServiceContainerBuilder
    {
      public function initializeServiceInstance($service)
      {
          $serviceClass = get_class($service);
          $definition = $this->getServiceDefinition($serviceClass);
    
    
        foreach ($definition->getMethodCalls() as $call)
        {
          call_user_func_array(array($service, $call[0]), $this->resolveServices($this->resolveValue($call[1])));
        }
    
        if ($callable = $definition->getConfigurator())
        {
          if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof sfServiceReference)
          {
            $callable[0] = $this->getService((string) $callable[0]);
          }
          elseif (is_array($callable))
          {
            $callable[0] = $this->resolveValue($callable[0]);
          }
    
          if (!is_callable($callable))
          {
            throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
          }
    
          call_user_func($callable, $service);
        }
    
      }
    }
    

    Last, I created a custom controller class in /library/MyStuff/Controller.php which all my controllers inherit from:

    class MyStuff_Controller extends Zend_Controller_Action {
        /**
         * @override
         */
        public function dispatch($action)
        {
            // NOTE: the application settings have to be saved 
            // in the registry with key "config"
            $config = Zend_Registry::get('config');
    
            if($config['ioc']['controllers']['enableIoc'])
            {
                $sc = new MyStuff_Ioc_Builder();
    
                $loader = new sfServiceContainerLoaderFileXml($sc);
                $loader->load($config['ioc']['controllers']['wiringXml']);
    
                $sc->initializeServiceInstance($this);
            }
    
            parent::dispatch($action);
        }
    }
    

    What this basically does is using the IoC Framework in order to initialize the already created controller instance ($this). Simple tests I did seemed to do what I want... let´s see how this performs in real life situations. ;)

    It´s still monkey patching somehow, but the Zend Framework doesn´t seem to provide a hook where I can create the controller instance with a custom controller factory, so this is the best I came up with...

    0 讨论(0)
提交回复
热议问题