I'm quite new to zf2 and I'm experimenting with it. I have a view helper and I need it to access a table object. In my controller I can run:
$this->getServiceLocator();
But ideally I would run this inside my view helper. Unfortunately, I can't seem to access it from within my view helper. I tried passing it through the constructor, configuring a factory method in module.config.php, but when I try that, Zend will no longer pass a tablegateway object into one of my model objects created from a service factory method in the module's Module.php file. This seems to be because it no longer calls the factory method, and opts to run instantiate without any parameters.
I'm not certain I understand why the view factory methods would affect a different set of factory methods with different names.
Can anyone tell me what is wrong with what I'm doing? I can provide more details, but at this point I'm unclear on what details are actually important without supplying the entire codebase.
Thanks.
Crisp does provide a valid answer to your question, but I would suggest to take it one step further. The injection of the service locator makes your view helper tightly coupled to the framework and service locator pattern and vulnerable because every piece of code inside your application can modify every service in the service locator.
There are reasons to inject your dependency directly, so you only depend on your dependencies and you're not implementing this anti-pattern anymore. Let's assume your view helper depends on MyModule\Model\MyTable, then the constructor of your view helper would just look like this:
namespace MyModule;
use MyModule\Model\MyTable;
use Zend\View\Helper\AbstractHelper;
class MyViewHelper extends AbstractHelper
{
protected $table;
public function __construct(MyTable $table)
{
$this->table = $table;
}
}
As you pointed out, you just inject your MyTable now:
namespace MyModule;
class Module
{
public function getViewHelperConfig()
{
return array(
'factories' => array(
'MyViewHelper' => function($sm) {
$sm = $sm->getServiceLocator(); // $sm was the view helper's locator
$table = $sm->get('MyModule_MyTable');
$helper = new MyModule\View\Helper\MyHelper($table);
return $helper;
}
)
);
}
}
Note that inside a view helper factory your service manager is the view helper's service manager and not the "main" one where the table is registered (see also a blog post of I wrote earlier). The $sm->getServiceLocator() solves this for you.
I'm not certain I understand why the view factory methods would affect a different set of factory methods with different names.
It's not, so there is probably a bug in your code. If above does not work, please provide some more details on your service manager configuration so I can update my answer.
One of the great advantages of above approach is you make unit testing really easy for your view helper. You can mock the table gateway and focus on the complete behaviour of your view helper.
use MyModule\View\Helper\MyHelper;
public function testHelperusesTable
{
$mock = $this->getMock('MyModule\Model\MyTable');
$helper = new MyHelper($mock);
// Test your $helper now
}
You can inject the service locator into your view helper from the view helper config in Module.php
// Application/Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'myViewHelper' => function ($serviceManager) {
// Get the service locator
$serviceLocator = $serviceManager->getServiceLocator();
// pass it to your helper
return new \Application\View\Helper\MyViewHelper($serviceLocator);
}
)
);
}
In your view helper
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper,
Zend\ServiceManager\ServiceLocatorInterface as ServiceLocator;
class MyViewHelper extends AbstractHelper
{
protected $serviceLocator;
public function __construct(ServiceLocator $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
}
While working in Zend Framework,we often need custom helper,that make our work easy, In zf1 accessing database model from helper was easy,but i got stuck that how to access database model for any table in Custom View Helper, but as i was needing it i get around through the problem in unprofessional way by creatina new db adapter object in the view, which was never good way, but recently i came to know through very interesting way to access the database adapter in the view helper and there i have to execute any query on any table, it may be not so Zend F2 way, but very simple and short way to solve the issue.
Here is my Model Example...
<?php
namespace Application\Model;
use Zend\Db\TableGateway\TableGateway;
class SlideImageSubTable {
protected $tableGateway;
public $adapter;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
$this->adapter = $this->tableGateway->getAdapter();
}
public function fetchAll() {
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getSlideImageSub($id) {
$id = (int) $id;
$rowset = $this->tableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
public function getImageMenu($id) {
$id = (int) $id;
$rowset = $this->tableGateway->select(array('slide_image_id' => $id));
$rows = array_values(iterator_to_array($rowset));
if (!$rows) {
throw new \Exception("Could not find row $id");
}
return $rows;
}
public function saveSlideImageSub(SlideImageSub $slideImageSub) {
$data = array(
'slide_image_id' => $slideImageSub->slide_image_id,
'title' => $slideImageSub->title,
'description' => $slideImageSub->description
);
$id = (int) $slideImageSub->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getSlideImageSub($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
public function deleteSlideImageSub($id) {
$this->tableGateway->delete(array('id' => $id));
}
}
Just look at the 'public $adapter' public variable. And in the constructor i am going to initialize it by calling $this->tableGateway->getAdapter(); method, getAdapter() is available thorugh gateway object.
Then in my controller action view, i have to assign it to any variable and pass that variable to view page. like this..
public function equitiesAction() {
$image_id = $this->params('id');
$result = $this->getTable('SlideImageSub')->getImageMenu($image_id);
$adapter = $this->table->adapter;
$view = new ViewModel(array(
'result' => $result,
'adapter' => $adapter,
));
return $view;
}
And in the view i pass the 'adapter' object to custom view like this..
<?php echo $this->GetMenuProducts( $this->adapter); ?>
Now in custom view i can use this database adapter object and create select query on any table.
Hope this will help someone, i look around for using database access in custom view helper but the configurations methods provided was not working for me.
Thanks
$this->getView()->getHelperPluginManager()->getServiceLocator();
Related
It looks like it has been touched several times already, but i still can't get it work. I set up an JSON-RPC server in a separate module, it works fine. Its functionality is in a new class Rpcapi. Now I want reuse DB related functions that already implemented in another module from that class. According to ZF2 docs my Rpcapi class has to be ServiceLocator-aware and it looks like I made it that way. Unfortunatelly still can't get it working. Please help keeping in mind that I'm new with ZF2 :)
Rpccontroller.php
namespace Rpc\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Json\Server\Server;
use Zend\Json\Server\Smd;
use Rpc\Model\Rpcapi;
class RpcController extends AbstractActionController
{
public function indexAction()
{
header('Content-Type: application/json');
$jsonrpc = new Server();
$jsonrpc->setClass(new Rpcapi);
$jsonrpc->getRequest()->setVersion(Server::VERSION_2);
if ($this->getRequest()->getMethod() == "GET") {
$smd = $jsonrpc->getServiceMap()->setEnvelope(Smd::ENV_JSONRPC_2);
echo $smd;
} else {
$jsonrpc->handle();
}
}
}
module.config.php for Rpc module
'service_manager' => array(
'invokables' => array(
'rpcapi' => 'Search\Model\SiteTable',
),
),
Rpcapi.php
namespace Rpc\Model;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Rpcapi implements ServiceLocatorAwareInterface
{
protected $services;
protected $siteTable;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function getSiteTable()
{
if (!$this->siteTable) {
$sm = $this->getServiceLocator();
$this->siteTable = $sm->get('rpcapi');
}
return $this->siteTable;
}
/**
* Returns list of all sites
*
*
* #return array
*/
public function getAllSites()
{
$results = $this->getSiteTable()->fetchAll();
$r = array ('1' => '1', '2' => 2); //Just to return something for now
return $r;
}
}
All I could get out is: Fatal error: Call to a member function get() on a non-object in /var/www/html/AmeriFluxZF2/module/Rpc/src/Rpc/Model/Rpcapi.php on line 28. Line 28 is:
$this->siteTable = $sm->get('rpcapi');
Any help is much appreciated!
Making the class service locator aware tells the ZF2 that the service locator should be injected into your class upon instantiation. However, you still need to use the service locator to instantiate this class, rather than creating an instance of it yourself, or this will never happen.
Your probably want to add a new entry to invokables for your Rpcapi class, and then grab this from the service locator instead of doing new Rpcapi in your controller.
PS: The naming of your classes is very confusing - you have an Rpcapi class, and an invokable called rpcapi, yet this invokable creates an instance of a completely different class?
If you want serviceLocator to be injected by the service manager in your Rpcapi, you must get it via the service manager itself :
'service_manager' => array(
'invokables' => array(
'rpcapi' => 'Search\Model\SiteTable',
'Rpc\Model\Rpcapi' => 'Rpc\Model\Rpcapi',
),
),
the action :
public function indexAction()
{
header('Content-Type: application/json');
$jsonrpc = new Server();
$jsonrpc->setClass($this->getServiceLocator()->get('Rpc\Model\Rpcapi'));
$jsonrpc->getRequest()->setVersion(Server::VERSION_2);
if ($this->getRequest()->getMethod() == "GET") {
$smd = $jsonrpc->getServiceMap()->setEnvelope(Smd::ENV_JSONRPC_2);
echo $smd;
} else {
$jsonrpc->handle();
}
}
And this is where you can see that your 'rcpai' name for SiteTable is not a good choice... ;)
guys,
At this point i am close to start pulling hair out of my head. I don't find a way to achieve this.
I have a custom class that belongs to a custom folder i created under my WebServices Module src folder. I need to be able to instantiate this class from inside another module/controller but when i do that and dump the services member it contains null.
How can i have the service manager accesible from inside my ApiAuthentication class.
Any help will be appreciated. Thanks
<?php
namespace WebServices\Services;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ApiAuthenticationService extends \Zend\Soap\Client implements ServiceLocatorAwareInterface{
public $services;
function __construct($options = null){
parent::__construct('http://tinysoa.local/soap/security/api_authentication?wsdl',$options);
}
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
When i call this from inside another module/controller it dumps a null value:
class IndexController extends AbstractActionController
{
public function indexAction()
{
$a = new \WebServices\Services\ApiAuthenticationService();
var_dump($a->services);
Responding with my own answer to add-on to Adrian's, and the question you asked in response.
If your service has dependencies of it's own, you just use a factory instead of going the invokable route.
Say your service needs a cache adapter and database adapter. Also imagine that it can optionally be configured with some other service (FooService, below):
<?php
public function getServiceConfig()
{
return array(
'factories' => array(
'my_service' => function($sm){
$cache = $sm->get('Cache');
$dbAdapter = $sm->get('DefaultDbAdapter');
$fooService = $sm->get('FooService');
// instantiate your service with required dependencies
$mySvc = new \My\Shiny\Service($cache, $dbAdapter);
// inject an optional dependency
$mySvc->setFooService($fooService);
// return your shiny new service
return $mySvc;
}
)
);
}
Side Note: It's generally bad design to inject the ServiceManager all over the place. You're better off managing your dependencies more explicitly, like above.
This stuff is covered quite well in the Quick Start, if you haven't already read that.
Register your Service in Service Config and access it through getServiceLocator() method in controller.
Module.php
public function getServiceConfig()
{
return array(
'invokables' => array(
'my_service' => 'WebServices\Services\ApiAuthenticationService'
)
);
}
Controller
public function indexAction()
{
$service = $this->getServiceLocator()->get('my_service');
}
I want create a custom element and use the short name for add the element into Form, using the new ServiceManager tecnique for ZF2 V.2.1+
I am try to copy the same sample of the zend documentation step to step but it not works.
When I use the service writting the short name, it raises a exception because service not found:
Zend\ServiceManager\Exception\ServiceNotFoundException
File:
Zend\ServiceManager\ServiceManager.php:456
Message:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Test
I think I have all classes identically, see follows
This is my custom element:
namespace SecureDraw\Form\Element;
use Zend\Form\Element\Text;
class ProvaElement extends Text {
protected $hola;
public function hola(){
return 'hola';
}
}
This is my Module.php I have my invokable service be able to use short name:
class Module implements FormElementProviderInterface {
//Rest of class
public function getFormElementConfig() {
return array(
'invokables' => array(
'Test' => 'SecureDraw\Form\Element\ProvaElement'
)
);
}
}
In my form I use for add the element, the commented line works ok, but with short name not works:
$this->add(array(
'name' => 'prova',
//'type' => 'SecureDraw\Form\Element\ProvaElement',
'type' => 'Test', //Fail
));
In my action:
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('SecureDraw\Form\UserForm');
$prova = $form->get('prova');
echo $prova->hola();
The problem is that the elements created via FormElementManager have to be created into init method instead __Construct method how it can see in this page.
The zend documentation is badly explained
Workaround:
In your own module, create the following two files:
FormElementManagerConfig with the invokables short names of your custom form elements;
Subclass Form\Factory and override getFormElementManager and pass the config to the FormElementManager constructor;
You then use your own Factory to create your Form, like this (you can pass a very rudimentary, e.g. empty array, or a more or less full-fledged $spec to $factory->createForm()):
$factory = new Factory();
$spec = array();
$form = $factory->createForm($spec);
FormElementManagerConfig.php:
class FormElementManagerConfig implements ConfigInterface
{
protected $invokables = array(
'anything' => 'MyModule\Form\Element\Anything',
);
public function configureServiceManager(ServiceManager $serviceManager)
{
foreach ($this->invokables as $name => $service) {
$serviceManager->setInvokableClass($name, $service);
}
}
}
MyFactory.php:
class Factory extends \Zend\Form\Factory
{
public function getFormElementManager()
{
if ($this->formElementManager === null) {
$this->setFormElementManager(new FormElementManager(new FormElementManagerConfig()));
}
return $this->formElementManager;
}
}
In order to get my object manager inside my fieldset's init() function I followed the docs
At first I found out that I had to change
public function setServiceLocator(ServiceLocator $sl)
to
public function setServiceLocator(ServiceLocatorInterface $sl)
otherwise I got an error:
setServiceLocator() must be compatible with that of Zend\ServiceManager\ServiceLocatorAwareInterface::setServiceLocator()
When calling $this->getServiceLocator() I get an instance of the FormManager.
Additionally calling $this->getServiceLocator()->getServiceLocator() returns NULL.
Since I am still new to DI I wonder if I am missing a place to inject?
Testing I switched from
$form = new MyForm();
to
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('Application\Form\MyForm');
Since then I get this error:
exception 'Zend\Di\Exception\RuntimeException' with message 'Invalid instantiator of type "NULL" for "Zend\ServiceManager\ServiceLocatorInterface".'
An abstract factory could not create an instance of applicationformmyform(alias: Application\Form\MyForm).
Anyway reading some other threads using the ServiceLocator Awareness isn't recommended. Is using the FormElementProviderInterface the alternative?
I used the ServiceLocatorAwareInterface before in my classes like this:
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyClass implements ServiceLocatorAwareInterface
{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
and simply calling it per service locator in my c
$sm = $this->getServiceLocator();
$myClass = $sm->get('Application\Service\MyClass');
without having to set an DI. Is this necessary for Fieldsets / Form and where / how exactely?
I tried to inject my Form and Fieldset this way:
'service_manager' => array(
'factories' => array(
'Application\Form\CreateCostcenter' => function (\Zend\ServiceManager\ServiceLocatorInterface $sl) {
$form = new \Application\Form\CreateCostcenter();
$form->setServiceLocator($sl);
return $form;
},
'Application\Form\CostcenterFieldset' => function (\Zend\ServiceManager\ServiceLocatorInterface $sl) {
$fieldset = new \Application\Form\CostcenterFieldset();
$fieldset->setServiceLocator($sl);
return $fieldset;
},
),
),
The injection works for my form when calling it in my controller:
$form = $this->getServiceLocator()->get('Application\Form\CreateCostcenter');
But of course it won't pass the serviceManager to the Fieldset.
Anyway I don't understand why there has to be a config for the serviceManagerAwareness since it works with other class by just implementing it.
There also is no hint in the docs for advanced usage since ZF 2.1:
use an initializer (like Zend\ServiceManager\ServiceLocatorAwareInterface) to inject a specific object to all your forms/fieldsets/elements
Is there any possible way to translate strings in controllers instead of view?
Right now, in my controllers, if I pass strings like :
public function indexAction() {
return array('message' => 'example message');
}
It will be translated in index.phtml
<?php print $this->translate($message);?>
It works well, but poeditor unable to find strings from controller files
Guess it would be cool if I can use something like :
public function indexAction() {
return array('message' => $view->translate('example message'));
}
in controllers
Thanks in advance for help
To use view helper in controller, you can use 'getServiceLocator'
$helper = $this->getServiceLocator()->get('ViewHelperManager')->get('helperName');
Either you can use php getText function ___('my custom message') and add "_" as sources keyword in poedit (in catalog properties) so poedit will filter strings from controller. eg:
array('message' => _('my custom message'));
And as per your code, you can use helper directly like this
$translate = $this->getServiceLocator()->get('ViewHelperManager')->get('translate');
array('message' => $translate('my custom message'));
You should not use the view's plugin manager to get to the translator helper. Grab the translator like I have explained here already.
A copy/paste of that post:
Translation is done via a Translator. The translator is an object and injected for example in a view helper, so if you call that view helper, it uses the translator to translate your strings. For this answer I assume you have configured the translator just the same as the skeleton application.
The best way is to use the factory to inject this as a dependency into your controller. The controller config:
'controllers' => array(
'factories' => array(
'my-controller' => function($sm) {
$translator = $sm->getServiceLocator()->get('translator');
$controller = new MyModule\Controller\FooController($translator);
}
)
)
And the controller itself:
namespace MyModule;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\I18n\Translator\Translator;
class FooController extends AbstractActionController
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
}
An alternative is to pull the translator from the service manager in your action, but this is less flexible, less testable and harder to maintain:
public function fooAction()
{
$translator = $this->getServiceManager()->get('translator');
}
In both cases you can use $translator->translate('foo bar baz') to translate your strings.
I use for that purpose a simple plugin. Then in controller you can do $this->translate('example message');
class Translate extends AbstractPlugin {
private $translator;
public function __construct(PluginManager $pm) {
$this->translator = $pm->getServiceLocator()->get('Translator');
}
public function __invoke($message, $textDomain = 'default', $locale = null) {
return $this->translator->translate($message, $textDomain, $locale);
}
}