Я хочу получить сервисный локатор в моем собственном плагине. Я создаю его через фабрику в Module.php:
public function getControllerPluginConfig() {
return [
'factories' => [
'Application\Plugin\AppPlugin' => function($serviceManager) {
$serviceLocator = $serviceManager->getServiceLocator();
$appPlugin = new \Application\Plugin\AppPlugin();
$appPlugin->setLocator($serviceLocator);
return $appPlugin;
}
]
];
}
…и мой плагин:
<?php
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AppPlugin extends AbstractPlugin {
protected $locator;
public function getAppInfo() {
$config = $this->locator->get('Config');
}
/**
* Set Service Locator
* @param \Zend\ServiceManager\ServiceManager $locator
*/
public function setLocator($locator) {
$this->locator = $locator;
}
}
Затем я вызываю getAppInfo () в моем контроллере:
$appPlugin = new AppPlugin();
$appPlugin->getAppInfo();
…и я получаю ошибку:
Fatal error: Call to a member function get() on a non-object in /vagrant/app/module/Application/src/Application/Plugin/AppPlugin.php on line 13
Call Stack
# Time Memory Function Location
1 0.0027 232104 {main}( ) ../index.php:0
2 0.6238 3166904 Zend\Mvc\Application->run( ) ../index.php:21
3 1.4410 5659112 Zend\EventManager\EventManager->trigger( ) ../Application.php:314
4 1.4410 5659112 Zend\EventManager\EventManager->triggerListeners( ) ../EventManager.php:205
5 1.4411 5660872 call_user_func ( ) ../EventManager.php:444
6 1.4411 5661440 Zend\Mvc\DispatchListener->onDispatch( ) ../EventManager.php:444
7 1.4505 5704600 Zend\Mvc\Controller\AbstractController->dispatch( ) ../DispatchListener.php:93
8 1.4505 5705080 Zend\EventManager\EventManager->trigger( ) ../AbstractController.php:118
9 1.4505 5705080 Zend\EventManager\EventManager->triggerListeners( ) ../EventManager.php:205
10 1.4507 5716656 call_user_func ( ) ../EventManager.php:444
11 1.4507 5716784 Zend\Mvc\Controller\AbstractActionController->onDispatch( ) ../EventManager.php:444
12 1.4508 5717504 Application\Controller\IndexController->indexAction( ) ../AbstractActionController.php:82
13 1.4641 5727768 Application\Plugin\AppPlugin->getAppInfo( ) ../IndexController.php:21
Но если я передаю сервисный локатор с моего контроллера, он работает нормально:
$appPlugin = new AppPlugin();
$appPlugin->setLocator($this->getServiceLocator());
$appPlugin->getAppInfo();
Я буду рад, если кто-нибудь объяснит мне, что я делаю не так. Благодарю.
Когда это вообще возможно, вы не должны пытаться получить доступ к ServiceLocator внутри какого-либо класса, кроме фабрики. Основная причина этого заключается в том, что если ServiceLocator внедряется в ваш класс, вы теперь не представляете, каковы зависимости этого класса, потому что теперь он может потенциально содержать что-либо.
Что касается внедрения зависимостей, у вас есть два основных варианта: внедрение конструктора или установки. Как правило, всегда предпочитайте конструктор инъекций. Внедрение сеттера должно использоваться только для необязательных зависимостей, и это также делает ваш код более двусмысленным, потому что класс теперь изменчив. Если вы используете чисто конструкторское внедрение, ваши зависимости неизменны, и вы всегда можете быть уверены, что они будут там.
Кроме того, вместо использования замыканий, как правило, лучше использовать конкретный класс фабрики, поскольку замыкания не могут быть кэшированы с помощью кода операции, а также ваш массив конфигурации не может быть кэширован, если он содержит замыкания.
Увидеть https://stackoverflow.com/a/18866169/1312094 для хорошего описания того, как настроить класс для реализации FactoryInterface
При этом давайте придерживаться вашего примера фабрики замыкания для целей этого обсуждения. Вместо внедрения ServiceManager мы внедряем массив конфигурации (который все еще слишком зависим от бога; лучше ввести конкретный ключ конфигурации, который вам нужен, или еще лучше — экземпляр класса).
public function getControllerPluginConfig() {
return [
'factories' => [
\Application\Plugin\AppPlugin::class => function($serviceManager) {
$serviceLocator = $serviceManager->getServiceLocator();
$config = $serviceLocator->get('Config');
$appPlugin = new \Application\Plugin\AppPlugin($config);
return $appPlugin;
}
]
];
}
И здесь плагин модифицируется, чтобы принять конфигурацию в конструкторе. Я предполагаю, что этот код плагина является просто доказательством концепции, и что вы действительно сделаете что-то полезное с плагином, кроме возврата конфигурации, но, надеюсь, это действительно показывает шаблон для внедрения ваших зависимостей плагина.
<?php
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AppPlugin extends AbstractPlugin {
protected $config;
public function __construct($config){
$this->config = $config;
}
public function getAppInfo() {
return $this->config;
}
}
Кроме того, небольшая вещь, но при условии PHP 5.5+, рассмотрите возможность использования \Application\Plugin\AppPlugin::class
как я сделал в примере выше, вместо строкового литерала для ключа конфигурации.
Вы не можете создать экземпляр класса непосредственно в контроллере, который вам нужен, чтобы получить его из диспетчера плагинов контроллера с помощью ключа ‘Application \ Plugin \ AppPlugin’
$pluginManager = $this->getPluginManager();
$appPlugin = $pluginManager->get('Application\Plugin\AppPlugin');
$appPlugin->getAppInfo();
В качестве альтернативы вы можете изменить метод на:
public function getAppInfo()
{
$serviceManager = $this->controller->getServiceLocator();
$config = $serviceManager->get('Config');
}
Я создал его через ServiceLocatorAwareInterface и сделал его активируемым:
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class AppPlugin extends AbstractPlugin implements ServiceLocatorAwareInterface {
/**
* @var ServiceLocatorInterface
*/
protected $serviceLocator;
/**
* Get system information
* @return type
*/
public function getAppInfo() {
$config = $this->getServiceLocator()->get('Config');
// to do something...
}
/**
* Retrieve service manager instance
*
* @return ServiceLocator
*/
public function getServiceLocator() {
return $this->serviceLocator->getServiceLocator();
}
/**
* Set service locator
*
* @param ServiceLocatorInterface $serviceLocator
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator;
}
}
Затем в module.appplication.config:
'controller_plugins' => [
'invokables' => [
'AppPlugin' => \Application\Controller\Plugin\AppPlugin::class
]
]
…и теперь я могу использовать свой плагин в контроллере следующим образом:
$app = $this->AppPlugin()->getAppInfo();