Symfony2 twig mobile template fallback - symfony

I need a simple way to fallback on a default template if no mobile version exists.
With some regular expressions I recognize mobile platforms and want to render a template with the following pattern:
<template_name>.mobile.html.twig
But if this template doesn't exist, I want it to automatically fallback on:
<template_name>.html.twig
which always exists.
I tried nearly all the answers from this post:
Symfony 2 load different template depending on user agent properties
but with no success. Unfortunately there are no version numbers referenced.
At the moment I am trying to copy and modify the default twig loader.
By the way, What I want to achieve with this is the possibility to deploy different templates for mobile devices by just adding a template of the same name and adding a .mobile.
UPDATE:
http://www.99bugs.com/handling-mobile-template-switching-in-symfony2/
This one is also a good approach. It modifies the format property of the request object which affects the automatic template guessing when you don't specify a template in the controller with the render function (or annotation) but just return an array.
Resulting template name:
view/<controller>/<action>.<request format>.<engine>
So you could switch the request format from html to mobile.html based on the device detection.
The downside of this is that every template needs a mobile.html pendant (which then could just include the non-mobile version if not needed).
UPDATE:
Besides using a custom templating provider there is also the possibility to hook into the kernel.view event.

You could create a service to handle it and then use it in the same way that you do the templating service like so..
Create a service with the templating and request service injected into it..
Service (YAML)
acme.templating:
class: Acme\AcmeBundle\Templating\TemplatingProvider
scope: request
arguments:
- #templating
- #request // I assume you use request in your platform decision logic,
// otherwise you don't needs this or the scope part
- 'html'
Class
class TemplatingProvider
{
private $fallback;
private $platform;
... __construct($templating, $request, $fallback) etc
private function setPlatform() ... Your platform decision logic
private function getPlatform()
{
if (null === $this->platform) {
$this->setPlatform();
}
return $this->platform;
}
private function getTemplateName($name, $platform)
{
if ($platform === 'html') {
return $name;
}
$template = explode('.', $name);
$template = array_merge(
array_slice($template, 0, -2),
array($platform),
array_slice($template, -2)
);
return implode('.', $template);
}
public function renderResponse($name, array $parameters = array())
{
$newname = $this->getTemplateName($name, $this->getPlatform());
if ($this->templating->exists($newname)) {
return $this->templating->render($newname);
}
return $this->templating->renderResponse($this->getTemplateName(
$name, $this->fallback));
}
And then you could just call your templating service instead of the current one..
return $this->container->get('acme.templating')
->renderResponse('<template_name>.html.twig', array());

Can't you check if the template exist before ?
if ( $this->get('templating')->exists('<templatename>.html.twig') ) {
// return this->render(yourtemplate)
} else {
// return your default template
}
OR :
You can create a generic method, to insert in your root controller like :
public function renderMobile($templateName, $params)
{
$templateShortName = explode('.html.twig', $templateName)[0];
$mobileName = $templateShortName.'.mobile.html.twig';
if ( $this->get('templating')->exists($mobileName) ) {
return $this->renderView($mobileName, $params);
} else {
return $this->renderView($templateName, $params)
}
}
with this you can do :
return $this->renderMobile('yourtemplate', [yourparams]);

You can easily do this by harnessing the bundle inheritance properties in Symfony2 http://symfony.com/doc/current/cookbook/bundles/inheritance.html
create a bundle which holds your desktop templates (AcmeDemoDesktopBundle)
create a bundle which will hold your mobile templates (AcmeDemoMobileBundle) and mark the parent as AcmeDemoDesktopBundle
Then when you render a template simply call AcmeDemoMobileBundle:: - if the template exists, it'll be rendered otherwise you'll neatly fall back to the desktop version. No extra code, listeners or anything none-obvious required.
The downside of this of course is that you move your templates out of the individual bundles.

The fallback behavior you describe isn't that easy to implement (we found out the hard way..). Good news is we wanted the same setup as you ask for and ended up using the LiipThemeBundle for this purpose. It allows you to have different "themes" based on for example a device. It will do the fallback part for you.
For example:
Rendering a template:
#BundleName/Resources/template.html.twig
Will render and fallback to in order:
app/Resources/themes/phone/BundleName/template.html.twig
app/Resources/BundleName/views/template.html.twig
src/BundleName/Resources/themes/phone/template.html.twig
src/BundleName/Resources/views/template.html.twig
Edit: so with this approach you can have default templates that will always be the final fallback and have a special template for mobile where you need it.

Related

Somfony 2 set header content mime-types automatically

I have a question about Symfony 2.
I would like to know how if there is a function implemented in Symfony 2 who return the content mime-type ?
Why mime-type cause trouble ? i have some file and i dont want everyone access to it then i made a methode who check if you have the right to access to this ressource.
chdir("Directory/".$nameoftheressource);
$file = file_get_contents($nameoftheressource);/**/
$namearray=explode(".", $nameoftheressource);
$extension=end($namearray);
$returnFile= new Response();
$returnFile->setContent($file);
if($extension == "css" )
{ $returnFile->headers->set('Content-Type', 'text/css');
return $returnFile;}
Thanks to you xabbuh this is near to work perfectly and as u said saved lot of time
now the code look like
EDIT
use Symfony\Component\HttpFoundation\BinaryFileResponse;
//some code
return new BinaryFileResponse('/Directory/'.$nameoftheressource);
But now it does display the css file but propose me to download it bt i would like to display it as a css normal css file
You can save a lot of code by using the BinaryFileResponse class which among other things automatically adds the right content type header.
It seems that you want to serve a protected CSS file. In this case, you can use the following code and protect the access to this controller using the Symfony Security system:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
class DefaultController extends Controller
{
/**
* #Route("/css/app.css", name="css")
* #Security("has_role('ROLE_ADMIN')")
*/
public function renderCss()
{
$cssFilePath = $this->container->getParameter('kernel.root_dir').'/data/app.css';
$cssContent = file_get_contents($cssFilePath);
return Response($cssContent, 200, array('Content-Type' => 'text/css'));
}
}

Share a method with all controllers : Best practice

I'm developing a notification system in symfony2 and I need to get the notifications for every page I'm running.
the trivial solution is to copy the content of the function in every controller and call the function from $this.
How can I make the notification function accessible for every controller? I heard that setting a controller as service is bad practice. what's the best practice then ?
If just using it for output in the template then best approach would be to use a custom TwigFunction and then calling that in a base/layout/extended template like so..
TwigExtension
namespace Acme\NotificationBundle\Twig;
use Acme\NotificationBundle\Provider\NotificationProviderInterface;
class AcmeNotificationExtension extends \Twig_Extension
{
protected $container;
protected $notificationProvider;
public function __construct(
ContainerInterface $container,
NotificationProviderInterface $notificationProvider
)
{
$this->notificationProvider = $notificationProvider;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction(
'acme_render_notifications',
array($this, 'renderNotifications')
),
);
}
public function renderNotification($template = 'default:template.html.twig')
{
$notifications = $this->notificationsProvider->getCurrentNotifications();
// Or whatever method provides your notifications
return $this->container->get('templating')->render(
$template,
array('notifications' => $notifications)
);
}
public function getName()
{
return 'acme_notification_extension';
}
}
Services
parameters:
acme.twig.notification_extension.class:
Acme\NotificationBundle\Twig\AcmeNotificationExtension
services:
acme.twig.notification_extension:
class: %acme.twig.notification_extension.class%
arguments:
- #service_container
- #acme.provider.notifcation
// Or what ever your notification provider service is named
tags:
- { name: twig.extension }
This way you would be able to call your notifications in any template using acme_render_notifications() (with the default template) or acme_render_notifications('AcmeOtherBundle:Notifications:in_depth.html.twig') (with a different template if needed) and your controller aren't even touched.
If it was put in a parent template in a block like ..
{% block notifications %}
{{ acme_render_notifications() }}
{% endblock notifications %}
..then it would run on every page unless you had overridden the block in your child class.
The way I would do it, and I think it is among the best practices, is setting up a service with the function then just instantiate it in each controller.
No doubt its bad practice,
Many solutions are possible, Here we will discuss on abstract level
A global utility can be used with different scopes (application, session scope) depending upon the requirements
Make this utility accessible to all available controllers

creating css using twig

reading symfony documentation about templating I found mention about twig being able to output css files.
how is this used? is it possible to generate dynamic css same way as I generate html?
for example when I want to display some html template I create controller action and inside I render .html.twig file possibly passing it some parameters.
can I render .css.twig same way? where would be this file stored and how could I possibly include it to another html template.
I would like to keep all styles in separate files, but some of these styles change under some conditions. for example, right now I'm setting height of some divs according to calculations in controller and I pass result height as parameter to template. but I don't feel like this is very MVC, having part of representation logic in controller (or even model).
It certainly is possible. You would do most of things exactly the same as if you would do for html template.
Create file, eg:
/* src/Acme/MyBundle/Resources/views/somefile.css.twig */
.someclasss {
background-color: {{ backgroundColor }};
}
Create controller action
// src/Acme/MyBundle/Controller/MyStyleController.php
// ...
public function styleAction()
{
// Fetch it from service or whatever strategy you have
$backgroundColor = '#ff0000';
return $this->render(
'AcmeMyBundle::somefile.css.twig',
['backgroundColor' => $backgroundColor],
['Content-Type' => 'text/css']
);
}
// ...
Create route for that action
# src/Acme/MyBundle/Resources/config/routing.yml
css_route:
path: /some/path
defaults: { _controller AcmeMyBundle:MyStyleController:style }
methods: [GET]
Use that css in your layout
{# src/AcmeMyBundle/Resources/views/mypage.html.twig #}
{# ... #}
<link href="{{ path('css_route') }}" rel="stylesheet">
{# ... #}
Now, whether or not this is a good idea should be a separate question. There certainly are some cases where this approach is perfectly valid, but there are cases where you can avoid this. Keep in mind that serving CSS file like this is a lot more expensive than serving static CSS file. Furthermore, since it's a CSS file and it's in HEAD section of your response, it will slow down page load time since it will have to fetch CSS file before rendering body itself.
If you do decide to do this be sure to check out caching possibilities you have to make this as fast as possible.
Actually
public function styleAction()
{
// Fetch it from service or whatever strategy you have
$backgroundColor = '#ff0000';
return $this->render(
'AcmeMyBundle::somefile.css.twig',
['backgroundColor' => $backgroundColor],
['Content-Type' => 'text/css']
);
}
should be more like this
/**
* #Route("/css/style", name="style")
* #param Request $request
* #return Response
*/
public function styleAction(Request $request)
{
$firstColor = 'rgba(130, 30, 30, 0.9)';
/** #var Response $response */
$response = $this->render(':css:style.css.twig', [
'firstColor' => $firstColor,
]);
$response->headers->set('Content-Type', 'text/css');
return $response;
}
Note that I have annotations because I use Symfony 3. But the important thing in my code sample is that for the response I set Content-Type to 'text/css'.

Zend Framework 2 - Add style to controller

I have been looking through the documentation, and I can't seem to find a way to do this. I know I can use headScript to add style sheets to individual views, but I would like to add a style sheet to all actions in a controller.
Has anyone done this? I am sure it is a simple task.
Thanks
What you need to do is hook into the dispatch event and, based on the type of controller that was dispatched, set the appropriate layout (recommended). You could also directly modify the view and add the required assets.
This can be achieved by using the following code in your Module class:
<?php
namespace App;
class Module
{
public function onBootstrap(MvcEvent $event)
{
$event->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH, function (MvcEvent $event)
{
$application = $event->getApplication();
$services = $application->getServiceManager();
$view = $services->get('ViewRenderer');
$controller = $event->getTarget();
if ($controller instanceof \App\Controller\Entry)
{
$controller->layout('layout/app/entry');
// -- OR --
$view->headStyle()->appendStyle('body{background:red}');
}
}, 100);
}
}
I hope this answers your question!

SonataAdminBundle Extending templates

I have set up my Admin class to render a custom template:
public function getTemplate($name)
{
switch ($name)
{
default:
case 'list':
return 'MyBundle:Admin:list.html.twig';
break;
return parent::getTemplate($name);
break;
}
}
This is working OK. I can enter some html in my template file and it renders OK. However, I want to extend the existing templates from the admin bundle as I only want to make some minor changes for this entity.
I've added the following to my template file:
{% extends 'SonataAdminBundle:CRUD:base_list.html.twig' %}
But this gives me the following error:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 77 bytes)
Can anyone tell me what I am doing wrong?
Your switch/case is incorrect.
It should be:
public function getTemplate($name)
{
switch ($name) {
case 'list':
return 'MyBundle:Admin:list.html.twig';
break;
default:
return parent::getTemplate($name);
break;
}
}
I'm not sure if you are doing anything 'wrong' (besides the weird case syntax that doesn't do what I assume you think it does, see http://php.net/manual/en/control-structures.switch.php and scroll down to section describing the importance of 'break' statements).
It does seem like it's possible put symfony in an infinite loop when extending templates. I've seen this with a couple of templates. I haven't figured out exactly what triggers it, but I think it has something to do with bundle inheritance using EasyExtends. In my application I had a child sonata-admin bundle:
class ApplicationSonataAdminBundle extends Bundle
{
/**
* {#inheritdoc}
*/
public function getParent()
{
return 'SonataAdminBundle';
}
}
I then had overridden the standard_layout.html.twig with just the contents:
{% extends "SonataAdminBundle::standard_layout.html.twig" %}
This was causing "SonataAdminBundle::standard_layout.html.twig" to be loaded an infinite number of times because the template seems to effectively be extending itself.
Assuming your setup is similar to mine. I suspect that the only way to try to do what you are trying to do is to use a different template name (e.g. "my_standard_layout.html.twig") and then set that template as the application wide default as described here: https://sonata-project.org/bundles/admin/master/doc/reference/templates.html#configuring-templates

Resources