ZF2: get access to the service manager (locator) from external class - php

In two words: I need to get access to the service manager (locator) from external class.
Details:
I have next structure in my ZF2 project:
Api.php is the class, I use in SOAP server, which is created in Controller:
class IncomingInterfaceController extends AbstractActionController
{
...
public function indexAction()
{
if (isset($_GET['wsdl']))
$this->handleWSDL();
else
$this->handleSOAP();
return $this->getResponse();
}
private function handleWSDL()
{
$autodiscover = new AutoDiscover();
$autodiscover->setClass('\Application\Api\Api')->setUri($this->getURI());
$autodiscover->handle();
}
In this Api.php class I need to get access to services.
I need something like this in my Api.php class:
public function OnNewDeal($uid)
{
$error_log=$this->getServiceLocator()->get('error_log'); // this doesn't work!
$error_log->write_error('error_text');
}

In Module.php
public function getServiceConfig() {
return array(
'invokables' => array(
'Application\Api\Api' => 'Application\Api\Api'
)
);
}
In Api.php
class Api implements ServiceLocatorAwareInterface{
protected $services;
public function OnNewDeal($uid){
$this->getServiceLocator()->get('error_log')->write_error('SOAP ERROR');
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator){
$this->services = $serviceLocator;
}
public function getServiceLocator(){
return $this->services;
}
}
In IncomingInterfaceController.php
class IncomingInterfaceController extends AbstractActionController{
...
protected $api;
public function indexAction()
{
if (isset($_GET['wsdl']))
$this->handleWSDL();
else
$this->handleSOAP();
return $this->getResponse();
}
private function handleWSDL()
{
$autodiscover = new AutoDiscover();
$autodiscover->setClass('\Application\Api\Api')->setUri($this->getURI());
$autodiscover->handle();
}
public getApi(){
if(!$api){
$this->api = $this->getServiceLocator()->get('Application\Api\Api');
}
return $this->api;
}

In controller where you do $this->handleSOAP(); use setObject with already created instance instead setClass.
You should pass into Api __construct $this->getServiceLocator() and handle it there.
class IncomingInterfaceController extends AbstractActionController
{
private function handleSOAP()
{
$soap = new Server(null, array('wsdl'=>$this->getWSDLURI()));
$soapClass = new \Application\Api\Api($this->getServiceLocator());
$soap->setObject($soapClass);
$soap->handle();
}
In Api class, handle serviceManager instance and use as you wish:
class Api
{
protected $serviceManager;
public function __construct($serviceManager)
{
$this->serviceManager = $serviceManager;
}
public function OnNewDeal($uid)
{
$this->serviceManager->get('error_log')->write_error('SOAP ERROR');
}
....
}

Perhaps your API could implement ServiceLocatorAwareInterface like:
class Api implements ServiceLocatorAwareInterface
and add
class Api implements ServiceLocatorAwareInterface
{
protected $serviceManager;
}
Then the service manager would be available
UPDATED
module.config.php example
<?php
return array(
'service_manager' => array(
'factories' => array(
'Api' => 'Namespace\Api'
),
'shared' => array(
'Api' => false
)
),
)
?>

Injecting the Service Manager instance to an user defined "service locator aware class" should responsibility of the framework's itself (via initializers, invokables or user defined factories) not a specific controller's handleSOAP() method.
Yes, #SirJ's solution will work too but that's not a good practice. ZF2 provides ready-to-use Traits and Interfaces exactly for requirements like this. Just use them!
Your very own API class should seem like this:
<?php
namespace Application\Api;
use Zend\ServiceManager\ServiceLocatorInterface;
class Api implements ServiceLocatorInterface
{
// Here is the trait. (php >= 5.4)
use \Zend\ServiceManager\ServiceLocatorAwareTrait;
public function OnNewDeal($uid)
{
$this->getServiceLocator()->get('error_log')->write_error('SOAP ERROR');
}
}
And you should add this key to your module.config.php
<?php
return array(
'service_manager' => array(
'invokables' => array(
'api-service' => 'Application\Api\Api',
)
);
Thats all! Now you can:
<?php
...
$soap = new Server(null, array('wsdl'=>$this->getWSDLURI()));
$soapClass = $this->getServiceLocator()->get('api-service');
$soap->setObject($soapClass);
...

Related

How to call doctrine.entitymanager.orm_default in zf2 plugin

I wanna use my entities inside my custom plugin. So, I am doing in that order:
1) Declared my plugin in Module\src\Plugin\Plugin.php
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Doctrine\ORM\EntityManager;
use User\Entity\UserProfile;
use Zend\ServiceManager\ServiceManager;
class AuthenticationPlugin extends AbstractPlugin {
protected $entityManager;
protected $serviceManager;
public function setServiceManager(ServiceManager $locator) {
$this->serviceManager = $locator;
}
public function getServiceManager() {
return $this->serviceManager;
}
public function getEntityManager() {
$userEntityFactory = new \Application\Factory\UserEntityFactory();
$this->entityManager = $userEntityFactory->createService($this->getServiceManager());
return $this->entityManager;
}
public function someAction($user_email) {
$user = $this->getEntityManager()->getRepository('User\Entity\User')->findBy(array('email'=>$user_email));
}
}
2) Created my factory:
namespace User\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserEntityFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
return $serviceLocator->get('doctrine.entitymanager.orm_default');
}
}
3) Defines it in module.config.php:
'service_manager' => array(
'factories' => array(
'UserEntityFactory' => 'Application\Factory\UserEntityFactory',
),
),
'controller_plugins' => array(
'invokables' => array(
'AuthenticationPlugin' => 'Application\Plugin\AuthenticationPlugin',
)
),
4) Sending ServiceLocator to my plugin in Module.php:
public function getServiceConfig() {
return array(
'factories' => array(
'AuthenticationPlugin' => function($sm) {
$locator = $sm->getServiceLocator();
$instance = new \Application\Plugin\AuthenticationPlugin();
$instance->setServiceManager($locator);
return $instance;
},
),
);
}
5) ...and calling it in onBootstrap:
$em->attach('ZfcUser\Service\User', 'register', function($e) {
$user = $e->getParam('user'); // User account object
$authenticationPlugin = new AuthenticationPlugin();
$authenticationPlugin->someAction($user->getEmail());
});
But I received the error that $locator in plugin is null... I'm confused and I am sure that I'm doing something wrong... or all. I would be happy if somebody will share experiences or will show the order of actions. Thanks.
You don't need to inject the entire service manager object into your plugin class.
You only need to inject the User\Entity\User repository object, this appears to be the only dependancy required in your plugin class.
You should pass this into the constructor of your plugin class via your factory :
public function getServiceConfig() {
return array(
'factories' => array(
'AuthenticationPlugin' => function($sm) {
return new \Application\Plugin\AuthenticationPlugin($sm->get('doctrine.entitymanager.orm_default')->getRepository('User\Entity\User'));
},
),
);
}
in your plugin class:
class AuthenticationPlugin extends AbstractPlugin {
private $userRepository;
public function __construct(\User\Entity\User $userRepository){
$this->userRepository=$userRepository;
}
public function someAction($user_email) {
$user = $this->userRepository->findBy(array('email'=>$user_email));
}
}
As you are configuring the plugin via the module.php you don't need to also declare the plugin as an invokable in your config file. So remove the following line from your module.config.php
'AuthenticationPlugin' => 'Application\Plugin\AuthenticationPlugin'
As a side note, there are various pros and cons between declaring your services/plugins in either the module.php or the module.config file. This though wasn't the question so I won't go into detail here.

Can't initialize my plugin function in ZF2 constructor

I am quite new to ZF2 and I am preparing a demo application with simple login and CRUD system. Now for login I have prepared a plugin which consists of some functions that will authenticate users, return the logged in user data, return the logged in status etc. But the problem that I am facing is I can't initialize any variable into the constructor of my controller which will store any return value from the plugin. It's always showing service not found exception.
Please find my plugin code below:
AuthenticationPlugin.php
<?php
namespace Album\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Session\Container as SessionContainer;
use Zend\View\Model\ViewModel;
use Album\Entity\User;
class AuthenticationPlugin extends AbstractPlugin{
protected $entityManager;
protected $usersession;
public function __construct(){
$this->usersession = new SessionContainer('UserSession');
}
public function dologin($email,$password)
{
$getData = $this->em()->getRepository('Album\Entity\User')->findOneBy(array('email' => $email, 'password' => $password));
if(count($getData)){
$this->usersession->offsetSet('userid', $getData->getId());
return true;
}
else{
return false;
}
}
public function isloggedin(){
$userid = $this->usersession->offsetGet('userid');
if(!empty($userid)){
return true;
}
else{
return false;
}
}
public function logindata(){
$userid = $this->usersession->offsetGet('userid');
$getData = $this->em()->getRepository('Album\Entity\User')->findOneBy(array('id' => $userid));
return $getData;
}
public function logout(){
$this->usersession->offsetUnset('userid');
}
public function em(){
return $this->entityManager = $this->getController()->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
}
?>
In my module.config.php
'controller_plugins' => array(
'invokables' => array(
'AuthPlugin' => 'Album\Controller\Plugin\AuthenticationPlugin',
)
),
Now I am doing this in my controller:
protected $entityManager;
protected $isloggedin;
protected $authentication;
public function __construct(){
$this->authentication = $this->AuthPlugin();
$this->isloggedin = $this->authentication->isloggedin();
}
The error I am getting is like below:
An error occurred An error occurred during execution; please try again
later. Additional information:
Zend\ServiceManager\Exception\ServiceNotFoundException
File:
D:\xampp\htdocs\subhasis\zf2-tutorial\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php:555
Message:
Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for AuthPlugin
But if I write the above constructor code in any of my controller actions everything is fine. in ZF1 I could initialize any variable in the init() method and could use the variable in any of my actions. How can I do this in ZF2? Here, I want to detect if the user is logged in the constructor itself. Now I have to call the plugin in every action which I don't want.
What should I do here?
The error you are receiving is because you are trying to use the ServiceManager (via the Zend\Mvc\Controller\PluginManager) in the __construct method of the controller.
When a controller is registered as an invokable class, the Service Manager (ControllerManager) is responsible for the creating the controller instance. Once created, it will then call the controllers various default 'initializers' which also inlcudes the plugin manager. By having your code in __construct it is trying to use the plugin manager before it has been set.
You can resolve this by using a controller factory, rather than an invokable in module.config.php.
'controllers' => [
'factories' => [
'MyModule\Controller\Foo' => 'MyModule\Controller\FooControllerFactory',
],
],
Then the factory
namespace MyModule\Controller\FooControllerFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class FooControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllerManager)
{
$serviceManager = $controllerManager->getServiceLocator();
$controllerPluginManager = $serviceManager->get('ControllerPluginManager');
$authPlugin = $controllerPluginManager->get('AuthPlugin');
return new FooController($authPlugin);
}
}
Lastly, update the controller __construct to add the new argument and remove the call to $this->authPlugin()
class FooController extends AbstractActionController
{
public function __construct(AuthPlugin $authentication)
{
$this->authentication = $authentication;
$this->isloggedin = $authentication->isloggedin();
}
}

ZF2 - How do I pass constructor arguments to a Service in Module.php

I have a PHP class that has a constructor which takes arguments:
ex:
Users.php
namespace Forms;
class Users
{
protected $userName;
protected $userProperties = array();
public function __construct($userName, array $userProperties = null)
{
$this->userName = $userName;
$this->userProperties = $userProperties;
}
public function sayHello()
{
return 'Hello '.$this->userName;
}
}
Now, I am trying to use this class in a Model file like this:
$form = new Forms\Users( 'frmUserForm', array(
'method' => 'post',
'action' => '/dosomething',
'tableWidth' => '800px'
) );
It works just fine. However, in order to write Unit tests, I need to refactor this to a Service Factory, so I can mock it.
So, my Service factory now looks like this:
public function getServiceConfig()
{
return array(
'initializers' => array(
function ($instance, $sm)
{
if ( $instance instanceof ConfigAwareInterface )
{
$config = $sm->get( 'Config' );
$instance->setConfig( $config[ 'appsettings' ] );
}
}
),
'factories' => array(
'Forms\Users' => function ($sm )
{
$users = new \Forms\Users();
return $users;
},
)
);
}
With this refactoring in place, I have two questions:
How do I use the Forms\Users Service in the Model File, considering ServiceLocator is not available in a model file?
How can I change the Service Factory instance to take arguments for the constructor while instantiating Users class in the model.
I faced similar issue some time. Then I decide not to pass arguments to Factory itself. But build setter methods for handling this like.
namespace Forms;
class Users
{
protected $userName;
protected $userProperties = array();
public function setUserName($userName)
{
$this->userName = $userName;
}
public function setUserProperties($userProperties)
{
$this->userProperties = $userProperties;
}
public function sayHello()
{
return 'Hello '.$this->userName;
}
}
You can implement your model ServiceLocatorAwareInterface interface Then it would can call any service like below.
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyModel implements ServiceLocatorAwareInterface
{
protected $service_manager;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->service_manager = $serviceLocator;
}
public function getServiceLocator()
{
return $this->service_manager;
}
public function doTask($name, $properties)
{
$obj = $this->getServiceLocator('Forms\Users');
$obj->setUserName($name);
$obj->setUserProperties($properties);
}
}

How to put getAlbumTable() into a service?

What I really dislike in ZF2 is that Controller is aware of storage engine (This is a clear violation of SRP) and that a storage engine has a concept of Tables. I believe that this is not correct way, and Controller should only be aware of services (while only services should be aware of Storage engine)
class AlbumController extends AbstractActionController
{
protected $albumTable;
public function getAlbumTable()
{
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('Album\Model\AlbumTable');
}
return $this->albumTable;
}
Nowhere in manual I could find on how to put that into a Service and make controller only aware of actions. How would you put that into a service?
I know that's how it's done in the official tutorial, but in my opinion it's not the best approach. Instead you want to inject your dependencies into your controller class via. its constructor. This makes it easier to see what's going on, and easier to test.
To do this, modify your controller class to add an appropriate constructor:
class AlbumController extends AbstractActionController
{
protected $albumTable;
public function __construct(AlbumTable $albumTable)
{
$this->albumTable = $albumTable;
}
}
Then, remove the invokable line in your module.config.php for this controller, since it can no longer just be instantiated without any arguments. Instead, you define a factory to tell ZF how to instantiate the class. In your Module.php:
use Zend\Mvc\Controller\ControllerManager;
use Album\Controller\AlbumController;
class Module
{
public function getControllerConfig()
{
return array(
'factories' => array(
'Album\Controller\Album' => function(ControllerManager $cm) {
$sm = $cm->getServiceLocator();
$albumTable = $sm->get('Album\Model\AlbumTable');
$controller = new AlbumController($albumTable);
return $controller;
},
),
);
}
}
(Alternatively you could create a separate factory class to do this.)
In your controller actions you can then access the album table via. $this->albumTable instead of $this->getAlbumTable().
Hopefully you can see that this approach can easily be modified to inject a service class instead. If you want your album table injected into the service, and the service injected into the controller; you might end up with something like this:
class Module
{
public function getServiceConfig()
{
return array(
'factories' => array(
'Album\Model\AlbumTable' => function($sm) {
$tableGateway = $sm->get('AlbumTableGateway');
$table = new AlbumTable($tableGateway);
return $table;
},
'AlbumTableGateway' => function($sm) {
[etc...]
},
'Album\Service\AlbumService' => function($sm) {
$albumTable = $sm->get('Album\Model\AlbumTable');
return new AlbumService($albumTable);
}
),
);
}
public function getControllerConfig()
{
return array(
'factories' => array(
'Album\Controller\Album' => function(ControllerManager $cm) {
$sm = $cm->getServiceLocator();
$albumService = $sm->get('Album\Service\AlbumService');
$controller = new AlbumController($albumService);
return $controller;
},
),
);
}
}
Controller:
class AlbumController extends AbstractActionController
{
protected $albumService;
public function __construct(AlbumService $albumService)
{
$this->albumService = $albumService;
}
public function someAction()
{
// do stuff with $this->albumService
}
}

ZF2 dependency injection in parent

I want to call the method SteeringWheelMapper->fetchCarBrandList() from a controller.
This works good now, but there's a problem.
The SteeringWheelMapper extends the AbstractWebServiceMapper which has a construct method which requires an instance of \Zend\Http\Client.
As you can see in my module.config.php file, I use "factories" for the instantiation of my SteeringWheelMapper.
The supplier has multiple products, so I will have to build multiple mappers. In the current situation that means I have to add a key to the factories config for every mapper which extends AbstractWebServiceMapper.
For example, when I want to add an ExhaustMapper, I have to add
SupplierName\Mapper\Exhaust => function ($serviceMapper) {
$httpClient => new \Zend\Http\Client;
return new SupplierName\Mapper\ExhaustMapper($httpClient);
}
Now I am repeating myself, because I also have to do this for SupplierName\Mapper\SteeringWheelMapper.
I think there should be a way to make a factory for all the mappers, instead of a new key added to the factories config.
Is my thought right?
Does anyone has a suggestion how I should do this?
Please see code below.
I'm using ZF2 and I use this setup:
/vendor
SupplierName
config
module.config.php
log
log.log
src
SupplierName
Entity
AbstractEntity.php
SteeringWheelEntity.php
Mapper
AbstractWebServiceMapper.php
SteeringWheelMapper.php
$steeringWheelMapper = $this->getServiceLocator()->get('SupplierName\Mapper\SteeringWheel');
$carBrandList = $steeringWheelMapper->fetchCarBrandsList();
SteeringWheelMapper.php
<?php
namespace SupplierName\Mapper;
class SteeringWheelMapper extends AbstractWebServiceMapper
{
public function fetchCarBrandList()
{
// Code for request
// Dispatch HTTP request
$this->dispatch();
}
}
My SupplierName/config/module.config.php looks like this:
<?php
return array(
'service_manager' => array(
'factories' => array(
'SupplierName\Mapper\SteeringWheel' => function ($serviceManager) {
$httpClient = new \Zend\Http\Client;
return new SupplierName\Mapper\SteeringWheelMapper($httpClient);
},
),
),
'supplier_name' => array(
'api' => array(
'url' => 'http://api.example.com',
),
'log' => array(
'file_location' => __DIR__ . '/../log/log.log',
),
),
);
What you're actually talking about is an abstract factory, the service manager supports the concept, but you'll need to write your own, here's an example that assumes all your mappers begin with SupplierName\Mapper
<?php
namespace SupplierName\Services;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MapperAbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
{
if (0 === strpos($requestedName, 'SupplierName\\Mapper') && class_exists($requestedName)){
return true;
}
return false;
}
public function createServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
{
$httpClient = new \Zend\Http\Client;
return new $requestedName($httpClient);
}
}
In your service config, add an abstract factories key, along with the fqcn of the abstract factory, and hopefully any time you call $sm->get('SupplierName\Mapper\SomeClass'); providing the class exists, you'll get a composed instance returned
public function getServiceConfig()
{
return array(
'abstract_factories' => array(
'SupplierName\Services\MapperAbstractFactory'
),
);
}
Final working solution:
<?php
// module/Application/src/Application/Controller/IndexController.php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\I18n\Translator\Translator;
class IndexController extends AbstractActionController
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function indexAction()
{
$steeringWheelMapper = $this->getServiceLocator()->get('SupplierName\Mapper\SteeringWheel');
$carBrandList = $steeringWheelMapper->fetchCarBrandList();
return new ViewModel();
}
}
<?php
// vendor/SupplierName/src/SupplierName/Module.php
namespace SupplierName;
class Module
{
public function getConfig()
{
return include __DIR__ . '/../../config/module.config.php';
}
public function getServiceConfig()
{
return array(
'abstract_factories' => array(
'SupplierName\Mapper\MapperAbstractFactory'
),
);
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__,
),
),
);
}
}
<?php
// vendor/SupplierName/src/SupplierName/Mapper/SteeringWheelMapper.php
namespace SupplierName\Mapper;
class SteeringWheelMapper extends AbstractWebServiceMapper
{
public function fetchCarBrandList()
{
$this->dispatch();
}
}
<?php
// vendor/SupplierName/src/SupplierName/Mapper/AbstractWebServiceMapper.php
namespace SupplierName\Mapper;
use \Zend\Http\Client;
class AbstractWebServiceMapper
{
public function __construct(Client $client)
{
}
public function dispatch()
{
}
}
<?php
// vendor/SupplierName/src/SupplierName/Mapper/MapperAbstractFactory.php
namespace SupplierName\Mapper;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use \Zend\Http\Client;
class MapperAbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
{
if (0 === strpos($requestedName, 'SupplierName\Mapper')) {
$requestedName .= 'Mapper';
if (class_exists($requestedName)) {
return true;
}
}
return false;
}
public function createServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
{
$requestedName .= 'Mapper';
$httpClient = new Client();
return new $requestedName($httpClient);
}
}

Categories