In ZF1 I used to declare variables in the application.ini
brandname = "Example"
weburl = "http://www.example.com/"
assetsurl = "http://assets.example.com/"
And in the Bootstrap I did this so i could access them in the view
define('BRANDNAME', $this->getApplication()->getOption("brandname"));
define('WEBURL', $this->getApplication()->getOption("weburl"));
define('ASSETSURL', $this->getApplication()->getOption("assetsurl"));
Whats the ZF2 way to do this, I know that i can create an array in the local.php config file like:
return array(
'example' => array(
'brandname' => 'Example',
'weburl' => 'http://www.example.com/',
'asseturl' => 'http://assets.example.com/',
),
);
When I want to access that variable in the controller I can do
$config = $this->getServiceLocator()->get('Config');
$config['example']['brandname']);
So far so good... but how do i access this variable in the view?
I don't want to create a view variable for it in every controller. And when i try the above in a view phtml file i get an error.
Zend\View\HelperPluginManager::get was unable to fetch or create an instance for getServiceLocator
Any ideas?
You could create a sinmple view helper to act as a proxy for your config, (totally un tested).
Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'configItem' => function ($helperPluginManager) {
$serviceLocator = $helperPluginManager->getServiceLocator();
$viewHelper = new View\Helper\ConfigItem();
$viewHelper->setServiceLocator($serviceLocator);
return $viewHelper;
}
),
);
}
ConfigItem.php
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceManager;
/**
* Returns total value (with tax)
*
*/
class ConfigItem extends AbstractHelper
{
/**
* Service Locator
* #var ServiceManager
*/
protected $serviceLocator;
/**
* __invoke
*
* #access public
* #param string
* #return String
*/
public function __invoke($value)
{
$config = $this->serviceLocator->get('config');
if(isset($config[$value])) {
return $config[$value];
}
return NULL;
// we could return a default value, or throw exception etc here
}
/**
* Setter for $serviceLocator
* #param ServiceManager $serviceLocator
*/
public function setServiceLocator(ServiceManager $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
}
You could then do something like this in your view, assuming you have them set in your config of course :)
echo $this->configItem('config_key');
echo $this->configItem('web_url');
I would personally tend to just pass the values through to the view every time though, keeping the view a dumb as possible.
I answered this before on a different post.
/* Inside your action controller method */
// Passing Var Data to Your Layout
$this->layout()->setVariable('stack', 'overflow');
// Passing Var Data to Your Template
$viewModel = new ViewModel(array( 'stack' => 'overflow' ));
/* In Either layout.phtml or {Your Template File}.phtml */
echo $this->stack; // Will print overview
That's it... No need to mess with view helpers, event manager, service manager, or anything else.
Enjoy!
Related
I know there are dozen of questions about PHPRenderer not finding the path of a template, but I think the problem is quite different here.
First, the goal is to render a view to a variable in order to send it to a PDF Renderer (I use ZF3 TCPDF module). If there is any better way to do that, please tell me.
Here is roughly the architecture of the project: https://imgur.com/UhQ7hgP
In AlertAction() of ToolsController, I return the view like this, and it works, which make me think the template path is alright.
$view = new ViewModel();
$view->setTemplate('tools/tools/alert');
return $view;
However, when I try to render the same view with the same path in exportPDFAction(), it does not work and gives the following error.
Zend\View\Renderer\PhpRenderer::render: Unable to render template "tools/tools/alert"; resolver could not resolve to a file
The code in exportPDFAction() is:
$view = new ViewModel();
$renderer = new PhpRenderer();
$view->setTemplate('tools/tools/alert');
$html = $renderer->render($view);
I assume the last line screws it as it is the difference, but I can't get why, does anyone have any clue ?
Quite all the topics about Template path on SO were talking about the template map in module.config.php, but I think this is not the problem here since it works perfectly in AlertAction().
EDIT
The PhpRenderer is injected in the controller directly in module.config.php:
'controllers' => [
'factories' => [
ToolsController::class => function($container) {
return new ToolsController(
$container->get(Adapter::class),
$container->get(\TCPDF::class),
$container->get(PhpRenderer::class)
);
},
],
],
EDIT 2
This is the controller constructor:
public function __construct($db, $tcpdf, $renderer)
{
$this->db = $db;
$this->tcpdf = $tcpdf;
$this->renderer = $renderer;
...
}
The error you're getting might be due to the fact your Renderer is not injected via the Factory.
Try:
class MyCustomControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var \Zend\View\Renderer\PhpRenderer $renderer */
$renderer = $container->get('ViewRenderer')
return new MyCustomController($renderer);
}
}
In the Controller, require it be set in the __construct() function:
public function __construct(PhpRenderer $renderer)
{
// ... set it somewhere, e.g.:
$this->setRenderer($renderer);
}
Then use it in your function:
$view = new ViewModel();
$renderer = $this->getRenderer();
$view->setTemplate('tools/tools/alert');
$html = $renderer->render($view);
Why, you ask?
Because the Renderer is configured via the Zend Configuration. You can find that in the \Zend\Mvc\Service\ServiceManageFactory class. The alias configuration provided is the following:
'ViewPhpRenderer' => 'Zend\View\Renderer\PhpRenderer',
'ViewRenderer' => 'Zend\View\Renderer\PhpRenderer',
'Zend\View\Renderer\RendererInterface' => 'Zend\View\Renderer\PhpRenderer',
The alias'es are mapped to Factory:
'Zend\View\Renderer\PhpRenderer' => ViewPhpRendererFactory::class,
That Factory is:
class ViewPhpRendererFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $name
* #param null|array $options
* #return PhpRenderer
*/
public function __invoke(ContainerInterface $container, $name, array $options = null)
{
$renderer = new PhpRenderer();
$renderer->setHelperPluginManager($container->get('ViewHelperManager'));
$renderer->setResolver($container->get('ViewResolver'));
return $renderer;
}
}
As such, it has some presets included when you use it with $this->getRenderer, namely it has the HelperPluginManager and the Resolver set. So it knows where to get additional resources (if needed) and it knows how to resolve (ie render) a View.
Let's say I have a configuration like this (snippet from official guide):
$config = [
// ...
'container' => [
'definitions' => [
'yii\widgets\LinkPager' => ['maxButtonCount' => 5],
],
],
// ...
];
I create a class named FancyLinkPager:
class FancyLinkPager extends \yii\widgets\LinkPager
{
// ...
}
When I create an object of class FancyLinkPager like so (please ignore the $pagination object, it's here for correctness sake):
$pagination = \Yii::createObject(Pagination::class);
$linkPager = \Yii::createObject(['class' => LinkPager::class, 'pagination' => $pagination]);
$fancyLinkPager = \Yii::createObject(['class' => FancyLinkPager::class, 'pagination' => $pagination]);
$linkPager->maxButtonCount; // 5 as configured
$fancyLinkPager->maxButtonCount; // 10 as LinkPager's default
My problem is that I wished $fancyLinkPager->maxButtonCount to be 5 as configured. I know I can add another line in the configuration or adjust it to specify my custom class, but it's not solution for me because:
I want to keep the code DRY
This is an oversimplified example of my needs - in real world you don't expect to have multiple LinkPager's child classes, but it is highly possible for other objects
My question is: is there any framework-supported way of achieving this? The solutions I came up with are:
Hack a custom __construct in FancyLinkPager (or another intermediate class or trait) so that it would look into App's configuration and call Yii::configure on the instance, but I don't find a good way to do it in generic way
Inject a dependency into FancyLinkPager with "setup" object, like LinkPagerSettings and configure that class in container section of my configuration, but it would make some trouble to work with vanilla LinkPager instances as well
Maybe the only real solution would be to create my own implementation of yii\di\Container that allows for inheriting configuration from parent classes but before I dive into this I would like to know if I haven't overlooked something.
Finally I came up with my own implementation of DI container introducing new configurable property:
public $inheritableDefinitions = [];
Full class code:
<?php
namespace app\di;
/**
* #inheritdoc
*/
class Container extends \yii\di\Container
{
/**
* #var array inheritable object configurations indexed by class name
*/
public $inheritableDefinitions = [];
/**
* #inheritdoc
*
* #param string $class
* #param array $params
* #param array $config
*
* #return object the newly created instance of the specified class
*/
protected function build($class, $params, $config) {
$config = $this->mergeInheritedConfiguration($class, $config);
return parent::build($class, $params, $config);
}
/**
* Merges configuration arrays of parent classes into configuration of newly created instance of the specified class.
* Properties defined in child class (via configuration or property declarations) will not get overridden.
*
* #param string $class
* #param array $config
*
* #return array
*/
protected function mergeInheritedConfiguration($class, $config) {
if (empty($this->inheritableDefinitions)) {
return $config;
}
$inheritedConfig = [];
/** #var \ReflectionClass $reflection */
list($reflection) = $this->getDependencies($class);
foreach ($this->inheritableDefinitions as $parentClass => $parentConfig) {
if ($class === $parentClass) {
$inheritedConfig = array_merge($inheritedConfig, $parentConfig);
} else if (is_subclass_of($class, $parentClass)) {
/** #var \ReflectionClass $parentReflection */
list($parentReflection) = $this->getDependencies($parentClass);
// The "#" is necessary because of possible (and wanted) array to string conversions
$notInheritableProperties = #array_diff_assoc($reflection->getDefaultProperties(),
$parentReflection->getDefaultProperties());
// We don't want to override properties defined specifically in child class
$parentConfig = array_diff_key($parentConfig, $notInheritableProperties);
$inheritedConfig = array_merge($inheritedConfig, $parentConfig);
}
}
return array_merge($inheritedConfig, $config);
}
}
This is how it can be used to accomplish customization of LinkPager described in the question:
'container' => [
'inheritableDefinitions' => [
'yii\widgets\LinkPager' => ['maxButtonCount' => 5],
],
],
Now if I create a class FancyLinkPager that extends yii\widgets\LinkPager the DI container will merge the default configuration:
$pagination = \Yii::createObject(Pagination::class);
$linkPager = \Yii::createObject(['class' => LinkPager::class, 'pagination' => $pagination]);
$fancyLinkPager = \Yii::createObject(['class' => FancyLinkPager::class, 'pagination' => $pagination]);
$linkPager->maxButtonCount; // 5 as configured
$fancyLinkPager->maxButtonCount; // 5 as configured - hurrah!
I have also taken into account qiangxue's comment about explicit setting of default property values in class definition, so if we declare the FancyLinkPager class as such:
class FancyLinkPager extends LinkPager
{
public $maxButtonCount = 18;
}
the property setting will be respected:
$linkPager->maxButtonCount; // 5 as configured
$fancyLinkPager->maxButtonCount; // 18 as declared
To swap default DI container in your application you have to explicitly set Yii::$container somewhere in an entry script:
Yii::$container = new \app\di\Container();
In my project (BtoB project), I have a global application with a lot of modules in it.
Each module provides common functionnalities, for all of my clients.
I have also in the root directory a clients folder, in it, I have all clients specificities, in their folder.
Thoses folders, aren't modules. So they are not loaded with Zf2. I usually load those specificities with abstractFactories.
This architecture follow is what I have currently :
- clients
- clientOne
- Invoice
- Cart
- Orders
- clientTwo
- Invoice
- Orders
- clientThree
- Reporting
- module
- Application
- CartModule
- InvoiceModule
- OrdersModule
- Reporting
My clients wants to have some custom views, sometimes, they ask us to provide those views. But my application give a common view for all of them. I have to modify this architecture to load a client view if it exist, or load the common view.
To handle this case I Imagine to have into each clients folder this :
- client
- clientOne
- Invoice
- Cart
- View
- cartView.phtml
- Orders
EDIT :
After some good answers (#AlexP & #Wilt), I tried to implements this solution :
So I have a ClientStrategy; it's factory is Like This :
<?php
namespace Application\View\Strategy;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\View\Resolver\TemplateMapResolver;
use Zend\View\Resolver;
class ClientStrategyFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$viewRenderer = $serviceLocator->get('ViewRenderer');
$session = new \Zend\Session\Container('Session');
$map = $serviceLocator->get('config')['view_manager']['template_map'];
$resolver = new Resolver\AggregateResolver();
$map = new TemplateMapResolver($map, $this->clientMap($session->offsetGet('cod_entprim')));
$resolver
->attach($map)
->attach(new Resolver\RelativeFallbackResolver($map));
$viewRenderer->setResolver($resolver);
return new ClientStrategy($viewRenderer);
}
/**
* permet de retourner le namespace du client selectionné avec la relation codpriml / nom de dossier
* #return array
*/
public function clientMap($codprim)
{
$clients = array(
21500 => 'clientOne',
32000 => 'clientTwo',
// ..
);
return (isset($clients[$codprim])) ? $clients[$codprim]: false;
}
}
My clientMap method allow me to load my client folder, and views it may have in it like this :
class ClientOne
{
/**
* get The main Code
* #return integer
*/
public function getCodEntPrim()
{
return 21500;
}
/**
* Load all customs views
* #return array
*/
public function customViews()
{
return array(
'addDotations' => __DIR__ . '/Dotations/view/dotations/dotations/add-dotations.phtml',
);
}
/**
* GetName
* #return string
*/
public function getName()
{
return get_class();
}
}
So when it comes to my TemplateMapResolver to do his job I do this :
<?php
namespace Application\View\Resolver;
class TemplateMapResolver extends \Zend\View\Resolver\TemplateMapResolver
{
/**
* Client name to use when retrieving view.
*
* #param string $clientName
*/
protected $clientName;
/**
* Merge nos vues avec celle clients avant de repeupler l'arrayMap global
* #param array $map [description]
*/
public function __construct(array $map, $client)
{
$this->setClientName($client);
if ($this->getCLientName()) {
$map = $this->mergeMap($map);
}
parent::__construct($map);
}
/**
* Merge les map normales avec les map clients, pas propre ?
* #param array $map
* #return array
*/
public function mergeMap($map)
{
$name = $this->getClientName() . '\\' . $this->getClientName() ;
$class = new $name;
$clientMap = $class->customViews();
return array_replace_recursive($map, $clientMap);
}
/**
* Retrieve a template path by name
*
* #param string $name
* #return false|string
* #throws Exception\DomainException if no entry exists
*/
public function get($name)
{
return parent::get($name);
}
/**
* Gets the Client name to use when retrieving view.
*
* #return string
*/
public function getClientName()
{
return $this->clientName;
}
/**
* Sets the Client name to use when retrieving view.
*
* #param mixed $clientName the client name
*
* #return self
*/
public function setClientName($clientName)
{
$this->clientName = $clientName;
return $this;
}
}
I tried a lot of things, this works but somes issues cames up :
My template_path_stack not works anymore, so a lot of my views are broken.
I think this is a complete mess, to do this, that way.
Hard to maintain.
I understand a bit better, how it works, but i'm still unable to implements it the good way.
If you really want to do that (I am not so sure if it is the best way) then you can extend the TemplateMapResolver with your custom logic and set it in your Renderer instance.
Make your custom class:
<?php
Application\View\Resolver
class TemplateMapResolver extends \Zend\View\Resolver\TemplateMapResolver
{
/**
* Client name to use when retrieving template.
*
* #param string $clientName
*/
protected $clientName;
/**
* Retrieve a template path by name
*
* #param string $name
* #return false|string
* #throws Exception\DomainException if no entry exists
*/
public function get($name)
{
if ($this->has($clientName . '_' . $name)) {
return $this->map[$clientName . '_' . $name];
}
if (!$this->has($name)) {
return false;
}
return $this->map[$name];
}
}
And now something like:
$resolver = new TemplateMapResolver();
$resolver->setClientName($clientName);
// Get the renderer instance
$renderer->setResolver($resolver);
You might still have to take care of setting the map in the resolver. Maybe you can just get it from the old resolver? I am not sure... That is for you to find out. This is just to get you on the correct way.
So if you set cart_view as a template it will first try to get client_name_cart_view if not found it sets cart_view.
UPDATE
If you want to take this to the next level, then what you can do is make a custom view model for example ClientViewModel that extends the normal ViewModel class.
The constructor for this ClientViewModel takes both a client and a template name:
new ClientViewModel($client, $template, $variables, $options);
$variables and $options are optional and can be passed to the parent::__construct (constructor of the normal ViewModel)
The next step would be to create a Application\View\ClientStrategy.
This strategy is connected on render event and in this strategy you add a ViewRenderer instance with your custom TemplateMapResolver set. During rendering you can get your client from your ViewModel and find the correct template in your TemplateMapResolver using this client.
More details can be found online, there are examples. Check for example here.
The advantage will be that other views with ViewModel or JsonModel will be rendered as normally, only your ClientViewModel gets a special treatment. Thus you are not breaking your applications default logic.
Requirements
Multiple possible views per client
Default view fallback if client specific view not found
Create a new service, say TemplateProviderService which has a simple interface.
interface ViewTemplateProviderInterface
{
public function hasTemplate($name);
public function getTemplates();
public function setTemplates($templates);
public function getTemplate($name);
public function setTemplate($name, $template);
public function removeTemplate($name);
public function removeTemplates();
}
Inject and hard code the template name in controller classes.
// Some controller class
public function fooAction()
{
$view = new ViewModel();
$view->setTemplate($this->templateProvider->get('some_view_name'));
return $view;
}
Now you can create client specific factories that inject custom template script config into your template provider. All you would then need to do is decide which template provider service you want to inject into your controller.
class ViewTemplateProviderFactory
{
public function __invoke($sm, $name, $rname)
{
$config = $sm->get('config');
if (! isset($config['view_template_providers'][$rname])) {
throw new ServiceNotCreatedException(sprintf('No view template provider config for \'%s\'.', $rname));
}
return new ViewTemplateProvider($config['view_template_providers'][$rname]);
}
}
The key here is ALL view scripts, for all clients, are registered under the 'view_manager' key as normal however the name of the template in the controller never changes.
Edit
You could just use one factory and pull from config (see changes above).
return [
'view_template_providers' => [
'ClientOneTemplateProvider' => [
'some_view_name' => 'name_of_script_1'
],
'ClientTwoTemplateProvider' => [
'some_view_name' => 'name_of_script_2'
],
'ClientThreeTemplateProvider' => [
'some_view_name' => 'name_of_script_3',
],
],
'service_manager' => [
'factories' => [
'ClientOneTemplateProvider' => 'ViewTemplateProviderFactory',
'ClientTwoTemplateProvider' => 'ViewTemplateProviderFactory',
'ClientThreeTemplateProvider' => 'ViewTemplateProviderFactory',
],
],
'view_manager' => [
'template_map' => [
'name_of_script_1' => __DIR__ . 'file/path/to/script',
'name_of_script_2' => __DIR__ . 'file/path/to/script',
'name_of_script_3' => __DIR__ . 'file/path/to/script',
],
],
];
It seems I solved my problem, but i'm not sure it's the good way to do it. So if someone can do better, I let the bounty runs for a better solution, if exists.
Here is what I've done :
/**
* Factory permettant d'établir que les vues client soient chargé si elle existent, avant les vues par défaut.
*/
class ClientStrategyFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$viewRenderer = $serviceLocator->get('ViewRenderer');
$session = new \Zend\Session\Container('Session');
$clientList = $serviceLocator->get('Config')['customers_list'];
$clientName = $this->clientMap($session->offsetGet('cod_entprim'), $clientList);
$clientMap = new TemplateMapResolver($clientName);
$viewRenderer->resolver()->getIterator()->insert($clientMap, 2);
return new ClientStrategy($viewRenderer);
}
/**
* permet de retourner le namespace du client selectionné avec la relation codpriml / nom de dossier
* #param integer $codprim
* #param array $clientList
* #return array
*/
public function clientMap($codprim, $clientList)
{
return (isset($clientList[$codprim])) ? $clientList[$codprim]: false;
}
}
You can see that my custom TemplateMapResolver needs a clientName, this is for loading custom views. But the most important thing is : I don't create a new Resolver, I just add my Resolver to the list by this line :
$viewRenderer->resolver()->getIterator()->insert($clientMap, 2);
The second argument means, that this resolver is top priority (Default priority is 1)
My TemplateMapResolver is pretty much simple, the most important thing is this :
public function __construct($client)
{
$this->setClientName($client);
if ($this->getCLientName()) {
$map = $this->getMap();
} else {
$map = array();
}
parent::__construct($map);
}
/**
* Return all custom views for one client
* #param array $map
* #return array
*/
public function getMap()
{
$name = $this->getClientName() . '\\' . $this->getClientName() ;
$class = new $name;
return $class->customViews();
}
My solution, force me to create then a class in my clients folder with the same name of the folder so, if my clientName is TrumanShow i will have an architecture like :
- [clients]
-- [TrumanShow]
--- TrumanShow.php
--- [Cart]
---- [view]
----- [cart]
------ [index]
------- cart-view.phtml
--- [Invoice]
--- [Reporting]
And in this file I will have this function that declare all my custom views :
/**
* Ici nous mettons nos custom views afin de les charger dans le template Map
* #return array
*/
public function customViews()
{
return array(
'cartView' => __DIR__ . '/Cart/view/cart/index/cart-view.phtml',
);
}
So it's possible to do this without break template_path_stack or my others routes. Now I have to call setTemplate method in my Controller, like this :
// code ...
public function cartAction() {
$view->setTemplate('cartView');
return $view;
}
And ZendFramework will check first if a custom view exists in my clients folder, or load the common view if no view is found.
Thanks to #Wilt and #AlexP for their contribution and help.
Don't overcomplicate things. Just set the ViewModel's template before you render it.
$vm = new ViewModel();
$vm->setTemplate( $user_service->getTemplate( $this->getRequest() ) );
return $vm;
Pretty clean if you inject your user into this fictitious user service, and use it to ascertain which template to inject.
The concern of the $user_service should be completely disparate from the concern for your Controller action.
I've made a global variable in bootstrap of Module.php
public function setCashServiceToView($event) {
$app = $event->getParam('application');
$cashService = $app->getServiceManager()->get('Calculator/Service/CashServiceInterface');
$viewModel = $event->getViewModel();
$viewModel->setVariables(array(
'cashService' => $cashService,
));
}
public function onBootstrap($e) {
$app = $e->getParam('application');
$app->getEventManager()->attach(\Zend\Mvc\MvcEvent::EVENT_RENDER, array($this, 'setCashServiceToView'), 100);
}
I can use it inside of my layout.phtml as
$this->cashService;
But I need this variable to use in my partial script of navigation menu, which I call in layout.phtml:
echo $this->navigation('navigation')
->menu()->setPartial('partial/menu')
->render();
?>
How can I use it inside of my partial/menu.phtml? And may be there is a better way, than to declare it in onBootstrap function?
Thank you for your answers. I decided to make an extended class of \Zend\View\Helper\Navigation\Menu to provide there a property of cashService. However I receive an error:'Zend\View\Helper\Navigation\PluginManager::get was unable to fetch or create an instance for Calculator\Service\CashServiceInterface'.
I need this service to display navigation menu. Seems weird, but that's true. I display some diagram in it, using the data, which I get from the service. So why do I have the error?
I added to module.config.php
'navigation_helpers' => array(
'factories' => array(
'mainMenu' => 'Calculator\View\Helper\Factory\MainMenuFactory'
),
MainMenuFactory:
namespace Calculator\View\Helper\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Calculator\View\Helper\Model\MainMenu;
Class MainMenuFactory implements FactoryInterface {
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator) {
return new MainMenu(
$serviceLocator->get('Calculator\Service\CashServiceInterface')
);
}
P.S: CashServiceInterface is an alias to CashServiceFactory
You could remove the event listener and use a custom view helper to access the service in the view.
namespace Calculator\View\Helper;
use Zend\View\Helper\AbstractHelper;
class CashService extends AbstractHelper
{
protected $cashService;
public function __construct(CashServiceInterface $cashService)
{
$this->cashService = $cashService;
}
public function __invoke()
{
return $this->cashService;
}
}
Create a factory.
namespace Calculator\View\Helper;
class CashServiceFactory
{
public function __invoke($viewPluginManager)
{
$serviceManager = $viewPluginManager->getServiceLocator();
$cashService = $serviceManager->get('Calculator\\Service\\CashServiceInterface');
return new CashService($cashService);
}
}
Register the new helper in moudle.config.php.
'view_helpers' => [
'factories' => [
'CashService' => 'Calculator\View\Helper\CashServiceFactory',
],
],
Then you can use the plugin in all view scripts.
$cashService = $this->cashService();
What's the "Zend" way of adding default variables to the ViewModel.
Currently I have:
return new ViewModel(array('form' => new CreateUserForm));
But I want to always add some variables to the ViewModel array. Like the time and date say, or categories for a menu. I was thinking of extending the ViewModel as that seems the OO way, but Zend always does things differently...
You could always extend the ViewModel if you want some extra functionality in there...
class MyViewModel extends ViewModel
{
/**
* Default Variables to set
*/
protected $_defaultValues = array(
'test' => 'bob'
);
/**
* Constructor
*
* #param null|array|Traversable $variables
* #param array|Traversable $options
*/
public function __construct($variables = null, $options = null)
{
//$variables = array_merge($this->_defaultValues, $variables);
$this->setVariables($this->_defaultValues);
parent::__construct($variables, $options)
}
}
Now in your controller just use return your new view model instead:
/**
* Some Controller Action
*/
function myAction()
{
return new MyViewModel();
}
One approach could be to have a method in your controller that returns ViewModel populated with time, date, etc. and then addVariables() to the returned model in the Action.
However, a better approach will be to use view helpers since they will be available in every view/layout throughout the application.