Forcing the Twig Locale - php

I would like to use the Twig template system to template my e-mails. The locale of the e-mail should be based on a user setting, not from the session or request locale. How can I force the locale when rendering a Twig template?
The manual does mention how to force the locale for the Translator. But i'd like pass this locale to the render() method, in order to have the translations inside the twig template to be rendered in this locale.
This is different from using into in the template, because I think this forces a translation inside the template in a specific locale.
So, taking the example from Symfony, I'm looking for something like this:
public function indexAction($name)
{
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('send#example.com')
->setTo('recipient#example.com')
->setBody(
$this->renderView(
'HelloBundle:Hello:email.txt.twig',
array('name' => $name),
'nl_NL' // <-- This would be nice!
)
)
;
$this->get('mailer')->send($message);
return $this->render(...);
}

You can pass the locale through as an argument when you use the trans filter (see diff: https://github.com/symfony/symfony/commit/3ea31a02630412b1c732ee1647a0724378f67665).
So you could pass another user_locale variable in your render method in your controller (or pass through the whole user object instead of passing name and user_locale separately, or use app.user in your template if the user will be logged in, etc... (depending on your application obviously)), then in your email template you can have something like this:
{{ 'greeting' | trans({}, "messages", user_locale) }} {{ name | title }}
{# rest of email template with more translation strings #}
Then in your translation file for that locale (assuming you're using yaml) just have something like this and the translations will work nicely for you on the fly:
# messages.fr.yml
greeting: 'Bonjour'

Get a hold of the Translator component and change its locale before rendering the template. This solution does not require passing an extra value to the parameters' array of the render() method and painfully refactoring all your Twig files.
public function indexAction($name)
{
$translator = $this->get('translator');
// Save the current session locale
// before overwriting it. Suppose its 'en_US'
$sessionLocale = $translator->getLocale();
$translator->setLocale('nl_NL');
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('send#example.com')
->setTo('recipient#example.com')
->setBody(
$this->renderView(
'HelloBundle:Hello:email.txt.twig',
array('name' => $name)
)
)
;
$this->get('mailer')->send($message);
// Otherwise subsequent templates would also
// be rendered in Dutch instead of English
$translator->setLocale($sessionLocale);
return $this->render(...);
}
A common approach to user mailing is storing the user's locale in the User entity and passing it directly to the translator, such as in this code snippet:
$translator->setLocale($recipientUser->getLocale());

Here is a solution (it works well, except sub-templates (Twig: render(controller('AppBundle:Invoice/Index:productTotalPartial')))
<?php
namespace Main\BoBundle\Service;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Class LocaleSwitcher
*
* #package Main\BoBundle\Service
*/
class LocaleSwitcher
{
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #var string
*/
private $previousLocale;
/**
* #param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* Change the locale
*
* #param string $locale
*/
public function setLocale($locale)
{
$this->previousLocale = $this->translator->getLocale();
$this->translator->setLocale($locale);
$this->setPhpDefaultLocale($locale);
}
/**
* Revert the locale
*/
public function revertLocale()
{
if ($this->previousLocale === null) {
return;
}
$this->translator->setLocale($this->previousLocale);
$this->setPhpDefaultLocale($this->previousLocale);
$this->previousLocale = null;
}
/**
* Use temporary locale in closure function
*
* #param string $locale
* #param \Closure $c
*/
public function temporaryLocale($locale, \Closure $c)
{
$this->setLocale($locale);
$c();
$this->revertLocale();
}
/**
* Sets the default PHP locale.
* Copied from Symfony/Component/HttpFoundation/Request.php
*
* #param string $locale
*/
private function setPhpDefaultLocale($locale)
{
// if either the class Locale doesn't exist, or an exception is thrown when
// setting the default locale, the intl module is not installed, and
// the call can be ignored:
try {
if (class_exists('Locale', false)) {
/** #noinspection PhpUndefinedClassInspection */
\Locale::setDefault($locale);
}
} catch (\Exception $e) {
}
}
}
Example:
if ($this->getLocale()) {
$this->localeSwitcher->setLocale($this->getLocale());
}
$html = $this->templating->render($templatePath);
if ($this->getLocale()) {
$this->localeSwitcher->revertLocale();
}

u can do this: send a paramater (e.g. locale) to template
public function indexAction($name)
{
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('send#example.com')
->setTo('recipient#example.com')
->setBody(
$this->renderView(
'HelloBundle:Hello:email.txt.twig',
array('name' => $name, 'locale' => 'nl_NL'),
)
)
;
$this->get('mailer')->send($message);
return $this->render(...);
}

Related

send mail returns null

When trying to send a message, In my view verifyEmail.blade.php $agent is null and $agent->name says trying to get property of non object.
verifyEmail.blade.php
<body>
<h2> Welcome to our website{{ $agent->name }} </h2>
click here to verify your email
</body>
This is how I am using the Mail class. In my Mail folder in verifyEmail file I have a construct function which collects the $agent model.
verifyEmail.php
class verifyEmail extends Mailable
{
public $agent;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($agent)
{
$this->$agent = $agent;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('emails.verifyEmail');
}
}
And in my admin controller which does the registration for the user looks like this. The send method passes the agent model to the verifyEmail.php which was working in the tutorial I watched. How can I make agent model available in verifyEmail.blade.php
AdminController
$agent = new agent($data);
$agent->name = $data["name"];
$agent->email = $data["email"];
$agent->nrc = $data["nrc"];
$agent->resident = $data["residents"];
$agent->password = Hash::make($data["password"]);
$agent->save();
verifyUser::create(
[
'token' => Str::random(60),
'agent_id' => $agent->id,
]
);
Mail::to($agent->email)->send(new verifyEmail($agent));
I think there can be a slight modification in the code provided to solve this.
Inside the file verifyEmail.php, the line
$this->$agent = $agent;
should be
$this->agent = $agent;
Because $this->$agent might not be able to find the class level variable 'agent' and update its value that is provided in the constructor so it will have the default value null which is shown afterwards.
Configuring The Sender
Using The from Method
First, let's explore configuring the sender of the email. Or, in other words, who the email is going to be "from". There are two ways to configure the sender. First, you may use the from method within your mailable class' build method:
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('example#example.com')
->view('emails.orders.shipped');
}
Using A Global from Address
However, if your application uses the same "from" address for all of its emails, it can become cumbersome to call the from method in each mailable class you generate. Instead, you may specify a global "from" address in your config/mail.phpconfiguration file. This address will be used if no other"from" address is specified within the mailable class:
'from' => ['address' => 'example#example.com', 'name' => 'App Name'],
In addition, you may define a global "reply_to" address within your config/mail.php configuration file:
'reply_to' => ['address' => 'example#example.com', 'name' => 'App Name'],
So you can try this In verifyEmail.php Change this
public function build()
{
return $this->view('emails.verifyEmail');
}
To this
public function build()
{
return $this->from('info#domain.com')->view('emails.verifyEmail');
}

Laravel 7 set log path dynamically in Job class

Im building project on Laravel 7.3 with multiple Jobs that run at the same time.
I need to make each Job write logs to different daily rotated file. The name of the log file should be based on model, that Job is processing.
The issue is I cant find smart solution.
What I have tried:
1) creating multiple channels in config/logging.php.
That works as expected but at the moment there are about 50 different Jobs and amount keeps growing. Method is ugly and hardly maintained.
2) setting up Config(['logging.channels.CUSTOMCHANNEL.path' => storage_path('logs/platform/'.$this->platform->name.'.log')]);.
Messing with Config variable was bad idea because of many Jobs running one time. As a result messages from one job often were written in another Job log.
3) using Log::useDailyFiles()
Seems like this stops working since laravel 5.5 or 5.6. Just getting error Call to undefined method Monolog\Logger::useDailyFiles(). Any thoughts how to make with work in laravel 7?
4) using tap parameter for channel in config/logging.php.
Example in laravel docs
No ideas how to pass model name into CustomizeFormatter to setup file name.
Im almost sure there is smart solution and Im just missing something.
Any suggests? Thanks!
You could inherit the log manager to allow a dynamic configuration
<?php
namespace App\Log;
use Illuminate\Support\Str;
use Illuminate\Log\LogManager as BaseLogManager;
class LogManager extends BaseLogManager
{
/**
* Get the log connection configuration.
*
* #param string $name
* #return array
*/
protected function configurationFor($name)
{
if (!Str::contains($name, ':')) {
return parent::configurationFor($name);
}
[$baseName, $model] = explode(':', $name, 2);
$baseConfig = parent::configurationFor($baseName);
$baseConfig['path'] = ...; //your logic
return $baseConfig;
}
}
Likewise about Laravel's log service provider except this one can be totally replaced
<?php
namespace App\Log;
use Illuminate\Support\ServiceProvider;
class LogServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->singleton('log', function ($app) {
return new LogManager($app);
});
}
}
EDIT: I've just seen that Laravel's log service provider is missing from config/app.php, this is because it's "hard-loaded" by the application. You still can replace it by inheriting the application itself
<?php
namespace App\Foundation;
use App\Log\LogServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
use Illuminate\Foundation\Application as BaseApplication;
class Application extends BaseApplication
{
/**
* Register all of the base service providers.
*
* #return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
}
And finally in bootstrap/app.php, replace Illuminate\Foundation\Application with App\Foundation\Application
For example, if you try this
app('log')->channel('single:users')->debug('test');
Laravel will use the single channel's config and write to users.log if your resolution logic is
$baseConfig['path'] = $model + '.log';
I got a solution that I've been using since Laravel 4 that works, although it doesn't follow 'Laravel' way of doing things.
class UserTrackLogger
{
/**
* #var $full_path string
*/
protected $full_path;
/**
* #var $tenant string
*/
protected $tenant;
/**
* #var $user User
*/
protected $user;
/**
* #var $request Request
*/
protected $request;
public static function log(string $message, Request $request, User $user, array $data = []): void
{
/** #noinspection PhpVariableNamingConventionInspection */
$userTrack = new static($request, $user);
$userTrack->write($message, $data);
}
protected function __construct(Request $request, User $user)
{
$this->request = $request;
$this->user = $user;
$this->tenant = app()->make('tenant')->tenant__name;
$path = storage_path() . "/logs/{$this->tenant}/users";
$filename = $this->user->username_with_name;
$this->full_path = Formatter::formatPath("{$path}/{$filename}.log");
self::makeFolder($this->full_path);
}
protected function write(string $message, array $data = []): void
{
$formatter = $this->getFormat();
$record = [
'message' => $message,
'context' => $data,
'extra' => [],
'datetime' => date(Utility::DATETIME_FORMAT_DEFAULT),
'level_name' => 'TRACK',
'channel' => '',
];
file_put_contents($this->full_path, $formatter->format($record), FILE_APPEND);
}
protected function getFormat(): FormatterInterface
{
$ip = $this->request->getClientIp();
$method = strtoupper($this->request->method());
$format = "[%datetime%][{$this->tenant}][{$this->user->username}][{$this->user->name}]: $ip $method %message% %context%\n";
return new LineFormatter($format, null, true);
}
protected static function makeFolder(string $full_path): bool
{
$path = dirname($full_path);
if ( !is_dir($path) ) {
return mkdir($path, 0755, true);
}
return false;
}
}
And when I want to log something, I do UserTrackLogger::log($request->fullUrl(), $request, $user, $data);
What I would suggest is creating a logger similar to this but extends RotatingFileHandler.

Laravel 5 catching included view params in view composer

So i'am trying to make some widget service, but got one problem. In my dashboard i including number of partial views 'widget' and passing to it parameter 'settings'. Everything should be ok to me, but i want to read this 'settings' parameter in my view composer so i should be able to pass correct 'newData'
to 'widget' view. I found something like $view->getData(); but when i use this infinite loop accurs. Any suggestions?
Note: i MUST use view composer, and CAN'T pass this 'newData' while including.
index.blade.php
<p>
#include('widget', ['settings'=>'bla'])
</p>
Some service provider:
public function boot()
{
view()->composer(
'widget', 'App\Http\ViewComposers\WidgetComposer'
);
}
App\Http\ViewComposers\WidgetComposer.php
class WidgetComposer
{
/**
* Bind data to the view.
*
* #param View $view
* #return void
*/
public function compose(View $view)
{
$view->with('newData', 'someArrayWithData');
}
}
widget.blade.php
<p>
{!! $newData !!}
</p>
Ok, so it should be $view->getData();
class WidgetComposer
{
/**
* Bind data to the view.
*
* #param View $view
* #return void
*/
public function compose(View $view)
{
$settings = $view->getData()['settings'];
$data = $settings == 'bla' ? 'got bla' : array();
$view->with('newData', $data);
}
}
This works clean project, however in my main project this causes infinity loop in renderer. Not sure if this is because of some variables that i shouldnt be using here or because of some dependicies. I will add comment if found out something.

Loading clients views before default views if exists

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.

ZF2 How to use global variables in the view

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!

Categories