ZF2 Getting Autoloaded config info in a custom class - php

I have been racking my brain now for the better part of two days. I'm using Zend Apigility to create a RESTful web API application. Apigility builds its application using ZF2.
I created a custom class that I use throughout my API.
I would like to read in some autoloaded configuration information to make a connection to an memcache server. The file that is being autoloaded into the service manager is:
memcache.config.local.php:
return array(
'memcache' => array(
'server' => '10.70.2.86',
'port' => '11211',
),
);
My custom class that my REST services are calling is called checkAuth:
checkAuth.php:
namespace equiAuth\V1\Rest\AuthTools;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class checkAuth implements ServiceLocatorAwareInterface{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function userAuths() {
//** Some Code
$config = $this->getServiceLocator()->get('config');
// **
}
}
I believe I'm injecting the service manager into the class from my module.config.php with the following code:
'service_manager' => array(
'invokables' => array(
'checkAuth' => 'equiAuth\V1\Rest\AuthTools\checkAuth',
),
),
When I hit the code when I'm trying to read the 'config' from the get method of the ServiceLocator I get the following error:
Fatal error: Call to a member function get() on a non-object
I know I'm missing something, but I cant for the life of me figure out what.

Give your class an API that allow's you to 'set' the configuration from client code. This could be via the constructor or
a public setter.
namespace equiAuth\V1\Rest\AuthTools;
class CheckAuth
{
protected $config;
public function __construct(array $config = array())
{
$this->setConfig($config);
}
public function setConfig(array $config)
{
$this->config = $config;
}
public function doStuff()
{
$server = $this->config['server'];
}
}
In order to 'set' the configuration you would also need to also create a service factory class. The idea in the factory is to give you an area to inject the configuration in to the service; with the updates to CheckAuth above we can now do so very easily.
namespace equiAuth\V1\Rest\AuthTools;
use equiAuth\V1\Rest\AuthTools\CheckAuth;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class CheckAuthFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('config');
return new CheckAuth($config['memcache']);
}
}
Lastly, change the registered service with the service manager; the change here is service key form invokables to factories as we need to register the
above factory to create it.
// module.config.php
'service_manager' => array(
'factories' => array(
'checkAuth' => 'equiAuth\V1\Rest\AuthTools\CheckAuthFactory',
),
),

ZF2 use ServiceManager Container as well.
Your code is right at all, but
To auto-inject the servicelocator on your class you just need to use
$checkAuth = $this->getServiceLocator()->get('checkAuth');
then you can call
$checkAuth->userAuths();
and should work.
If you try to use:
$checkAuth = new \equiAuth\V1\Rest\AuthTools\checkAuth();
$checkAuth->userAuths(); //error
Will not work because what inject the serviceLocator into your class is just the
ServiceManager, once you use serviceManager you need to be evangelist with them.
But if you try:
$checkAuth = new \equiAuth\V1\Rest\AuthTools\checkAuth();
$checkAuth->setServiceLocator($serviceLocator)
//get $serviceLocator from ServiceManager Container
$checkAuth->userAuths();
Will work too.
Good job!

Related

Simple way to get config in ZF2 class

I want to create a simple class that will send a predefined email to given email address. So I created the following class:
namespace Custom;
use Zend\Mail\Message;
use Zend\Mail\Transport\Smtp as SmtpTransport;
use Zend\Mail\Transport\SmtpOptions;
class Notification
{
public function sendEmailNotification($to)
{
try {
$message = new Message();
$message->addTo($to)
->addFrom('test#example.com')
->setSubject('Hello')
->setBody('Predefined email body');
$transport = new SmtpTransport();
$options = new SmtpOptions(array(
'name' => 'smtp.example.com',
'host' => 'smtp.example.com',
'port' => '587',
'connection_class' => 'plain',
'connection_config' => array(
'username' => 'test#example.com',
'password' => 'somepasswd',
'ssl' => 'tls',
),
));
$transport->setOptions($options);
$transport->send($message);
} catch (\Exception $e) {
echo $e->getMessage();
}
}
}
From controller, the email is then sent with:
$notification = new \Custom\Notification();
$notification->sendEmailNotification('example#example.com');
This works as intended.
Next thing that I wanted to do is to move mail server configuration parameters into project configuration file (local.php). Problem is - how can I get the configuration parameters in my \Custom\Notification class (which is not a controller)?
Solutions I have found so far seem too complicated to a beginner like me. To do something like $config = $this->getServiceLocator()->get('Config'); you have to do some kind of magic all around the project.
Is there a simple way to get data from configuration file in a custom class?
You have to use injection for your purpose. Just create a factory for your controller, in which you inject the config to your controller.
namespace Application\Controller\Service;
class YourControllerFactory
{
public function __invoke(ContainerInterface $container)
{
$serviceLocator = $container->getServiceLocator();
$config = $serviceLocator->get('config');
$controller = new YourController($config);
return $controller;
}
}
For this purpose your controller needs a constructor which takes the config as parameter.
namespace Application\Controller;
class YourController extends AbstractActionController
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}
public function indexAction()
{
// inject your notification class with the config
$notification = new \Custom\Notification($this->config);
$notification->sendEmailNotification('example#example.com');
}
}
Therefore your notification class needs a constructor which takes the config as parameter.
Other approaches
Another way solving your issue can be the registration of your notification class as a service. Just create a factory for your notification class, in which you create all the needed stuff and then just inject it to your notification class.
namespace Application\Mail;
class Notification
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}
public function sendEmailNotification($to)
{
}
}
The factory itself is simple as pie, because we 've seen nearly the same approach in the controller factory.
namespace Application\Mail\Service;
class NotificationFactory
{
public function __invoke(ContainerInterface $container)
{
$config = $container->get('config');
$notification = new Notification($config);
return $notification;
}
}
Now you just have to note it in your module.config.php file in the service manager section.
'service_manager' => [
'factories' => [
Notification::class => NotificationFactory::class,
],
],
From now on you can access the notification class with the service container of zend framework 2. Remember the factory for your controller instance shown above? Instead of injecting the config to your controller, just inject it with the notification itself. Your notification class will be created with the needed config over the notification factory.
namespace Application\Controller\Service;
class YourControllerFactory
{
public function __invoke(ContainerInterface $container)
{
$serviceLocator = $container->getServiceLocator();
$notification = $serviceLocator->get(Notification::class);
return new YourController($notification);
}
}
Have fun. ;)

Zend Framework 2 Service in View Helper

I need to write a view helper that gets a service and do something with it. I successfully implemented the view helper to have access to the service locator. The problem is that the service I want to get is not being found through the service locator when the __invoke method is called.
The view helper code:
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper,
Zend\ServiceManager\ServiceLocatorAwareInterface,
Application\Model;
class LoggedCustomer extends AbstractHelper implements ServiceLocatorAwareInterface
{
use \Zend\ServiceManager\ServiceLocatorAwareTrait;
public function __invoke()
{
$model = new Model\Customer($this->getServiceLocator());
return $model->getCurrent();
}
}
A snippet of the model code:
namespace Application\Model;
use Application\Entity,
Andreatta\Model\Base as Base;
class Customer extends Base
{
/**
*
* #return Zend\Authentication\AuthenticationService
*/
public function getAuthService()
{
$serviceLocator = $this->getServiceLocator();
return $serviceLocator->get('Application\Auth');
}
/**
*
* #return Zend\Authentication\Adapter\AdapterInterface
*/
protected function getAuthAdapter()
{
return $this->getAuthService()->getAdapter();
}
public function getCurrent()
{
$authService = $this->getAuthService();
if ($authService->hasIdentity())
return $authService->getIdentity();
return null;
}
The snippet from module.config.php:
'service_manager' => array
(
'factories' => array
(
'Application\Auth' => function($sm)
{
$authService = $sm->get('doctrine.authenticationservice.application');
$authService->setStorage( new \Zend\Authentication\Storage\Session('Application\Auth'));
return $authService;
},
),
),
'view_helpers' => array
(
'invokables' => array
(
'loggedCustomer' => 'Application\View\Helper\LoggedCustomer',
),
),
When calling the view helper from any view I get the following:
Zend\View\HelperPluginManager::get was unable to fetch or create an instance for Application\Auth
The weird is that the application is functioning correctly (i.e. this service is being normally used by other parts of the application).
EDIT:
I did some research and I think the only services that I can access through the service manager inside the view helper are the ones registered inside the 'view_manager' section of module.config.php. Does anyone have an idea of how to access the other services?
$this->getServiceLocator() in view helper can only get u other view helpers you need to use $this->getServiceLocator()->getServiceLocator() to get the application services
#rafaame: I find a simple way to access service locator in view Helper
We just use:
$this->getView()->getHelperPluginManager()->getServiceLocator();
to get a service locator
A sample view Helper:
namespace Tmcore\View\Helper;
use Zend\View\Helper\AbstractHelper;
class Resource extends AbstractHelper
{
public function adminResource()
{
$sm = $this->getView()->getHelperPluginManager()->getServiceLocator();
$adminConfig = $sm->get('ModuleManager')->loadModule('admin')->getConfig();
return $adminConfig;
}
}
I guess you are retrieving the Zend\View\HelperPluginManager instead of the correct ServiceManager.
Probably you are not injecting it as you should.
That makes sense if thats your complete LoggedCustomer code, since you are not saving the SM. As far as I know, if you implement the ServiceLocatorAwareInterface the SM will be injected, but you have to handle it.
UPDATE:
sorry, i didnt realize you had ServiceLocatorAwareTrait; thats the same.
But, reading http://framework.zend.com/manual/2.0/en/modules/zend.service-manager.quick-start.html
i see
By default, the Zend Framework MVC registers an initializer that will inject the ServiceManager instance, which is an implementation of
Zend\ServiceManager\ServiceLocatorInterface, into any class
implementing Zend\ServiceManager\ServiceLocatorAwareInterface. A
simple implementation looks like the following.
So, the service manager is only being injected ... if you implement ServiceLocatorAwareInterface in a controller.
So, you should manually inject the service manager.
for that, what i use to do is to create a factory in Module.php, instead of creating the invokable in the config. for that you implement this function:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'loggedCustomer' => function($sm) {
$vh = new View\Helper\LoggedCustomer();
$vh->setServiceLocator($sm->getServiceLocator());
return $vh;
}
);
}
Also, i wont have the view helper implementing ServiceLocatorAwareInterface, so nothing else is automaticaly injected.
And with this it will work
It appears that the service manager that is injected into the view helper has only the services that are registered within the section 'view_manager' of module configs.
It is possible to inject the "main" service manager by registering the view helper as a factory like this:
'view_helpers' =>
[
'factories' =>
[
'loggedCustomer' => function($pluginManager)
{
$serviceLocator = $pluginManager->getServiceLocator();
$viewHelper = new View\Helper\LoggedCustomer();
$viewHelper->setServiceLocator($serviceLocator);
return $viewHelper;
},
]
],
But you have to make sure that you treat it in setServiceLocator method in the view helper. Otherwise the "limited" service manager will be injected into the view helper later on. Like this:
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
if($this->serviceLocator !== null)
return $this;
$this->serviceLocator = $serviceLocator;
return $this;
}
It fixes the problem, but it appears to be a tremendous hack to me.
In view helpers, if you want to access application services then use
$this->getServiceLocator()->getServiceLocator()

Why is the Servicemanager injected in a invokable and not a service

I'm currently playing with the ZF2 serviceManager, and i'm trying to figure out why the serviceManager doesn't inject the sm in a class that implements ServiceLocatorAwareInterface.
My main question is am i doing it right or is the "services" key not for services that implement ServiceLocatorAwareInterface but for services that don't need injection?
in Module.php
public function getServiceConfig() {
return array(
'invokables' => array(
'myService1' => 'MyModule\Service\Service'
),
'services' => array(
'myService2' => new MyModule\Service\Service(),
),
);
}
in MyModule\Service\Service.php
namespace MyModule\Service;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceManager;
class Service implements ServiceManagerAwareInterface
{
/**
* #var ServiceManager
*/
protected $serviceManager = NULL;
/**
* Retrieve service manager instance
*
* #return ServiceManager
*/
public function getServiceManager()
{
return $this->serviceManager;
}
/**
* Set service manager instance
*
* #param ServiceManager $serviceManager
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
}
}
When i call the service in a controller
<?php
namespace MyModule\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
public function IndexAction() {
$service1 = $this->getServiceLocator()->get('myService1');
$sm1 = $service1->getServiceManager();
//$sm1 becomes a object of Zend\ServiceManager\ServiceManager
//In other words i now can access the SM from within my service.
$service2 = $this->getServiceLocator()->get('myService2');
$sm2 = $service2->getServiceManager();
//$sm2 becomes NULL
//The service isn't aware of the SM and can't access it.
}
}
You should continue to use the "invokables" section if you'd like to utilize the ServiceManagerAwareInterface and the automatic injection of the Service Manager into your service.
Looking through /Zend/ServiceManager/ServiceManager.php, "services" are meant to be registered as already instantiated objects with the ServiceManager. When the service locator looks up in it's local cache of services during retrieval, it assumes that "services" are already fully set up and does not inject the sm or run any initializers.
"invokables", "factories", "abstract_factories" are created on the fly and injects the sm when the "initializers" are run on a newly created service instance (see function create($name)).
Forget about getServiceConfig() and use your module config instead. It's faster and cacheable:
module.config.php:
'service_manager' => array(
'invokables' => array(
'MyModule\Service\Service' => 'MyModule\Service\Service',
),
)
MyModule\Service\Service.php:
<?php
namespace MyModule\Service;
use \Zend\ServiceManager\ServiceLocatorInterface;
class Service
{
public function __invoke(ServiceLocatorInterface $sm)
{
// go bananas here :)
}
public functiom greet()
{
return 'Hello World';
}
}
anywhere inside zend2:
<?php
$service=$serviceManager->get('MyModule\Service\Service');
echo $service->greet();

How can I use Zend\Di in Zend\ServiceManager in ZF2?

I'm trying to make the Zend\ServiceManager use Zend\Di to create my instances, since I have pre-scanned and cached DI definitions already. I realize this might come with a speed penalty but on the other hand, I need to write a lot less meta-code.
The ServiceManager documentation says that
the ServiceManager also provides optional ties to Zend\Di, allowing Di
to act as an initializer or an abstract factory for the manager.
But I don't find any examples of how make the ServiceManager use Zend\Di. I'm not even sure where I should set this up, maybe in Module::getServiceConfig()? Can anyone provide some example code?
The following works for me. In order to make Zend\Di compatible with Zend\ServiceManager, I extended a class MyLib\Di\Di from Zend\Di\Di which implements the AbstractFactoryInterface.
namespace MyLib\Di;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Di extends \Zend\Di\Di implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return true;
}
public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return $this->get($requestedName);
}
}
Now, I can use MyLib\Di\Di as a fallback abstract factory for Zend\ServiceManager. Here's an example of how I create my IndexController. The IndexController's dependencies (constructor parameters) are injected automatically.
class Module
{
...
public function getServiceConfig()
{
$this->di = new \MyLib\Di\Di;
$this->configureDi($this->di); // Set up definitions and shared instances
return array(
'abstract_factories' => array($this->di),
);
}
public function getControllerConfig()
{
return array(
'factories' => array(
'Survey\Controller\IndexController' => function() {
return $this->di->get('Survey\Controller\IndexController');
},
),
);
}
}
One option - add to config/module.config.php
'service_manager' => array(
'invokables' => array(
'Application\Service\User' => 'Application\Service\User',
),
),
then class needs to implement Zend\ServiceManager\ServiceManagerAwareInterface
When initiated, serviceManager instance is going to be injected, then you can use something like this in class:
$authService = $this->getServiceManager()->get('Zend\Authentication\AuthenticationService');
second option would be to put it into Module.php
public function getServiceConfig()

ZF2: How to get Zend\Navigation inside custom route?

I have custom router and I have to get access to Zend\Navigation inside this custom router. I was googling, asking and searching and no results :/
All I need is to find nodes with 'link' param using Zend\Navigation in my Alias::match function.
Here is my module.config.php:
'navigation' => array(
'default' => array(
'account' => array(
'label' => 'Account',
'route' => 'node',
'pages' => array(
'home' => array(
'label' => 'Dashboard',
'route' => 'node',
'params' => array(
'id' => '1',
'link' => '/about/gallery'
),
),
),
),
),
),
[...]
And here is my Alias class:
// file within ModuleName/src/ModuleName/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Alias extends Http\Segment implements ServiceLocatorAwareInterface
{
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function match(Request $request, $pathOffset = null)
{
[...]
return parent::match($request, $pathOffset);
}
}
EDITED:
Now i know that I should inject service manager into my custom router. Let me know if You know how to do this :)
EDITED:
Ok, its not custom router but route. My bad. I was talking on #zftalk irc chanell and AliasSegment class should implements ServiceLocatorAwareInterface. Ok I've tried it but now there is another problem.
In setServiceLocator function i can't get service locator. It returns null object, however $serviceLocator is class Zend\Mvc\Router\RoutePluginManager.
public function setServiceLocator(ServiceLocatorInterface $serviceLocator){
$sl = $serviceLocator->getServiceLocator();
var_dump($sl); // NULL
}
Any ideas how to get Zend navigation from it ?
EDITED
Corresponding to what #mmmshuddup said, I've changed my custom router class. (New version is above). Also in my Module.php, within onBootstrap function, I added this line:
$sm->setFactory('Navigation', 'Zend\Navigation\Service\DefaultNavigationFactory', true);
Navigation works and its instantiated before route so it should be visible within my Alias class but it's not.
I've put into my match function in Alias class this line:
$servicesArray = $this->getServiceLocator()->getRegisteredServices();
and $servicesArray is almost empty. There is no service, no factories. The same line inserted into onBootstrap, just after setting new factory (as above) returns array with navigation and other services.
The question is: how can i share this array (or ServiceManager) with my custom router: Alias ?
I have to say that all I want to do was possible in ZF1 and it was quite easy.
EDIT
I found a solution. The answer is below
That is because the object itself really doesn't have any properties declared. But if you do this:
echo get_class($sl);
You will see that it is indeed an instance of Zend\ServiceManager\ServiceManager
You should be able to get your navigation instance by doing something like:
$nav = $sl->get('Navigation');
EDIT:
I just notice you have some stuff in the wrong location of your code. You're calling getServiceLocator() on $serviceLocator which is already the instance of that. Also you're calling it within setServiceLocator(). You should change it to:
// EDIT - file within ModuleName/src/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment implements ServiceLocatorAwareInterface
{
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function match(Request $request, $pathOffset = null)
{
$nav = $this->getServiceLocator()->get('Navigation');
// ...
return parent::match($request, $pathOffset);
}
}
I found the solution but this is NOT elegant solution i think. However everything works perfectly. If somebody knows disadvantages of this solution, please comment this answer or add another, better. I had to modify #mmmshuddup's idea (you can read the conversation).
First of all, the implementation of ServiceLocatorAwareInterface in custom route class is no more necessary.
In Module.php within onBootstrap function:
$app = $e->getApplication();
$sm = $app->getServiceManager();
$sm->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$sm->setFactory('Navigation',
'Zend\Navigation\Service\DefaultNavigationFactory', true);
$nav = $sm->get('Navigation');
$alias = $sm->get('Application\Router\Alias');
$alias->setNavigation($nav);
First we instantiate Navigation factory in ServiceManager and then our custom route. After that we can pass Navigation class into custom route using setNavigation function.
To complete instantiate of our custom route we need in getServiceConfig in the same file:
return array(
'factories' => array(
'Application\Router\Alias' => function($sm) {
$alias = new \Application\Router\Alias('/node[/:id]');
return $alias;
},
'db_adapter' => function($sm) {
$config = $sm->get('Configuration');
$dbAdapter = new \Zend\Db\Adapter\Adapter($config['db']);
return $dbAdapter;
},
)
);
And here is a tricky part. This instance is temporary. While routing, this class will be instantiated one more time and this is why, I think, it's not very elegant. We have to insert parameter into constructor however at this moment value of this parameter is not important.
The custom route class:
// file within ModuleName/src/ModuleName/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment
{
private static $_navigation = null;
public function match(Request $request, $pathOffset = null)
{
//some logic here
//get Navigation
$nav = self::$_navigation;
return parent::match($request, $pathOffset);
}
public function setNavigation($navigation){
self::$_navigation = $navigation;
}
}
Because first instance is temporary, we have to collect our Navigation class in static variable. It's awful but works nice. Maybe there is a way to instantiate it only once and in route configuration get instance of it, but at this moment this is best answer for my question. Simply enough and working correctly.

Categories