Need help calling a controller from another controller

Discussion in 'Zend Framework 2 (ZF2) & Apigility' started by kingston, Apr 23, 2015.

  1. kingston

    kingston Administrator Staff Member

    In my web application I am trying to keep all PHP Toolkit (and hence all RPG calls) in one module. This is great as it is allowing me to create a module for each potential API we might have to our database. When a user needs to enact a program I have a route setup to call my RPG and return JSON. I use AJAX within the view to call the program.

    On initial load I want to call this controller and action to pre populate some values. How would I dispatch a route from a controller and return the JSON and then return it to my view? Thanks!
     
  2. Chuk

    Chuk Software Developer

    You probably shouldn't dispatch another route from a controller. It sounds like it would be better for you do design your PHP Toolkit module as a collection of services. Thus this module wouldn't contain any controllers.

    I'm a little rusty concerning the process of setting up services in ZF2, but here's how I would start:
    1. Create two directories to house your services at /projectRoot/module/ToolkitModule/src/ToolkitModule/Service/Invokable and /projectRoot/module/ToolkitModule/src/ToolkitModule/Service/Factory. Your service classes will go under "invokable" and their corresponding factories will go under "factory."
    2. Create regular PHP classes receive a toolkit object through constructor injection.
    3. Create methods in those classes that use the toolkit object to make calls to your RPG/CL and return the output as a PHP array.
    4. Register the service classes with the service manager.
    5. Call these services from controllers in other modules. Within the controller methods you would get the service objects from the service manager.
    Did I understand your question correctly, King?
     
    kingston likes this.
  3. kingston

    kingston Administrator Staff Member

    Ah I get it. Yeah that seems more reasonable for this particular piece. I am looking to have some AJAX calls that go to a route to run a program and get a JSON response. But that only works when the page is loaded and I am using some javascript. For this piece when I am loading the page and I need to call the program within my controller that makes a lot more sense. Let me play with it and see what I can come up with. Thanks Chuk!
     
  4. Chuk

    Chuk Software Developer

    No problem. There are lots of advantages to this approach, but the main one is reusability across controllers. Any time you encapsulate behavior you make it more maintainable and reusable. What I mean is by repurposing your toolkit module as a collection of services you're making the services themselves have less responsibility. A controller's role is to handle the HTTP request input, pass it to the model, and return an HTTP response. If you need to accomplish something other than that then you should probably put it in a service. Avoid adding extra responsibilities to the controllers.

    For instance, when you make the AJAX call your ZF2 route configuration directs the http request to the appropriate controller. That controller shouldn't be concerned with building another route to reach another controller. Instead it should simply access the appropriate service
     
  5. kingston

    kingston Administrator Staff Member

    So now the question is: How do I do it?

    I am somewhat there.. I think.

    I created (in my RPG module) /RPG/Services/CalculateService.php
    PHP:
    <?php
    namespace RPG\Services;
    use Zend\ServiceManager\ServiceLocatorAwareInterface;
    use Zend\ServiceManager\ServiceLocatorInterface;

    class CalculateService implements ServiceLocatorAwareInterface
    {
        public function __construct()
        {
        }

        public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
        {
        }

        public function getServiceLocator()
        {
        }
    }
    Then in my module.config.php I have:

    PHP:
    <?php
    return array(
        'service_manager' => array(
            'invokables' => array(
                'RPG\Services\Calculate' => 'RPG\Services\CalculateService'
            ),
        ),
    And in a controller in another module I am trying to do this:

    PHP:
    <?php

    namespace Suppliers\Controller;
    use Zend\Mvc\Controller\AbstractActionController;
    use Zend\View\Model\ViewModel;
    use Zend\View\Model\JsonModel;
    use RPG\Services\Calculate;

    class OrderCycleController extends AbstractActionController
    {
        public function calculateAction()
        {
            //This is the part throwing an error
            $stuff = new Calculate();
        }
       

    }
    I am getting that Calculate() is not found. What am I missing?
     
  6. Chuk

    Chuk Software Developer

    In your controller you don't want to use autoloading to retrieve the Calculate object. You want to get it from the service manager. I forget what the syntax is, but you would want something like
    PHP:
    $stuff = $this->getServiceLocator->get('RPG\Services\Calculate');
    Like I said, my ZF2 is rusty, but you don't want to instantiate the calculate class in your controller.
     
  7. kingston

    kingston Administrator Staff Member

    I am closer!

    This is saying: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for RPG\Services\Calculate

    Code:

    PHP:
      $stuff = $this->getServiceLocator();//
            $test = $stuff->get('RPG\Services\Calculate');
     
  8. Chuk

    Chuk Software Developer

    Try this...

    Within service manager:
    PHP:
    'service_manager' => array(
        'invokables' => array(
            'calculator' => 'RPG\Services\CalculateService'
        }
    },
    Within controller:
    PHP:

    public function calculate()
    {
        $stuff = $this->getServiceLocator()->get('calculator');
    }
     
     
    Last edited: Apr 24, 2015
  9. kingston

    kingston Administrator Staff Member

    I actually did this before you wrote it and it worked. Then I added back in the RPG/Services part and it worked again. Weird.


    Now how do I get the ServiceLocater in my CalculateService Class?
     
  10. kingston

    kingston Administrator Staff Member

    I just passed in a copy of the service manager from my controller to the service class.... is that bad?
     
  11. Chuk

    Chuk Software Developer

    That will work, but ideally you'd want to create a factory for it. Within RPG\Services you would create two directories: Factory and Invokable. Create a CalculateFactory within RPG\Services\Factory that looks like this:
    PHP:

    namespace RPG\Services\Factory;

    use Zend\ServiceManager\FactoryInterface;
    use Zend\ServiceManager\ServiceLocatorInterface;
    use RPG\Service\Invokable\CalculateService;

    class CalculateFactory implements FactoryInterface
    {
        public function createService(ServiceLocatorInterface $serviceLocator)
        {
            $calculate = new CalculateService($serviceLocator);
            return $calculate;
        }
    }
     
    All factories receive an instance of the service manager. Then you could create the service from within the factory while injecting the service manager.

    PHP:

    'service_manager' => array(
        'factories' => array(
            'calculate' => 'RPG\Services\Factory\CalculateFactory'
        ),
    ),
     
    Then in your controller:
    PHP:

    public function calculate()
    {
        $stuff = $this->getServiceLocator()->get('calculate');
    }
     
     
    kingston likes this.
  12. kingston

    kingston Administrator Staff Member


    How do I access the Service Manager in my Service class? That part I seem to be missing. My $this->getServiceLocator() is returning NULL.
     
  13. Chuk

    Chuk Software Developer

    Constructor injection, my friend. The factory instantiates the service and passes the service manager in via the constructor. Move your service to RPG\Services\Invokable directory. Then I would write the service like this:

    PHP:

    namespace RPG\Services\Invokable;
    use Zend\ServiceManager\ServiceLocatorInterface;

    class CalculateService implements ServiceLocatorAwareInterface
    {
        private $sm;
        public function __construct(ServiceLocatorInterface $sm)
        {
            // The service manager is now a property in your class
            $this->sm = $sm;
        }
        public function myMethod()
        {
            // Refer to the service manager with $this->sm
            $stuff = $this->sm->get('myService');
        }
    }
     
  14. kingston

    kingston Administrator Staff Member

    I was able to get it to work but my code is slightly different. I don't quite understand why this works:
    PHP:

    class CalculateService implements ServiceLocatorAwareInterface
    {
        protected $serviceLocator;

        public function __construct()
        {
        }

        public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
        {
            $this->serviceLocator = $serviceLocator;
        }

        public function getServiceLocator()
        {
            return $this->serviceLocator;
        }

        //This calculates values for a suggested order cycle
        public function calorcy()
        {
            // Get the Service Manager, get the config, and get the K3SOBJ location.
            $sm = $this->getServiceLocator();
    }
     
  15. Chuk

    Chuk Software Developer

    I should add that it is standard practice not to inject the service manager itself into a service. Ideally you would only inject the objects that the given service depends on.
     

Share This Page