How to add and use multiple actions into Apigilities RPC Generated Controller - php

i need your advice.
I need to create multiple Actions in RPC style API Controller generated by Apigility.
How do I need to make routing, to make it work like it is in normal zend application.
'application' => array(
'type' => 'Literal',
'options' => array(
'route' => '/application',
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',/*I need flexible route like this one*/
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
),
),
),
),
),
Code generated by Apigility is:
<?php
namespace TestAPI\V1\Rpc\Test;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\JsonModel;
class TestController extends AbstractActionController
{
public function TestAction()
{
/*Added by myself*/
return new JsonModel(array(
'id' => 'test',
));
}
}
and routing is generated this way:
'controllers' => array(
'factories' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\TestControllerFactory',
),
),
'zf-rpc' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => array(
'service_name' => 'test',
'http_methods' => array(
0 => 'GET',
),
'route_name' => 'test-api.rpc.test',
),
),
Thank You for your help!

Multiple actions per controller is discouraged by Matthew Weier O'Phinney (Apigility's creator):
No.
Apigility's RPC functionality posits one route -> one controller ->
one action (although we allow multiple HTTP methods to it). This is
for several reasons:
Simplifies configuration
Simplifies finding the code for a given route (exactly one place to look)
Helps prevent large controllers
What I'd do is create a service object that can handle the various
related operations, and then an RPC service per discrete operation
(unless that operation could be described using different HTTP verbs
on the same route, that is). Then inject each controller with that
service object, and call the appropriate method.
source: https://groups.google.com/a/zend.com/d/msg/apigility-users/Or3xBLAd9Y0/RzQKIMpaV0cJ

Solution 1
Try register aliases on controllers key to your controller and register each alias on zf-rpc config.
Something like that:
'controllers' => array(
'factories' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\TestControllerFactory',
),
'aliases' => array(
'TestAPI\\V1\\Rpc\\Test2\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\Controller',
'TestAPI\\V1\\Rpc\\Test3\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\Controller',
),
),
'zf-rpc' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => array(
'service_name' => 'test',
'http_methods' => array(
0 => 'GET',
),
'route_name' => 'test-api.rpc.test',
),
'TestAPI\\V1\\Rpc\\Test2\\Controller' => array(
'service_name' => 'test2',
'http_methods' => array(
0 => 'GET',
),
'route_name' => 'test-api.rpc.test2',
),
'TestAPI\\V1\\Rpc\\Test3\\Controller' => array(
'service_name' => 'test3',
'http_methods' => array(
0 => 'GET',
),
'route_name' => 'test-api.rpc.test3',
),
),
Probably you have to copy and change de route config and another configs.
Solution 2
You can try generate another rpc service and change the factory to a alias, all configs will generate to you.
after you generate the service, you will get something like this:
'controllers' => array(
'factories' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\TestControllerFactory',
'TestAPI\\V1\\Rpc\\Test2\\Controller' => 'TestAPI\\V1\\Rpc\\Test2\\TestControllerFactory',
),
),
You have to change to something like this:
'controllers' => array(
'factories' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\TestControllerFactory',
),
'aliases' => array(
'TestAPI\\V1\\Rpc\\Test2\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\Controller',
),
),

You can try this for routing :
'controllers' => array(
'factories' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => 'TestAPI\\V1\\Rpc\\Test\\TestControllerFactory',
),
),
'zf-rpc' => array(
'TestAPI\\V1\\Rpc\\Test\\Controller' => array(
'service_name' => 'test',
'http_methods' => array(
0 => 'GET',
),
'route_name' => 'test-api.rpc.test',
),
),
'router' => array(
'routes' => array(
'test-api.rpc.test' => array(
'type' => 'Segment',
'options' => array(
'route' => '/api/test[/:action_name]',
'defaults' => array(
'controller' =>'TestAPI\\V1\\Rpc\\Test\\Controller',
'action' => 'test',
),
),
),))
`
And in your controller :
<?php
namespace TestAPI\V1\Rpc\Test;
use Zend\Mvc\Controller\AbstractActionController;
class TestController extends AbstractActionController
{
public function testAction()
{
$action_name = $this->getEvent()->getRouteMatch()->getParam('action_name');
switch ($action_name) {
case 'test1':
return $this->test1Action();
default:
return array();
}
}
public function test1Action(){
//Your code here
return ...; //preferable to be array
}
}
the url is .../api/test/test1
I Hope this will help you.

Related

zf2 using abstract restful controller

How to call the get() or getList() method, but i always get the get() method running what i have done wrong. I have make action to null in the child routes of the users in appointments.
module.config.php
<?php
return array(
'controllers' => array(
'invokables' => array(
'Users\Controller\Users' => 'Users\Controller\UsersController',
'Users\Controller\Appointments' => 'Users\Controller\AppointmentsController',
),
),
'router' => array(
'routes' => array(
//By default profile action is loaded
'users' => array(
'type' => 'segment',
'options' => array(
'route' => '/api/v1/users[/:id]',
'defaults' => array(
'controller' => 'Users\Controller\Users',
'action' => 'profile',
),
),
'may_terminate' => true,
'child_routes' => array(
//apointments action
'appointments' => array(
'type' => 'segment',
'options' => array(
'route' => '/appointments[/:apt_id]',
'constraints' => array(
'apt_id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Users\Controller\Appointments',
'action' => null
),
),
),
),
),
),
),
'view_manager' => array(
'strategies' => array(
'ViewJsonStrategy',
),
//'template_path_stack' => array(
// 'Register' => __DIR__ . '/../view',
//),
),
);
Appointments Controller:
<?php
namespace Users\Controller;
use Zend\Validator\AbstractValidator;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\View\Model\ViewModel;
use Users\Model\UsersTable;
use Zend\Db\Sql\Select;
use Zend\Db\ResultSet\ResultSet;
// use Zend\Debug\Debug;
use Zend\View\Model\JsonModel;
use Zend\Validator\Db\RecordExists;
use Zend\Http\Client as HttpClient;
use Users\Service\UsersService;
class AppointmentsController extends AbstractRestfulController
{
public function getList() {
echo 'getList Method';
}
public function get() {
echo 'get Method';
}
}
Its because you have the route variable $id defined in your user route.
This varible triggers the restful controller to call get($id) instead of getList()
Either move the route down so its not a child of the user-route or add a constraint on the id route and calling /api/v1/users/appointments or change the name of $id in the user route also works
Finally I got the answer,
Actually when the url says like /api/v1/users/id it points to the get method, but when the endpoints has /api/v1/users it points to the getList() method.
The key here is the id parameter, as suggested when we change the id to user_id, we wont get the $id value in the controller.
now my route looks likes this,
<?php
return array(
'controllers' => array(
'invokables' => array(
'Users\Controller\Users' => 'Users\Controller\UsersController',
'Users\Controller\Appointments' => 'Users\Controller\AppointmentsController',
'Users\Controller\Vitalrecords' => 'Users\Controller\VitalrecordsController',
),
),
'router' => array(
'routes' => array(
//By default profile action is loaded
'users' => array(
'type' => 'segment',
'options' => array(
'route' => '/api/v1/users[/:id]',
'constraints' => array(
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Users\Controller\Users',
'action' => null,
),
),
'may_terminate' => true,
),
'appointments' => array(
'type' => 'segment',
'options' => array(
'route' => '/api/v1/users/:user_id/appointments[/:id]',
'constraints' => array(
'id' => '[0-9]+',
'user_id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Users\Controller\Appointments',
),
),
'may_terminate' => true,
),
),
),
'view_manager' => array(
'strategies' => array(
'ViewJsonStrategy',
),
),
);
when we calling the url like this /api/v1/users/appointments, the appointments will be thrown.
To get the list of appointments for the concerned user of id 74, how to make the url. Think i am not getting right, i am pulling my heads

Multiple routes under same tree in ZF2

I am having problem while implementing Multiple routes as defined Below in my snippet
EDIT: i am getting this exception too
Additional information:
Zend\Mvc\Exception\InvalidControllerException
with Message
Controller of type Account\Controller\VoucherController is invalid; must implement Zend\Stdlib\DispatchableInterface
<?php
namespace Account;
return array(
'controllers' => array(
'invokables' => array(
'Account\Controller\Account' => 'Account\Controller\AccountController',
'Account\Controller\Voucher' => 'Account\Controller\VoucherController',
),
// --------- Doctrine Settings For the Module
'doctrine' => array(
'driver' => array(
'account_entities' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Account/Entity')
),
'orm_default' => array(
'drivers' => array(
'Account\Entity' => 'account_entities'
)
)
)
),
// The following section is new and should be added to your file
'router' => array(
'routes' => array(
'account' => array(
'type' => 'segment',
'options' => array(
'route' => '/account[/][:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Account\Controller\Account',
'action' => 'index',
),
),
),
'voucher' => array(
'type' => 'segment',
'options' => array(
'route' => '/account/voucher[/][:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Account\Controller\Voucher',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'account' => __DIR__ . '/../view',
),
),
),
);
Now the issue is i am getting a 404, when i try to access MyHost/account/Voucher
P.S: I already have A Controller under Account/Controller/Voucher and a view under Account/View/Voucher named as index.phtml now i dont know what am i missing here.
As Adnrew and Timdev comments above that there is something not right in your controller, you can check few basic things in your controller, that you have following code correct. specially the typos.
namespace Account\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class VoucherController extends AbstractActionController {
// you acctions
}

ZF2 run the application without using the router

I am learning ZF2.
Is it possible to run the application without Router as in Zf1?
Do we need to define router for each controller?
Example:
In ZF1: "admin/index/index" shows as "module/controller/action"
IN ZF2: "admin/index/index" shows as "router[/:controller][/:action]"
please help me to clear my doubts.
Please tyr this
return array(
// routes
'router' => array(
'routes' => array(
'album' => array(
'type' => 'Literal',
'options' => array(
'route' => '/album',
'defaults' => array(
'controller' => 'album\Controller\Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
// add the default namespace for :controllers in this route
'__NAMESPACE__' => 'album\Controller',
),
),
),
),
),
),
),
'controllers' => array(
'invokables' => array(
'album\Controller\Test' => 'Album\Controller\TestController',
),
),
'view_manager' => array(
'template_path_stack' => array(
'album' => __DIR__ . '/../view',
),
),
);
You need to add your controller name in invokables manually or invoke via Abstract Factories
'controllers' => array(
'invokables' => array(
'album\Controller\Test' => 'Album\Controller\TestController',
),
),
Automatic Controller Invokables via Abstract Factories
Reference

Routes with multiple modules

I'm currently working in ZF2 and need some help.
Unfortunately I can only find examples set the routes for the case that there is only one module. Or the example has still the application module in it, with dynamic segment routes. I want to totally remove the application module and only have my own modules running an configure all routing in them.
I have two modules:
CLFrontend,
CLBackend
My application config looks like this:
return array(
'modules' => array(
'ClFrontend',
'ClBackend'
),
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'./module',
'./vendor',
),
),
);
I want to register my 2 own modules there. The routing should now look someway like this:
everything under / should go to the frontend module excerpt /backend
/ --> IndexController --> indexaction
/controller1 --> Controller1Controller -> indexaction
/controller1/add --> Controller1Controller --> addaction
/controller1/add/1/ --> COntroller1Controller --> addaction --> item 1
Now should everything under /backend route to the backend module
/backend --> BackendIndexController --> indexaction
/backend/controller1 --> BackendController1Controller -> indexaction
/backend/controller1/add --> BackendController1Controller -->
addaction
/backend/controller1/add/1/ --> BackendCOntroller1Controller -->
addaction --> item 1
And i want to define that routes fixed and not something like a segment route looking like that:
:module/:controller/:action
I want to end up with something like
/
/controller1/[:action[/:id]]
AND
/backend
/backend/backendcontroller/[:action[/:id]]
Myapproach was the following. The problem is now, that even the backend routes seem to match to the frontend module?! I either get a 404 with
The requested URL could not be matched by routing.
Or
Fatal error: Class 'ClBackend\Controller\AnswerController' not found
in
//*/**/***/checklistenassistent3/vendor/ZF2/library/Zend/ServiceManager/AbstractPluginManager.php
on line 177
CLFrontend/config/module.config.php
return array(
'controllers' => array(
'invokables' => array(
'ClFrontend\Controller\Index' => 'ClFrontend\Controller\IndexController',
'ClFrontend\Controller\User' => 'ClFrontend\Controller\UserController',
),
),
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'ClFrontend\Controller\Index',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => array(
'layout/layout' => __DIR__ . '/../view/cl-frontend/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/cl-frontend/index/index.phtml',
'error/404' => __DIR__ . '/../view/cl-frontend/error/404.phtml',
'error/index' => __DIR__ . '/../view/cl-frontend/error/index.phtml',
),
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
);
CLBackend/config/module.config.php
return array(
'controllers' => array(
'invokables' => array(
'ClBackend\Controller\Answer' => 'ClBackend\Controller\AnswerController',
'ClBackend\Controller\AnswerGroup' => 'ClBackend\Controller\AnswerGroupController',
'ClBackend\Controller\Category' => 'ClBackend\Controller\CategoryController',
'ClBackend\Controller\Checklist' => 'ClBackend\Controller\ChecklistController',
'ClBackend\Controller\Index' => 'ClBackend\Controller\IndexController',
'ClBackend\Controller\Question' => 'ClBackend\Controller\QuestionController',
'ClBackend\Controller\User' => 'ClBackend\Controller\UserController',
),
),
'router' => array(
'routes' => array(
'backend' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/backend',
'defaults' => array(
'controller' => 'ClBackend\Controller\Index',
'action' => 'index',
),
'may_terminate' => true
),
'child_routes' => array (
'answer' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/answer/:action/:id',
'defaults' => array(
'controller' => 'ClBackend\Controller\Answer',
'action' => 'index',
),
),
),
'answergroup' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/answergroup/:action/:id',
'defaults' => array(
'controller' => 'ClBackend\Controller\AnswerGroup',
'action' => 'index',
),
),
),
'category' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/category/:action/:id',
'defaults' => array(
'controller' => 'ClBackend\Controller\Category',
'action' => 'index',
),
),
),
'checklist' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/checklist/:action/:id',
'defaults' => array(
'controller' => 'ClBackend\Controller\Checklist',
'action' => 'index',
),
),
),
'question' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/question/:action/:id',
'defaults' => array(
'controller' => 'ClBackend\Controller\Question',
'action' => 'index',
),
),
),
'user' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/user/:action[/:id]',
'defaults' => array(
'controller' => 'ClBackend\Controller\User',
'action' => 'index',
),
),
),
),
),
),
),
);
Have you considered a Controller factory? It would allow you to match a single route and use logic to decide on which controller to use.
For example your route could look like:
'backend-default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/:controller_type[/:action][/id]',
'defaults' => array(
'action' => 'index',
'controller' => 'MyFactory'
),
),
),
If this route was matched then the Controller factory (MyFactory) would be used - within this factory you can gain access to the route match parameters. Using these parameters you should be able to return the appropriate controller.
You could even pass in an additional parameter signifying it's a backend controller (and use a single factory).
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyFactoryController implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
// $serviceLocator is a Zend\Mvc\Controller\ControllerManager
$app = $serviceLocator->getServiceLocator()->get('application');
$routeMatch = $app->getMvcEvent()->getRouteMatch();
var_dump($routeMatch);
/**
* Create controller based off $routeMatch params
*/
return $controller;
}
}
The controller factory would allow you to lookup the controller_type variable to a valid class name - or prefix an invokable name to controller_type.
I'm not sure I'd take this route myself but I hope this is somewhat useful for what you are trying to achieve.

ZF2 Dependency Injection - Injecting tables into classes

Please note: This was ZF beta 1. I doubt this is the same for current release
I was just attempting to inject dependencies into some of my classes. I thought I had this down as I was already managing it with quite a few things like injecting database configurations, DB adapters and auth adapters into my UserMapper class.
I was trying to do the same with some other classes of mine and couldn't work out at all why it just wasn't working like my UserMapper class was. Now I see that the UserMapper class is being injected into a controller which has an alias set for it at the top of the config file.
So I guess without having it first injected by using a controller... how am I supposed to be injecting stuff into my Models? I'm using the standard ZF2 Skeleton by EvanDotPro
My Models are in:
Application\Model\
Application\Model\DbTable
My config currently looks something like:
<?php
return array(
'bootstrap_class' => 'Application\Bootstrap',
'layout' => 'layouts/layout.phtml',
'di' => array(
'instance' => array(
'alias' => array(
'index' => 'Application\Controller\IndexController',
'view' => 'Zend\View\PhpRenderer'
),
'Zend\View\HelperLoader' => array(
'parameters' => array(
'map' => array(
'url' => 'Application\View\Helper\Url',
),
),
),
'Zend\View\HelperBroker' => array(
'parameters' => array(
'loader' => 'Zend\View\HelperLoader',
),
),
'Application\Controller\IndexController' => array(
'parameters' => array(
'userMapper' => 'Application\Model\UserMapper',
'flashMessenger' => 'Zend\Mvc\Controller\Plugin\FlashMessenger'
)
),
'Application\Model\UserMapper' => array(
'parameters' => array(
'db' => 'Zend\Db\Adapter\PdoMysql',
'authAdapter' => 'Zend\Authentication\Adapter\DbTable',
'userTable' => 'Application\Model\DbTable\UserTable'
)
),
'Application\Model\DbTable\UserTable' => array(
'parameters' => array(
'config' => 'Zend\Db\Adapter\PdoMysql',
)
),
// Auth adapter
'Zend\Authentication\Adapter\DbTable' => array(
'parameters' => array(
'zendDb' => 'Zend\Db\Adapter\PdoMysql',
'tableName' => 'users',
'identityColumn' => 'username',
'credentialColumn' => 'password',
'credentialTreatment' => 'MD5(CONCAT(?,"OSalTyr$"))'
)
),
// DB Adapter
'Zend\Db\Adapter\PdoMysql' => array(
'parameters' => array(
'config' => array(
'host' => 'localhost',
'username' => 'root',
'password' => '',
'dbname' => 'blahblah',
),
),
),
// View
'Zend\View\PhpRenderer' => array(
'parameters' => array(
'resolver' => 'Zend\View\TemplatePathStack',
'options' => array(
'script_paths' => array(
'application' => __DIR__ . '/../views',
),
),
'broker' => 'Zend\View\HelperBroker',
),
),
),
),
'routes' => array(
'default' => array(
'type' => 'Zend\Mvc\Router\Http\Regex',
'options' => array(
'regex' => '/(?P<controller>[^/]+)(/(?P<action>[^/]+)?)?',
'spec' => '/%controller%/%action%',
'defaults' => array(
'controller' => 'error',
'action' => 'index',
),
),
),
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'index',
'action' => 'index',
),
),
),
),
);
Thanks, Dominic
Ahah... I found out why this wasn't working.
Although the above config is correct and I'm defining what I want injecting into what... I wasn't actually using the injected instance of the object in question.
I thought when using 'new thingy();' that DI would magically pick up that it has dependencies and apply them when needed but this is not the case...
What I infact needed to do... was inject the thingy class into my UserMapper class like a chain and then use the injected instance instead of creating it a new instance of it without stuff injected into it.
Like:
RegisterController requires UserMapper so inject it in and use that instance
UserMapper requires Thingy so inject it in and use that instance

Categories