I have a ZF3 project and an index controller that accesses a single postgres table. I have the usual factories setup
return array(
'factories' => [
Model\IsdepotstockTable::class => function($container) {
$tableGateway = $container->get(Model\IsdepotstockTableGateway::class);
return new Model\IsdepotstockTable($tableGateway);
},
Model\IsdepotstockTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Isdepotstock());
return new TableGateway('isdepotstock', $dbAdapter, null, $resultSetPrototype);
},
],
I also have my controller constructor thus:
public function __construct(IsdepotstockTable $table)
{
$this->isdepotstockTable = $table;
}
My question is, if I wish to access a second table, how do I modify the construct statement to handle multiple tables? Obviously I have to add the factories for the additional table that much I understand.
I've looked through the ZF3 documentation but cannot find any example.
Thanks
I believe you are looking for a controller factory function which will instantiate the controller and pass the arguments to the constructor.
Add your second table class as the 2nd argument in the __construct() method, then create your factory in module.config.php.
<?php
use Zend\ServiceManager\Factory\InvokableFactory;
return [
// ...
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class
// Put other controllers registration here
],
],
// ...
];
Here is a free open source book on ZF3, I link to the controller registration section for your reference. Good luck! https://olegkrivtsov.github.io/using-zend-framework-3-book/html/en/Model_View_Controller/Controller_Registration.html
Related
I've a controller Controller\Api\ProductController for rest call and it's defined in module.config.php
'controllers' => [
'factories' => [
Controller\Api\ProductController::class => function($container) {
return new Controller\Api\ProductController(
$container->get(\Commerce\Model\Product::class), $container->get(\Commerce\Controller\Plugin\ProductPlugin::class)
);
}
]
]
In the above code you can see I'm injecting a plugin class \Commerce\Controller\Plugin\ProductPlugin::class which is defined in module.config.php
'controller_plugins' => [
'factories' => [
Controller\Plugin\ProductPlugin::class => InvokableFactory::class,
],
'aliases' => [
'product' => Controller\Plugin\ProductPlugin::class,
]
]
Now when I'm hitting the rest url it shows error message
Unable to resolve service "Commerce\Controller\Plugin\ProductPlugin" to a
factory; are you certain you provided it during configuration?
What I'm missing ?
Plugin code is
<?php
namespace Commerce\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class ProductPlugin extends AbstractPlugin
{
//....
}
Controller plugins do not get injected to the controller.
Remove
$container->get(\Commerce\Controller\Plugin\ProductPlugin::class)
from the factory callback and also remove the 2nd parameter from the constructor of your ProductController
To use the plugin, just do:
$plugin = $this->plugin(Plugin\ProductPlugin::class);
or
// using the alias
$plugin = $this->product();
in your action controllers.
https://docs.zendframework.com/zend-mvc/plugins/
Controller plugin example
I'm using a Base controller and I had to inject plugins through dependencies
based on the request.
Above configuration was fine. All I had to do is define my plugin in service_manager section.
'service_manager' => [
'factories' => [
Controller\Plugin\ProductPlugin::class => function($sm) {
$dependencies = /////
$model = new \Commerce\Model\Product($dependencies);
return new Controller\Plugin\ProductPlugin($model);
},
\Commerce\Model\Product::class => function($sm) {
$dependencies = /////
return new \Commerce\Model\Product($dependencies);
}
],
],
In current state I've got two modules - main module, and admin panel module.
Main module is called "Kreator", admin -> "KreatorAdmin". All the models are located inside the Kreator module (Kreator/Model/UserTable.php etc.).
"KreatorAdmin" is almost empty, there is a configuration for it:
KreatorAdmin/config/module.config.php
<?php
return array(
'controllers' => array(
'invokables' => array(
'KreatorAdmin\Controller\Admin' => 'KreatorAdmin\Controller\AdminController',
),
),
'router' => array(
'routes' => array(
'zfcadmin' => array(
'options' => array(
'defaults' => array(
'controller' => 'KreatorAdmin\Controller\Admin',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view'
),
),
);
KreatorAdmin/src/KreatorAdmin/AdminController.php
<?php
namespace KreatorAdmin\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class AdminController extends AbstractActionController
{
public function indexAction()
{
//$this->getServiceLocator()->get('Kreator\Model\UserTable');
return new ViewModel();
}
}
KreatorAdmin/Module.php
<?php
namespace KreatorAdmin;
class Module
{
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}
Simply adding "use" statements in controller and navigating by namespaces results in error
Argument 1 passed to KreatorAdmin\Controller\AdminController::__construct() must be an instance of Kreator\Model\UserTable, none given,
I also tried to play a bit with service manager as described here:
ZF2 Models shared between Modules but no luck so far.
How am I supposed to access UserTable from KreatorAdmin/src/KreatorAdmin/AdminController.php ?
Cheers!
update 1
I've added getServiceConfig to Module.php
public function getServiceConfig()
{
return [
'factories' => [
// 'Kreator\Model\UserTable' => function($sm) {
// $tableGateway = $sm->get('UserTableGateway');
// $table = new UserTable($tableGateway);
// return $table;
// },
// 'UserTableGateway' => function($sm) {
// $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
// $resultSetPrototype = new ResultSet();
// $resultSetPrototype->setArrayObjectPrototype(new User());
// return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
// },
'DbAdapter' => function (ServiceManager $sm) {
$config = $sm->get('Config');
return new Adapter($config['db']);
},
'UserTable' => function (ServiceManager $sm) {
return new UserTable($sm->get('UserTableGateway'));
},
'UserTableGateway' => function (ServiceManager $sm) {
$dbAdapter = $sm->get('DbAdapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new User());
return new TableGateway('users', $dbAdapter, null, $resultSetPrototype);
},
],
];
}
And updated controller
class AdminController extends AbstractActionController
{
protected $userTable;
public function indexAction()
{
$userTable = $this->getServiceLocator()->get('Kreator\Model\UserTable');
return new ViewModel();
}
}
First error - using commented version:
Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Zend\Db\Adapter\Adapter
Second - using uncommented part:
Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Kreator\Model\UserTable
Solution
If anyone wonder. Using above configuration there is a correct solution in jobaer answer.
Using commented version, you have to remember to add
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
somewhere in config to service_manager.
May be you messed up with ZF2 and ZF3 configuration. I am not sure but somewhere may be, you tried to create a factory of AdminController by passing an instance of UserTable to make it available inside AdminController's action methods. And later you are not passing that instance of UserTable into the AdminController's constructor while working with it further. The highlighted part from the previous line results in that error.
In ZF2 you do not need to pass that UserTable instance in the controller's constructor for its availability. Just use the following one in any controller's action methods.
$userTable = $this->getServiceLocator()->get('UserTable');
If want to know how this process is done, please, refer to this part of the tutorial.
I was reading Zend 3 documentation on Service Manager and i got this problem.
In documentation it says that if we have some DI in our controller we should update module.config.php file and add controllers key and invoke controller not with InvokableFactory::class but with custom factory class and add another key service_manager that contains array of classes that my first controller uses.
Ok so i do that:
module.config.php
'service_manager' => [
'factories' => [
Controller\Controller2::class => Factory\Controller2Factory::class,
Controller\Controller3::class => Factory\Controller3Factory::class,
],
],
'controllers' => [
'factories' => [
Controller\Controller1::class => Factory\Controller1Factory::class
],
]
Controller1Factory.php
class Controller1Factory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new Controller1(
$container->get(Controller2::class),
$container->get(Controller3::class),
);
}
}
But now i have error that Controller2 and Controller3 also have DI in their constuctors, so i make new custom factories and so on and so on...until i get to my models.
And Models also have Dependency that is injected in their controller which is zend native \Zend\Db\TableGateway\TableGatewayInterface and i now have to edit my conf file again and add TableGatewayInterface.
And that is wrong. I should never be forced to inject native zend classes and services this way.
So what am i doing wrong?
If your Controller has no dependency, it's the best way to declare it in module.config.php as you did.
But if it has dependecies, it's better to do it in Module.php. You first declare your services, then the controller (don't forget to remove it from module.config.php), injecting in it the services it depends :
public function getServiceConfig()
{
return [
'factories' => [
Model\MyObjectTable::class => function($container) {
$tableGateway = $container->get(Model\MyObjectTableGateway::class);
return new Model\MyObjectTable($tableGateway);
},
Model\MyObjectTableGateway::class => function($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\User());
return new TableGateway('myObject', $dbAdapter, null, $resultSetPrototype);
},
]
];
}
public function getControllerConfig()
{
return [
'factories' => [
Controller\MyObjectController::class => function($container) {
return new Controller\MyObjectController(
$container->get(Model\MyObjectTable::class)
);
},
]
];
}
And in your controller:
private $table;
public function __construct(MyObjectTable $table)
{
$this->table = $table ;
}
It is described in This ZF3 tutorial page and following.
I'm building my first Zend Framework 2 application with the skeleton tutorial. But whenever I try to call any plugin from any controller, I get the error message:
A plugin by the name "PLUGIN_NAME" was not found in the plugin manager
Zend\Mvc\Controller\PluginManager
Unfortunately I don't know which part of the code could help you to help me. I post some files which I think could be important.
config/modules.config
return [
'Zend\Router',
'Zend\Validator',
'Zend\Form',
'Album',
'Application',
'User',
];
module/User/src/Module.php
<?php
namespace User;
use User\Model\User;
use User\Model\UserTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
class Module
{
const VERSION = '1.0.0dev';
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getConfig()
{
return include __DIR__ . '/../config/module.config.php';
}
public function getServiceConfig()
{
return array(
'factories' => array(
'User\Model\UserTable' => function($sm) {
$tableGateway = $sm->get('UserTableGateway');
$table = new UserTable($tableGateway);
return $table;
},
'UserTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new User());
return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
}
module/User/config/module.config.php
<?php
namespace User;
use Zend\ServiceManager\Factory\InvokableFactory;
use Zend\Router\Http\Segment;
return [
'controllers' => [
'factories' => [
Controller\UserController::class => InvokableFactory::class,
],
'invokables' => [
'User\Controller\User' => 'User\Controller\UserController',
],
],
'router' => [
'routes' => [
'user' => [
'type' => Segment::class,
'options' => [
'route' => '/user[/:action[/:id]]',
'constraints' => [
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
],
'defaults' => [
'controller' => Controller\UserController::class,
'action' => 'index',
],
],
],
],
],
'view_manager' => [
'template_path_stack' => [
'user' => __DIR__ . '/../view',
],
],
];
I'm trying to call my plugins like this from a controller action:
$this->flashMessanger()->...
$this->identity();
$this->getServiceLocator();
Because I really needed the Service Locator, I found a workaround, which is not so nice I think, but works for me:
$sm = $this->getEvent()->getApplication()->getServiceManager();
But I guess something is wrong here.
Edit:
For a better reproduction of what I did (I installed Zend Framework again and it still gives me the same error):
Installed Zend Framework (2.4 I guess?) by this installation guide (https://framework.zend.com/manual/2.4/en/ref/installation.html). I installed it with the following command:
composer create-project -sdev --repository-url="https://packages.zendframework.com" zendframework/skeleton-application
On the installation, when I was asked if I want to install a minimum install, I chose "No". Every next question I answered with "Yes" (the installer asks to install a lot of modules, I installed them all).
The installer asked in which config I want to inject "ZendDeveloperTools". I answered 2 (config/development.config.php.dist). For all others I chose 1 (config/modules.config.php).
I tested the skeleton application by calling the url in my browser, it worked.
I downloaded the Album module from GitHub here (https://github.com/Hounddog/Album).
I copied the album module to my modules folder.
I added the entry 'Album' to my config/modules.config.php file
I browsed to the album page
I'm getting the error:
A plugin by the name "getServiceLocator" was not found in the plugin
manager Zend\Mvc\Controller\PluginManager
I assume the reason for this error is the getAlbumTable() method in the AlbumController
public function getAlbumTable()
{
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('Album\Model\AlbumTable');
}
return $this->albumTable;
}
In the ZF2 docs for controller plugins you can read that your controller has to implement the following methods: setPluginManager, getPluginManager and plugin. You can also read:
For an extra layer of convenience, both AbstractActionController and AbstractActionController have __call() implementations that allow you to retrieve plugins via method calls:
$plugin = $this->url();
Does your controller extend AbstractActionController or AbstractActionController? If yes, it should work as mentioned in the docs (so those methods you mention in your question should work).
Since you didn't share any controller code it is hard to say whether this is the problem...
UPDATE
The error you get is not related to the configuration of your ControllerPluginManager, but you get this error because you are doing:
$sm = $this->getServiceLocator();
Since the method getServiceLocator doesn't exist the magic __call() method is executed and this leads to the error.
This is because in the latest versions of ZF2 the controller classes are no longer 'service locator aware' meaning you cannot retrieve the ServiceManager by calling $this->getServiceLocator().
Instead you will have to inject your Album\Model\AlbumTable service into the controller class inside a factory:
1) Add a constructor method to your controller class:
public function __construct(AlbumTable $albumTable){
$this->albumTable = $albumTable;
}
2) Create a factory for your controller:
<?php
namespace Album\Controller\Factory;
use Album\Controller\AlbumController;
class AlbumControllerFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return AlbumController
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$controllerPluginManager = $serviceLocator;
$serviceManager = $controllerPluginManager->get('ServiceManager');
$albumTable = $serviceManager->get('Album\Model\AlbumTable');
return new AlbumController($albumTable);
}
}
3) Register your controller factory inside your module.config.php:
'factories' => [
`Album\Controller\AlbumController` => `Album\Controller\Factory\AlbumControllerFactory`,
],
The function getAlbumTable() was used in ZF2 tutorial but in ZF3 it was replaced by member table.
Change the deleteAction() function in AlbumController.php to read:
public function deleteAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('album');
}
$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->getPost('del', 'No');
if ($del == 'Yes') {
$id = (int) $request->getPost('id');
$this->table->deleteAlbum($id);
}
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
return array(
'id' => $id,
'album' => $this->table->getAlbum($id)
);
}
Note the two lines reading $this->table->... instead of $this->getAlbumTable()->...
I have two controllers in my Module and both of them need to see if the user is logged in or not. The Login controllers authenticates the user using DbTable and writes the identity into the storage.
I am using >Zend\Authentication\AuthenticationService; $auth = new AuthenticationService();
inside the controller function but then i instantiate its instance on multiple pageAction()
for this i wrote a function into the Module.php
as follows
public function getServiceConfig()
{
return array(
'factories' => array(
'Application\Config\DbAdapter' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
return $dbAdapter;
},
'Admin\Model\PagesTable' => function($sm){
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$pagesTable = new PagesTable(new TableGateway('pages',$dbAdapter) );
return $pagesTable;
},
'Admin\Authentication\Service' => function($sm){
return new AuthenticationService();
}
),
);
}
As you can see i am returning new AuthenticationService() every time which i think is bad. I could not find how to grab the already instantiated instance of the service or
i have to write a singleton class for this. Please advise any sample code snipets with deeper explaination would be highly regarded and appreciated thanks.
Try this instead:
public function getServiceConfig()
{
return array(
'aliases' => array(
'Application\Config\DbAdapter' => 'Zend\Db\Adapter\Adapter',
'Admin\Authentication\Service' => 'Zend\Authentication\AuthenticationService',
),
'factories' => array(
'Admin\Model\PagesTable' => function ($serviceManager) {
$dbAdapter = $serviceManager->get('Application\Config\DbAdapter');
$tableGateway = new TableGateway('pages', $dbAdapter);
$pagesTable = new PagesTable($tableGateway);
return $pagesTable;
},
),
);
}
Note mainly the 'aliases' section of the root array, any other changes are just cosmetic and you may prefer to do the original way you suggested (such as using a factory to retrieve the Zend\Db\Adapter\Adapter instance instead of aliasing that too).
Kind Regards,
ise