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. ;)
Related
I am working on a project where I am trying to implement a simple dependency injection. I implemented the class below, but am not sure if it's the best practice or should I go with some of the DI Containers around?
The Container Class
namespace App\Includes;
class Container {
private $class=[];
protected $config = [
'user' => 'App\Models\User',
'template' => 'Library\Template',
'database' => 'Library\Database'
];
public function getClass($name) {
if (!isset($this->class[$name])) {
$this->class[$name] = new $this->config[$name]();
}
return $this->class[$name];
}
}
The Controller
namespace App;
use App\Includes\Container as Container;
class Controller{
protected $template;
protected $database;
public function __construct(Container $container){
$this->database = $container->getClass('database');
$this->template = $container->getClass('template');
}
}
Here I initiate the app (The $controller is supplied by the router)
$container = new App\Includes\Container();
$obj = new $controller($container);
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();
}
}
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!
I'm using the mailing library from zend framework 2, and also I'm using the translator functionality. But the translations only work inside the template .phtml, and not work in the service function for send the email, for example in translating the subject of the email. This is my code inside my service function to send an email:
$mailFactory = $this->getDependency('mail');
$viewModel = new ViewModel(array(
'variable' => $x
));
$viewModel->setTemplate('template.phtml');
$message = $mailFactory->prepareMessage($viewModel);
$message->getHeaders()
->addHeaderLine('MIME-Version', '1.0')
->addHeaderLine('Content-type', 'text/html; charset=UTF-8');
$message->setSubject($this->translate('Recover your password'));
$message->setEncoding("UTF-8");
$message->addTo($email, $fullname);
$transport = $mailFactory->getTransport();
return $transport->send($message);
The function $this->translate('...') calls to a class that call this code: return $this->translator->translate($text), this works well in all my application, but only works wrong when I send any email (with an exception of the template.phtml, inside this file the translator works well). All my mails received, have the subject in english (not translated) and the content in spanish (translated)
You have to pass the translator to the service to use it.
So in your module.config.php under the service_manager key you can do it with a factory or an initializer.
Factory:
'service_manager' => array(
'factories' => array(
'YourNamespace\YourMailService' => function ($serviceManager) {
$mailService = new YourNamespace\YourMailService();
$mailService->setTranslator($serviceManager->get('translator'));
return $mailService;
},
)
),
You need the method setTranslator in your service class!
Inititalizer:
'service_manager' => array(
'initializers' => array(
function ($instance, $sm)
{
if ($instance instanceof \YourNamespace\TranslatorAwareInterface) {
$instance->setTranslator($sm->get('translator'));
}
}
),
),
With an initializer you need the interface TranslatorAwareInterface:
namespace YourNamespace;
use Zend\Mvc\I18n\Translator;
interface TranslatorAwareInterface
{
public function setTranslator(Translator $translator);
}
And your service need to implement that interface:
class YourMailService implements TranslatorAwareInterface
{
protected $translator;
public function setTranslator(Translator $translator)
{
$this->translator = $translator;
}
}
In both cases you can use the translator like:
$message->setSubject($this->translator->translate('Recover your password'));
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);
...