Zend Framework 2: Database connection in view helper - php

I found a few other posts relevant to this issue, however i wasn't able to achieve what i wanted so i decided to delete everything and start over with some help...
This is my work so far, which does the job but the data are provided hard coded in an array and i need to create a database connection to fetch those data.
In my module class i have:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'liveStreaming' => function() {
return new LiveStreaming();
},
),
);
}
This is the code i have in my view helper:
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
class LiveStreaming extends AbstractHelper
{
protected $liveStreamingTable;
public function __invoke()
{
$events = array(
'1' => array('name' => 'Event name',
'sport' => 'Soccer',
'time' => '11:30'),
'2' => array('name' => 'Event name',
'sport' => 'Soccer',
'time' => '17:00'),
);
return $events;
//this is what should be used (or something like that) to get the data from the db...
//return array('events' => $this->getLiveStreamingTable()->fetchAll() );
}
public function getLiveStreamingTable()
{
if (!$this->liveStreamingTable) {
$sm = $this->getServiceLocator();
$this->liveStreamingTable = $sm->get('LiveStreaming\Model\LiveStreamingTable');
}
return $this->liveStreamingTable;
}
}
So, i want to get the $events array from the database. I've created Application\Model\LiveStreaming and Application\Model\LiveStreamingTable (following the instructions of the ZF2 official tutorial) and i need some help proceeding to the next step, which should probably have to do with the service locator.

You seem to be almost there. The only thing missing is the ability to call $this->getServiceLocator(); from within the view helper (as the AbstractHelper doesn't provide this method).
There are two options
Inject the LiveStreamingTable into the view helper directly
inject the ServiceManager itself and create the LiveStreamingTable within the helper
Option 1 Make LiveStreamingTable a dependency of the view helper (type hint in constructor)
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use LiveStreaming\Model\LiveStreamingTable;
class LiveStreaming extends AbstractHelper
{
protected $liveStreamingTable;
public function __construct(LiveStreamingTable $liveStreamingTable)
{
$this->liveStreamingTable = $liveStreamingTable;
}
public function getLiveStreamingTable()
{
return $this->liveStreamingTable;
}
}
And the factory becomes:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'liveStreaming' => function($sl) {
// Get the shared service manager instance
$sm = $sl->getServiceLocator();
$liveStreamingTable = $sm->get('LiveStreaming\Model\LiveStreamingTable');
// Now inject it into the view helper constructor
return new LiveStreaming($liveStreamingTable);
},
),
);
}
Option 2 - Implement the ServiceLocatorAwareInterface (making it again a dependency of the view helper)
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class LiveStreaming extends AbstractHelper implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
protected $liveStreamingTable;
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator);
public function getServiceLocator();
public function getLiveStreamingTable()
{
if (null == $this->liveStreamingTable) {
$this->liveStreamingTable = $this->getServiceLocator()->get('LiveStreaming\Model\LiveStreamingTable');
}
return $this->liveStreamingTable;
}
}
Your factory will then look like:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'liveStreaming' => function($sl) {
// Get the shared service manager instance
$sm = $sl->getServiceLocator();
// Now inject it into the view helper constructor
return new LiveStreaming($sm);
},
),
);
}
Personally, I feel that Option 1 makes more sense from a Dependency Injection (DI) point of view - It's clear that the LiveStreamingTable is what is needed to create the view helper.
Edit
Make sure you have the LiveStreaming\Model\LiveStreamingTable service also registered with the service manager (as we request it in the above code when we did $sm->get('LiveStreaming\Model\LiveStreamingTable');)
// Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'LiveStreaming\Model\LiveStreamingTable' => function($sm) {
// If you have any dependencies for the this instance
// Such as the database adapter etc either create them here
// or request it from the service manager
// for example:
$foo = $sm->get('Some/Other/Registered/Service');
$bar = new /Directly/Created/Instance/Bar();
return new \LiveStreaming\Model\LiveStreamingTable($foo, $bar);
},
),
);
}

Related

ZF2 Call to a member function get() on null using $this->getServiceLocator()->get()

My first controller is
class MatchesController extends AbstractActionController {
public function checkLogsAction() {
// $logs=new LogsController();
$logs=$this->getServiceLocator()->get('Admin\LogsController');
$logs->writeLogs("log data");
die();
}
Logs Controller
class LogsController extends AbstractActionController {
public function writeLogs($logData) {
$this->getServiceLocator()->get('Zend\Log\opta')->info($logData);
return true;
}
global.php
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
'Zend\Log\opta' => function ($sm) {
$fileName=date("Y-m-d");
$log = new Zend\Log\Logger();
$writer = new Zend\Log\Writer\Stream("./data/opta/$fileName");
$log->addWriter($writer);
return $log;
}
),
),
module.php
public function getServiceConfig() {
return array(
"factories"=>array(
'Admin\LogsController' => function ($sm) {
$logsController = new LogsController();
return $logsController;
},
I am getting this error:
Fatal error: Call to a member function get() on null
Please help me to solve the solution
Your Admin\LogsController extends AbstractActionController. But you do not use it as AbstractActionController!
An AbstractActionController is usuallay invoked by processing the (http) request, whereby the ZF2 application is going to route the request to a controller and executes an action method. During this processing, an instance of ServiceLocator/ServiceManager is passed to the controller. That is what you are missing. Hence, you try to call a method on a null object.
You can not simply instantiate an ActionController from another ActionController. (of course, it is possible, with a lot of afford). If you use it this way, you to make sure the new controller instance holds an instance of the ServiceLocator, request, response etc...
You should consider:
a) is Admin\LogsController really a AbstractActionController in your application? (I assume it is not, respectively your code example)
b) inject the ServiceLocator in to your custom object (LogsController), or a way cleaner: inject the logger instance.
Example:
public function getServiceConfig() {
return array(
'factories' => array(
'Admin\LogsController' => function ($sm) {
$logsController = new LogsController();
$logsController->setServiceLocator($sm); // you have to implement!
return $logsController;
},
);
}

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.

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: get access to the service manager (locator) from external class

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

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