How to inject and use plugin in zend framework 3 - php

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);
}
],
],

Related

How to get a service manager instance in a controller in zend framework 3?

I am trying to get a ServiceManager instance in my controller to use a factory for Db\Adapter.
I added to module/Application/config/module.config.php:
'service_manager' => [
'factories' => [
Adapter::class => AdapterServiceFactory::class,
],
],
To config/autoload/local.php I added the following lines:
'db' => [
'driver' => 'Mysqli',
'database' => 'mydb',
'username' => 'myuser',
'password' => 'mypassword',
]
An now I want to access the ServiceManager in my module/Application/src/Controller/IndexController.php. How do I do that?
I tried $sm = $this->getPluginManager(); without success. If I run $serviceManager->get(Adapter::class) with the PluginManager it gives me an error:
Too few arguments to function Zend\Db\Adapter\Adapter::__construct(), 0 passed in (...)\vendor\zendframework\zend-servicemanager\src\Factory\InvokableFactory.php on line 30 and at least 1 expected
What can I do, to get a ServiceManager that will get my that Adapter object?
I changed the controller factory from
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class,
],
],
to
'controllers' => [
'factories' => [
Controller\IndexController::class => function(ContainerInterface $serviceManager) {
return new Controller\IndexController($serviceManager);
},
],
],
I also added a getServiceConfig() method to the module.config.php and added a constructor to the IndexController, which receives the ServiceManager. Now I have access inside the controller.
But my question is now: is there a nicer, a more "zend like" way to achieve this?
Thanks to SO's great related topics I finally found the answer. ServiceManager in ZF3
It seems to be done by using Controller Factories, almost like I did.
I'm experienced with ZF1 and now I'm learning ZF3, I wanted to do a simple thing: set the DB configuration in the configuration file, and then get the db adapter at the controller. It took me awhile to figure it out as the official documents have millions of options for different customization. So I'm posting my answer to help anyone looking.
1- Add the db credentials in config/autoload/global.php or config/autoload/local.php, like this:
<?php
return [
'db' => [
'driver' => 'Pdo_Mysql',// can be "Mysqli" or "Pdo_Mysql" or other, refer to this link for the full list: https://docs.zendframework.com/zend-db/adapter/
'hostname' => 'localhost',// optional
'database' => 'my_test_db',
'username' => 'root',
'password' => 'root',
],
];
2- In module/YOUR_MODULE_NAME/config/module.config.php, add this under the controllers factories section:
return [
//...
'controllers' => [
'factories' => [
//...
// Add these lines
Controller\MycontrollernameController::class => function($container) {// $container is actually the service manager
return new Controller\MycontrollernameController(
$container->get(\Zend\Db\Adapter\Adapter::class)
);// this will pass the db adapter to the controller's constructor
},
//...
]
]
//...
];
3- Finally, in your controller module/YOUR_MODULE_NAME/src/Controller/MycontrollernameController, you can get and use the db adapter:
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Db\Adapter\Adapter;
class MycontrollernameController extends AbstractActionController
{
private $db;
public function __construct($db)
{
$this->db = $db;
}
public function indexAction()
{
$result = $this->db->query('SELECT * FROM `my_table`', Adapter::QUERY_MODE_EXECUTE);
echo $result->count();// output total result
return new ViewModel();
}
}
There is another way to achieve the same thing by creating a factory for your controller, and inside that factory pass the db adapter to the controller. For beginners trying out ZF3 at hello-world level like me, I think that's too much.

Registering custom Form View Helper

With ZF2, it was very simple to register custom view helpers for custom form elements.
You could simply create an element like such:
use Zend\Form\Element;
class Recaptcha extends Element
{
protected $attributes = [
'type' => 'recaptcha',
];
protected $secret;
public function getSecret()
{
return $this->secret;
}
public function __construct($secret)
{
parent::__construct();
$this->secret = $secret;
}
}
Create a matching helper:
use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\FormElement;
class Recaptcha extends FormElement
{
public function render(ElementInterface $element)
{
return '<div class="form-group">
<div id="register_recaptcha">
<div class="g-recaptcha" data-sitekey="' . $element->getSecret() . '"></div>
</div>
</div>
<script src="//www.google.com/recaptcha/api.js"></script>';
}
}
And then wire it up at config:
return [
'form_elements' => [
'factories' => [
Recaptcha::class => RecaptchaFactory::class,
],
],
'view_helpers' => [
'invokables' => [
'recaptcha' => RecaptchaHelper::class,
],
],
];
IIRC, you would have to wire it up in the Bootstrap too
public function onBootstrap($e)
{
$application = $e->getApplication();
$services = $application->getServiceManager();
$services->get('ViewHelperManager')->get('FormElement')->addType('recaptcha', 'recaptcha');
}
Upgrading a project from ZF2 to ZF3, the custom element now appears as a textfield.
If I call the helper directly on the field, it renders properly:
{{ recaptcha( user_form.get('recaptchafield') ) | raw }}
It's the automatic association that's seemingly vanished. Such that calling formRow on each doesn't invoke the helper.
Anyone have the quick fix? Hopeful to save myself from reviewing the actual zend-form and zend-view code.
Thank you!
I had the same issue and I resolved it by replacing
$services->get('ViewHelperManager')->get('FormElement')->addType('recaptcha', 'recaptcha');
with
$services->get('ViewHelperManager')->get('FormElement')->addClass(Recaptcha::class, RecaptchaHelper::class);
The config also needed some adaption. It now reads like this:
return [
'form_elements' => [
'factories' => [
Recaptcha::class => RecaptchaFactory::class,
],
],
'view_helpers' => [
'invokables' => [
RecaptchaHelper::class => RecaptchaHelper::class,
],
],
];
Hope that helps someone else find the issue faster ;)
Invokables no longer exist in ZF3. You need to move your recatpcha view helper to the factories key instead and wire it up to Zend\ServiceManager\Factory\InvokableFactory::class

Correct way to use service manager in Zend3

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.

Plugins not working in controller

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()->...

Yii::configure does not work in module init()

I have a module calls API, and i want to load config file for it. The guide says that i have to use function \Yii::configure. I use it, but it doesn't apply any new configs. And i tried to use array instead config file, the result is same
class API extends \yii\base\Module
{
public $controllerNamespace = 'api\client\controllers';
public function init()
{
parent::init();
// \Yii::configure($this, require(__DIR__ . '/config/main.php'));
\yii::configure($this, [
'components' => [
'user' => [
'class' => 'yii\web\UserTest',
'identityClass' => 'api\client\models\User',
],
]
]);
echo \yii::$app->user->className();
die();
}
}
How I can override config in my module ?
UPDATE
You have to use setComponents method of Yii::$app
Yii::$app->setComponents(
[
'errorHandler'=>[
'errorAction'=>'forum/forum/error',
'class'=>'yii\web\ErrorHandler',
],
'user' => [
'class' => 'yii\web\User',
'identityClass' => 'app\modules\profile\models\User',
],
]
);
OLD ANSWER
Didn't it give you errors? Your casing are wrong and so instead of "yii" in small letters use "Yii" capitalized
class API extends \yii\base\Module
{
public $controllerNamespace = 'api\client\controllers';
public function init()
{
parent::init();
\Yii::configure($this, [
'components' => [
'user' => [
'class' => 'yii\web\UserTest',
'identityClass' => 'api\client\models\User',
],
]
]);
echo \Yii::$app->user->className();
die();
}
}
I see no reason to override the application components here. I'd use #StefanoMtangoo trick but to set the component to the Module itself instead of Yii::$app:
public function init()
{
parent::init();
$this->setComponents([
'db' => [
'class' => 'yii2tech\filedb\Connection',
'path' => '#app/builder/data',
]
]);
}
Then the tricky part is to differentiate between any app's components and your module's own components. For example if my Module had a model extending yii\db\ActiveRecord I'd override its getDB() as follow (original code here):
public static function getDb()
{
return Yii::$app->getModule('api')->get('db');
// instead of: return Yii::$app->getDb();
}
So whatever the app that is using my module has or hasn't a db component it won't matter.

Categories