I have a project with 3 different Layouts. 1 Layout for the Login page, 1 Layout for the administrators and editors and a 3rd Layout for general users where also the administrators have access to (its a layout for 3 different pages where you can fill out surveys which is possible for the 3 mentioned user groups).
At the moment I am using EdpModuleLayouts for this purpose, which did fine until now. Because now I need to adapt the 3rd layout depending on which usergroup accesses it.
Do you have any idea how to get this done?
Thanks in advance.
you can listen you to the zf2 mvc events and change your layout based on your criterias.
this is an example how your application Module.php can look like.
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedManager = $eventManager->getSharedManager();
// DISPATCH EVENT
$sharedManager->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function( MvcEvent $e) use ($serviceManager) {
// get your instance to locate current user group or something else
$auth = $serviceManager->get('Some/Auth/Service');
$userGroup = $auth->getUserGroupFromCurrentUser();
$controller = $e->getTarget();
if( $userGroup == 'someGroup' )
{
$controller->layout('layout/somelayoutname');
} else {
$controller->layout('layout/someotherlayoutname');
}
}, 50 );
}
Related
I'm wanting to put a google analytics account number into the local or global.php for each update across the application. However, I can't figure out how to access it from the views. Am I going about this wrong, or is there a way to do this? What I want to avoid is putting things into every controller to pass it along. I just want the layouts to be able to grab it.
If there's a better solution to this, I'm all ears.
Thanks!
If you have just a code for all the pages, probably you can hardcode all the analytics code in your layout view, so it will be there for al the modules view.
If you dont want to hardcode it, or you dont want to have the code in all the pages, or you have a set of diferent layout files, then I think that the best you can do is to create a ViewHelper.
Ill try to explain it from the beginning, in case this is the first time you do this.
First, create a Module, i'd call it Utils.
in the modules src, you can create a folder Utils\View\Helper and there you create a file Analytics.php with the class Analytics, as follows:
<?php
namespace Utils\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\Mvc\Controller\Plugin\FlashMessenger as FlashMessenger;
class Analytics extends AbstractHelper
{
var $code = null;
public function setCode($code)
{
$this->code = $code;
}
public function __invoke()
{
ob_start();
?>
<!-- analytics-->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '<?=$this->code?>']);
_gaq.push(['_trackPageview']);
(function () {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = 'http://www.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
<!-- end analytics-->
<?php
return ob_get_clean();
}
}
?>
in your module.php create function getViewHelperConfig where you can define the factory for the viewhelper your about to create, likethis
public function getViewHelperConfig()
{
return array(
'factories' => array(
'analytics' => function($sm) {
$config = $sm->getServiceLocator()->get('Config');
//now in config, you have all your configurations (local, global, etc) as an asociative array
$add = new \Utils\View\Helper\Analytics();
//you asign here the code from your merged configuration
//(replace *analytics_account_number* with whatever you called it)
$add->setCode($config["analytics_account_number"]);
return $add;
}
));
}
Now, inside a view, you can simply call
echo $this->analytics();
and the framework will search the factory, create the object, asign the values, and call the invoke method, to return the full code to your view. And thats all. write once, run anywhere!
How can we get local value (i.e: 'en' or 'en_US', 'de' etc) in layout.phtml or views in Zend Framework 2?
My local setting are exactly same as explained here
<?php
namespace FileManager;
use Zend\Mvc\ModuleRouteListener;
class Module
{
public function onBootstrap($e)
{
$translator = $e->getApplication()->getServiceManager()->get('translator');
$translator
->setLocale(\Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']))
->setFallbackLocale('en_US');
}
//...
}
I want to get local value something like this:
$locale = $this->translate()->getLocale(); // <-- It's not working anyway
I need to use '$locale' it while calling google map api url to get matched locale/language. I'm calling it throughtout the application in layout.phtml
$this->headScript()->appendFile('http://maps.googleapis.com/maps/api/js?language=' . $locale);
So I want to make language option dynamic while calling api.
PS: I don't have any query string parameter such as 'language', It's a google api thing which I need to set in script url (if you don't know) Please don't get confused.
Not answered here
Depends on where you want to get the Locale value from. In any case, you can do it in your controller, e.g.:
$locale = $this->request->getQuery('language');
$this->layout()->locale = $locale;
or
return new ViewModel(array('locale' => $locale));
Edit if you just want to get the locale from the translator, you can try this in view script:
$this->plugin('translate')->getTranslator()->getLocale();
My version is like that
<?php
namespace FileManager;
use Zend\Mvc\ModuleRouteListener;
use Zend\Session\Container;
class Module
{
public function onBootstrap($e)
{
$application = $e->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$events = $eventManager->getSharedManager();
// session container
$sessionContainer = new Container('locale');
// test if the language in session exists
if(!$sessionContainer->offsetExists('mylocale')){
// doesn't so the browser lan
if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){
$sessionContainer->offsetSet('mylocale', Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']));
}else{
$sessionContainer->offsetSet('mylocale', 'en_US');
}
}
// translation
$translator = $serviceManager->get('translator');
$translator ->setLocale($sessionContainer->mylocale)
->setFallbackLocale('en_US');
$mylocale = $sessionContainer->mylocale;
$events->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) use ($mylocale) {
$controller = $e->getTarget();
$controller->layout()->mylocale = $mylocale;
}, 100);
}
//...
}
in your layout
$this->headScript()->appendFile('http://maps.googleapis.com/maps/api/js?language=' . $this->mylocale);
I am using ZF2 as a component of another application.
I am looking for a way to set the URL and View Template of the application between an init() and a run() call. I would like a way to either modify the Request and Response objects, or regenerate them with a different URL.
I currently use ob_start() and ob_get_clean() and a view template that simply generates the_content, thus injecting the output of ZF2 inside a page of another application.
Any suggestions on methodology would be appreciated.
In Module.php you can attach event to event manager for exemple.
class Module
{
public function onBootstrap($e)
{
$eventManager = $e->getApplication()->getEventManager();
$serviceManager = $e->getApplication()->getServiceManager();
$eventManager->attach(MvcEvent::EVENT_ROUTE, function($e) use ($eventManager, $serviceManager){
// your code here
}, -1000);
}
}
Or your action in your controller can dispatch another action and get the result
in action method :
$return = $this->forward()->dispatch('controllerName', array('action' => 'actionName', 'param1' => 'value', ...));
The following code inside another application can be used to set the calling URL and View Template from outside of the application:
$bootstrap = \Zend\Mvc\Application::init( include( '/zf2/config/application.config.php' ) );
$event = $bootstrap->getMvcEvent( );
/* Modify the event with a custom request. */
$request = new \Zend\Http\Request( );
$request->setMethod( \Zend\Http\Request::METHOD_GET );
$request->setUri( $custom_url );
$event->setRequest( $request );
/* Modify the view. */
$event->getViewModel()->setTemplate('layout/custom-layout');
ob_start( );
$bootstrap->run( );
$html = ob_get_clean( );
At present I set a couple of variables to be used by the app's overall layout.phtml, using the onDispatch method of a BaseController, which all my other controllers extend:
public function onDispatch(MvcEvent $e)
{
$config = $this->getServiceLocator()->get('config');
$this->layout()->setVariable('platformName', $config['platform']['name']);
$this->layout()->setVariable('platformYear', $config['platform']['year']);
}
This works fine, until I test some error pages and find that these pages do not get provided with the variables, as it's not using the base controller.
How can I get around this problem and provide the error pages with the same variables?
Change the event you're listening for.
In this case, I'd move this logic to the application bootstrap event or the application render event (I haven't tested this, but it would probably work fine).
One example, in your Module.php
public function onBootstrap($e)
{
$config = $e->getApplication()->getServiceManager()->get('config');
//$e->getViewModel()->setVariable();
}
Haven't tested that commented out line, but it should get you headed in the right direction.
EDIT: Found an example of using the render event
public function onBootstrap($e)
{
$event = $e->getApplication()->getEventManager();
$event->attach('render', function($e) {
$config = $e->getApplication()->getServiceManager()->get('config');
$e->getViewModel()->setVariable('test', 'test');
});
}
(Necro)
When using onDispatch in a Controller, remember to return the parent with the event and all:
public function onDispatch(MvcEvent $e)
{
// Your code
return parent::onDispatch($e);
}
Otherwise, the logic on your Actions in that Controller will be ignored.
Edit
after digging into the symfony code, particularly the ControllerResolver, it seems what im trying to do actually isnt possible unless i subclass/implement ControllerResolverInterface myself.
this is the following code which instantiates the controller passed from the route:
protected function createController($controller)
{
if (false === strpos($controller, '::')) {
throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller));
}
list($class, $method) = explode('::', $controller, 2);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
return array(new $class(), $method);
}
as you can see on the last line, this is always instantiated with no arguments passed, so i will have to override this method to inject something in that way. feels very hacky.
Original Question
I'm trying to figure out how I can inject services into a custom controller defined in dynamic routes using Symfony components (e.g. not the full stack framework).
Please note, I am not using the full stack framework and am not using their DemoBundle src code. I have a composer.json file that requires components, so I have a custom index.php file which is more or less the same as that detailed here:
http://fabien.potencier.org/article/55/create-your-own-framework-on-top-of-the-symfony2-components-part-12
I have the following:
$routes = new RouteCollection();
$routes->add(
'some route name',
new Route(
'a route path',
array(
'_controller' => 'App\MyBundle\Controller\MyController::handle'
)
)
);
Then I have the following within App/MyBundle/DependencyInjection/MyExtension.php:
public function load(array $configs, ContainerBuilder $container) {
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resource/config')
);
$loader->load('services.xml');
}
App/MyBundle/Resources/config/services.xml:
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="templating" class="Symfony\Component\Templating\EngineInterface" />
<service id="navigation" class="App\MyBundle\Controller\MyController">
<argument type="service" id="templating" />
</service>
</services>
</container>
I'm basically trying to get the templating service injected into the MyController constructor, and my understanding is the MyExtension file should be loaded automatically. I assume as I'm not using the full stack framework, this is the reason why, but how can I get this working?
Nothing wrong with overriding ControllerResolver. The full stack framework does that too. Otherwise the Controllers couldn't be ContainerAware.
I also use Symfony Components without the full stack framework and, partly copying the full stack framework, I ended up with this in order to inject the container in my controllers
class ControllerResolver extends SymfonyControllerResolver
{
protected $container;
public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
{
$this->container = $container;
parent::__construct($logger);
}
protected function createController($controller)
{
if (false === strpos($controller, '::')) {
throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller));
}
list($class, $method) = explode('::', $controller, 2);
$class = "Namespace\\Controllers\\" . $class;
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
return array($controller, $method);
}
}
If you wanted to add possibility to define controllers as services you can replace
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
With something like
if (!class_exists($class)) {
if (!$this->container->has($class)) {
throw new \Exception( ... );
}
$controller = $this->container->get($class);
return array($controller, $method);
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
return array($controller, $method);
Well, at first. You don't have to inject services into your controller. A normal controller will extend Symfony\Bundle\FrameworkBundle\Controller\Controller which gets the hole container injected. This means you can access the templating service like this:
public function myAction()
{
$templating = $this->get('templating');
}
But Symfony2 gives you also the possibility of creating controller as services. That means you remove the extend from the default Controller and instead of that only inject the services you need (usually request and response). More information can be found in this great post by Richard Miller.
You can also read this post by Lukas Kahwe Smith, in which he talks about why he thinks services are a 'best practise' (please note that Fabien, former of the Symfony project, disagrees with this).