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()->...
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 am creating a website using Zend Framework 2, and I'm using as an example the exercise from the official course of Zend Technology, Zend Framework 2: Fundamentals.
I have a table called posts and I want to show the table content in my home page, ordered by id. These are the codes I have written:
Controller/PostsTableTrait.php
trait PostsTableTrait
{
private $postsTable;
public function setPostsTable($postsTable)
{
$this->postsTable = $postsTable;
}
}
Controller/IndexController.php
class IndexController extends AbstractActionController
{
use PostsTableTrait;
public function indexAction()
{
return new ViewModel(array(
'post' => $this->postsTable->getPosts()
));
}
}
Factory/IndexControllerFactory.php
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator()->get('ServiceManager');
$indexController = new IndexController();
$indexController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
return $indexController;
}
}
Factory/PostsTableFactory.php
class PostsTableFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new PostsTable(PostsTable::$tableName, $serviceLocator->get('Zend\Db\Adapter\AdapterService'));
}
}
Model/PostsTable.php
class PostsTable extends TableGateway
{
public static $tableName = "posts";
public function getPosts()
{
$select = new Select(self::$tableName);
$select->columns(array(
'date',
'title',
'text',
'category'
));
$select->order('id DESC');
return $select;
}
}
config/module.config.php
'controllers' => array(
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\Panel' => 'Rxe\Controller\PanelController'
),
'factories' => array(
'Rxe\Factory\PanelController' => 'Rxe\Factory\PanelControllerFactory'
)
),
'service_manager' => array(
'factories' => array(
'Rxe\Factory\PanelForm' => 'Rxe\Factory\PanelFormFactory',
'Rxe\Factory\PanelFilter' => 'Rxe\Factory\PanelFilterFactory',
'Rxe\Factory\PostsTable' => 'Rxe\Factory\PostsTableFactory',
'Zend\Db\Adapter\AdapterService' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
I don't know if the error could be in the getPosts() method. I have tried many different ways to return the query but none of them made any difference, not even showed another error.
You have registered the controller as an 'invokable'. When the the controller manager creates IndexController it will do so without using the IndexControllerFactory; therefore the Rxe\Factory\PostsTable dependency is never set.
To fix this, update module.config.php and register the index controller with your factory class.
'controllers' => [
'factories' => [
'Rxe\Controller\Index' => 'Rxe\Factory\IndexControllerFactory',
],
],
Also (not an error as such) but the IndexControllerFactory calls ->get('ServiceManager') using the service manager.
You could update it to be like this.
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllerManager)
{
// #var \Zend\ServiceManager\ServiceManager
$serviceManager = $controllerManager->getServiceLocator();
$indexController = new IndexController();
$indexController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
return $indexController;
}
}
Could you please give a hint why PHP-DI integration with Zend Framework 2 is not working for me (reproduced with Apache/2.4.9 (Win64) PHP/5.5.12 and Apache/2.2.22 (Win32) PHP/5.3.13).
composer.json:
{
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": "2.3.5",
"mnapoli/php-di": "4.4.6",
"mnapoli/php-di-zf2": "0.3.0",
...
},
...
config\application.config.php:
<?php
return array(
'modules' => array(
'Morpho',
'DI\ZendFramework2',
),
'module_listener_options' => array(
'module_paths' => array(
'./module',
'./vendor',
),
),
);
?>
module/Morpho/config.module.config.php:
<?php
return array(
'service_manager' => array(
'factories' => array(
'DI\Container' => function() {
$builder = new DI\ContainerBuilder();
$builder->addDefinitionsFromFile("config/di.yml");
return $builder->build();
},
),
),
'router' => array(
...
),
'controllers' => array(
...
),
'view_manager' => array(
...
),
);
config/di.yml:
Morpho\Service\PartOfSpeechService:
class: Morpho\Service\PhpMorphyPartOfSpeechService
module/Morpho/src/Morpho/Controller/PartOfSpeechController:
class PartOfSpeechController extends AbstractRestfulController {
...
/**
* #Inject
* #var PartOfSpeechService
*/
public $partOfSpeechService;
public function processPostData(Request $request) {
$partsOfSpeech = $this->partOfSpeechService->getPartsOfSpeech("test", "en_EN");
return new JsonModel($partsOfSpeech);
}
}
When running this code under apache each time I get:
PHP Fatal error: Uncaught exception 'Zend\ModuleManager\Exception\RuntimeException'
with message 'Module (DI\ZendFramework2) could not be initialized.' in \vendor\zendframework\zendframework\library\Zend\ModuleManager\ModuleManager.php:195
Stack trace:
0 \vendor\zendframework\zendframework\library\Zend\ModuleManager\ModuleManager.php(169): Zend\ModuleManager\ModuleManager->loadModuleByName(Object(Zend\ModuleManager\ModuleEvent))
1 \vendor\zendframework\zendframework\library\Zend\ModuleManager\ModuleManager.php(96): Zend\ModuleManager\ModuleManager->loadModule('DI\ZendFramewor...')
2 [internal function]: Zend\ModuleManager\ModuleManager->onLoadModules(Object(Zend\ModuleManager\ModuleEvent))
3 \vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php(468):
call_user_func(Array, Object(Zend\ModuleManager\ModuleEvent))
4 \vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php(207): Zend\EventM in \vendor\zendframework\zendframework\library\Zend\ModuleManager\ModuleManager.php on line 195
Any your thoughts would be really appreciated.
It doesn't work because you are using the old YAML syntax, but since PHP-DI v4.0 the syntax is now PHP.
Head over to the documentation to learn about the syntax: http://php-di.org/doc/definition.html
For a service:
factory config:
'factories' => array(
'MyService' => 'Application\Factory\MyService',
),
Factory class:
class MyService implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceManager)
{
$purifier = new MyService($serviceManager->get('MyAwesomeDependency'));
return $purifier;
}
}
For a controller:
ControllerFactory.php:
class PartOfSpeechControllerFactory
{
public function __invoke($serviceLocator)
{
// Service locator here is the ControllerManager so get ServiceManager
$serviceManager = $serviceLocator->getServiceLocator();
$controller = new PartOfSpeechController($serviceManager->get('PartOfSpeechService'));
return $controller;
}
}
class PartOfSpeechController.php
class PartOfSpeechController extends AbstractRestfulController {
protected $partOfSpeechService;
public function __construct(PartOfSpeechService $partOfSpeechService)
{
$this->partOfSpeechService = $partOfSpeechService;
}
public function processPostData(Request $request) {
$var = $this->partOfSpeechService->serviceMethod();
}
}
The config for controller:
'factories' => array(
'Application\Controller\PartOfSpeechController' => 'Application\Factory\PartOfSpeechControllerFactory'
),
I followed the suggestion given by Purple Hexagon and here is a working implementation using Service Manager:
module/Morpho/config:
...
'service_manager' => array(
'services' => array(
"PartOfSpeechService" => new Morpho\Service\PhpMorphyPartOfSpeechService(),
),
),
...
module/Morpho/src/Morpho/Controller/PartOfSpeechController.php:
class PartOfSpeechController extends AbstractRestfulController {
...
public function processPostData(Request $request) {
$serviceManager = $this->getServiceLocator();
$partsOfSpeech = $serviceManager->get("PartOfSpeechService")->getPartsOfSpeech($request->getPost("phrase"),
$request->getPost("language"));
return new JsonModel($partsOfSpeech);
}
}
Why I don't like this:
I have to use a "dummy" code to obtain serviceManager. That's "dummy" because that's not related to business logic of my application at all.
The Dependency Injection approach provided by ServiceManager makes my code dependent on the ServiceManager itself. Typically I should not care how a bean/object is injected and so shouldn't refer to any kind of container or ServiceManager in my code.
I think PHP-DI is much more closer to the bean injection model used by Java Spring (that I believe is good). Unfortunately it is still not working for me.
And finally, the approach of obtaining object from container was working in PHP-DI as well.