I'm trying to manage multiple templates in Symfony. The active Template comes from a database and I have a controller which gives the correct path entry.
My Problem is to tell symfony about this path. I have searched the Twig render method in multiple classes but changes are not successfully.
My TemplateController.php
public function loadtpl() {
$repo = $this->getDoctrine()->getRepository(Templates::class);
$found = $repo->findByActive(1);
$tpl = $found[0]->getPath();
return $tpl;
}
This gives me the template path but I find no way to tell symfony about it.
UPDATE:
What I have - 2 different layouts located in templates/layout1 and template/layout2
What I get - my TemplateController (above) returns the active layout path (layout1/)
Now I can edit my twig.yaml to say twig my template path to ../templates/layout1 so I can use render(mysite.html.twig); which is located in layout1 (and layout2) but its not what I want.
What I want - I want to extend the base template path dynamically with my layout paths so that I can use the method render (mysite.html.twig) without editing twig.yaml manually.
What I need - I need the twig or symfony class to edit the main render() method but I can't find the right File. OR: Anyone have an idea which is better to solve this problem.
The render funtion expects the following params:
/**
* Renders a view.
*
* #param string $view The view name
* #param array $parameters An array of parameters to pass to the view
* #param Response $response A response instance
*
* #return Response A Response instance
*
* #final since version 3.4
*/
So as long as you pass the correct view name: #BUNDLE_NAME:RESOURCE_NAME_FOLDER:TWIG_FILE then you should be good to go.
UPDATE:
I think I understand what you need after you provided more details. As no example was provided I will try to work with realistic scenario.
Imagine you have 2 templates, blue and red, they have structure and color differences but the content is mostly the same. I would then have 2 directories under my template folder.
/app
/Resources
/views
/blue
base_template.html.twig
/red
base_template.html.twig (they can have different names it doesn't really matter)
each one would define a base_template where you set your imports and other specifics of the template.
Now on you controller you get the base_template value from the DB as your function already does.
Then in your controller, you can use that value and pass it to your template that will extend it dynamically.
public function indexAction()
{
return $this->render('AppBundle:Home:index.html.twig',["base_template"=>loadTpl()]);
}
Finally in your twig file you would extend the template like this:
{% extends base_template %}
{% block content %}
<div class="container">
My content
</div>
{% endblock %}
Here is a link to : Twig Dynamic Inheritance
Related
I'm starting a project using Phalcon framework with Volt as a template engine. I have some experience with Symfony/Twig.
I read documentation and tried searching all over the internet but can't find a satisfying way to accomplish what I want (I find ugly the solution described here: How do I create common template with header and footer for phalcon projects with Volt Engine; it's not using Volt per se for the navigation.)
So the story is pretty easy: my base template consists of 4 parts: navigation, header, content and footer. I use partials to include the common areas in the base template like the navigation, header and the footer, works fine with "static data".
Now the question is: how do I get to dynamically generate the navigation menu with items coming from the database? The template will have common areas that have to come from DB also in the header, footer and a sidebar. Having to fetch that in all Controller actions sounds like overkill and not very DRY (maybe do it on the init part? but will have to be done in every controller. Maybe in an abstract controller, I dunno.)
What is the best way to accomplish this in Phalcon/Volt? In Symfony/Twig you can call from the view a controller action, so you can have like a LayoutController that renders partials from a page.
Thanks!
Here are few variants:
1) Your controllers can extend a BaseController and in it's initialize() method you can assign those variables to the view.
class BaseController extends \Phalcon\Mvc\Controller
{
public function initialize()
{
// Common Variables
$this->view->assetsSuffix = $this->config->debug ? '' : '.min';
}
2) Create a custom Volt function which loads the data.
// In your Volt service:
$compiler->addFunction('getMenu', function($resolvedArgs, $exprArgs){
return 'Helpers\CommonFunctions::getMenu(' . $resolvedArgs . ')';
})
// Helper file and function
public static function getMenu()
{
return \Models\Menu::find();
}
// Volt usage
{% set menuItems = getMenu() %}
{% for item in menuItems %}
{% endfor %}
3) Use the models to query the DB directly from the template. However this is not yet supported with Volt (not sure if it is added in latest version, have to confirm).
<?php $menuItems = \Models\Menu::find(); ?>
{% for item in menuItems %}
{% endfor %}
4) Ajax/Javascript, but this is really dependant on your application. Something like Angular approach, but I will not get into details with this varaiant.
I would like to configure multiple URLs to one Action in Controller (internationalization purposes).
According to this answer it was surely possible in symfony2 to:
Make double annotation route.
Use 3rd party Bundle (for example "BeSimple's").
But I'm using Symfony 3.0.3 which prohibits me from doing so until I change the route's name (example):
/**
* #Route("/welcome", name="welcome", defaults={"_locale" = "en"})
* #Route("/bienvenue", name="welcomeFR", defaults={"_locale" = "fr"})
* #Route("/willkommen", name="welcomeDE", defaults={"_locale" = "de"})
*/
But adding additional "FR/DE" chars to routes change their presence and ruins my URL generating logic in template, I'm forced to make on all links:
{# homepage example #}
{% if _locale = 'en' %}
{{ path('welcome') }} {# Routes from set only for "en" #}
{% elseif _locale = 'fr' %}
{{ path('welcomeFR') }} {# "fr" only links #}
{% endif %} {# and so on #}
Anyone found the proper solution for this problem?
AFAIK, this is the preferred way to point multiple routes to an unique controller action. So, your current problem is to regenerate the current path, depending on which route is being used
Maybe you don't have to modify your logic if you use {{ app.request.get('_route') }} to get the name of your current routing. This way, you could use:
{{ path(app.request.get('_route')) }}
UPDATE:
What about create an action per route and forwarding them to the main language action? Maybe it is not the best practice, but could work fine
/**
* #Route("/welcome", name="welcome", defaults={"_locale" = "en"})
*/
public function welcomeAction()
{
/* code here */
}
/**
* #Route("/bienvenue", name="welcomeFR", defaults={"_locale" = "fr"})
*/
public function welcomeFrAction()
{
$response = $this->forward('AppBundle:ControllerName:welcome');
}
/*
* #Route("/willkommen", name="welcomeDE", defaults={"_locale" = "de"})
*/
public function welcomeDeAction()
{
$response = $this->forward('AppBundle:ControllerName:welcome');
}
After wasting 10 hours on finding solution on Symfony3 I've made those assumptions:
None of the route trick with exactly same "name" wouldn't work,
Any kind like double annotation, double "routing.yml" importing, host based matching gives the same effect - only one route is matched, usually the last one. Would be ("/willkommen", name="welcomeDE") in my example.
Both of the i18n bundles does not work on Symfony3 through the versioning constraints,
Composer wouldn't even let us install the bundle.
Locale Listener and Loader (Routing Component) is dying on caching.
My solution to match the "_locale" and pass it to the LocaleLoader in order to load "routing_en.yml/routing_fr.yml" etc. files respectively is cached on the first match by the Symfony and after that, changing the "_locale" does not affect route's mapping.
In conclusion
Symfony3 seems not supporting route "example" with more than one "url" at all. Through the constraints and caching.
After the disappointment I'm considering going back to Symfony 2.8, have no idea what was pointing Symony's masters to block "double annotation" solution and what is their current idea on links internationalization.
Hope someday it will be possible to achieve with S3, as link i18n is quite common SEO practice.
EDIT: Confirmed, working like a charm on 2.8.5 with BeSimple's i18n.
So, theming a form in Symfony2 is easy. You create a custom theme file and you add it to your config.yml file to load it. Done.
However, I have 2 different form themes. One for the front-end of the application and one for the back-end of the application.
I went through the documentation (http://symfony.com/doc/2.3/cookbook/form/form_customization.html) but couldn't find a good and easy way to do this.
When I add the theme to the config.yml file, I have the same theme in both front-end and back-end. I could also include the form within each view like this
{% form_theme form 'form_table_layout.html.twig' %}
However, that means I have to do it within each view.
Is there somehow a way to create a separate config file for front-end and back-end? Can I somehow indicate in the base template file which form theme should be used?
Anything else I could do?
If you use the Symfony2 default directory structure, i.e. you have a single kernel for both the frontend and the backend, you can only (as you mentioned) either set the form theme in each template, or use the same template application-wide by setting it in the config.yml file.
The alternative solution you mentioned, that is creating two base templates, each one setting a "global" form_theme tag would theoretically work. Create a base front-end.html.twig template for all your frontend pages with the following tag:
{% form_theme form 'form-front-end.html.twig' %}
That would work, but you would be forced to have a form variable in each inherited template. You would also not be able to set the theme to multiple forms in the same page.
You could improve the solution by checking if the form variable is defined before styling it:
{% if form is defined %}
{% form_theme form 'form-front-end.html.twig' %}
{% endif %}
or even better, if you want to be able to passing multiple form to the same template, you could do it by using a forms array:
{% if forms is defined %}
{% for form in forms %}
{% form_theme form 'form-front-end.html.twig' %}
{% endfor %}
{% endif %}
The good thing is that this would not throw any exceptions, even if you don't pass the variable at all, but you have to remember to put any forms to be rendered into the forms array.
Obviously, you would do the same thing for the back-end base template.
There may be better solutions, but in the meanwhile I hope this helps!
I also faced this problem some times ago, found this post, and followed the way as Andrea Sprega suggested. Recently I came out of a way which can save some repetitive typings.
Warning: This is somewhat "hackish", is probably not the best way, and is not guarantee to work in next framework release. You'll see why when you read below.
Goal: We want to have different default form templates depending on the application "environment" (the most common examples would be "frontend" and "backend). But fundamentally Symfony isn't aware of such "environment". Although it is possible to split the project into different environments and load different configurations, this is definitely an overkill if what we want is just having different form templates.
I think the best way would be an ability to set/override the default form themes in the base template. So we can have form theme A in frontend base template, and form theme B in backend base template. This sounds the best solution to me because as form theme is a "view" stuff, it makes perfect sense to have it changed in the view. However, the problem seems to be that, when the twig (template) code is executed, the form view is already initialized. So it would be too late to change the default form theme there. (I could be wrong here, because I didn't dig deep enough to have 100% confidence)
So I decided to do it another way. It works like this:
First, I made an assumption that all frontend controllers will extend the same base class (e.g. "BaseFrontendController"). Similarly, all backend controllers will extend the same BaseBackendController class. Here's how we distinguish a frontend and backend environment. This is actually the case in my project.
The default twig templates will be added to these base controller classes. It can be done via a method, or an annotation. In this post I'll use a public method.
Before the controller is executed, overwrite the defined default twig templates.
When the form view is initialized, it will use the default twig template defined in the controller.
Here's how it's done:
Firstly, the default form themes defined in your config.yml will be passed to the constructor of \Symfony\Component\Form\AbstractRendererEngine, and is assigned to the local instance $defaultThemes. This field is protected so that it can be used by its derived classes, but there is no setter to change its value.
So, we need to roll our own Symfony\Bridge\Twig\Form\TwigRendererEngine:
namespace AppBundle\Form\Twig;
use Symfony\Bridge\Twig\Form\TwigRendererEngine as BaseTwigRendererEngine;
class TwigRendererEngine extends BaseTwigRendererEngine
{
/**
* #param array $defaultThemes
*/
public function addDefaultThemes($defaultThemes)
{
$this->defaultThemes = array_merge($this->defaultThemes, $defaultThemes);
}
}
This custom renderer engine is plain simple - it just add a new method to append the default themes to the existing one. And this is why I say it's "hackish" - it will no longer work when the internal is changed.
Secondly, define an interface TwigTemplateProvider which is going to be implemented by the base frontend/backend controller classes:
namespace AppBundle\Form\Twig;
interface TwigTemplateProvider
{
/**
* #return array|string The form template path
*/
public function getDefaultFormTwigTemplates();
}
Thirdly, we need a listener which will run when the controller is executed.
<?php
namespace AppBundle\EventListener;
use AppBundle\Form\Twig\TwigRendererEngine;
use AppBundle\Form\Twig\TwigTemplateProvider;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class PerControllerFormTemplateListener
{
/**
* #var \Twig_Environment
*/
private $twig;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TwigTemplateProvider) {
/** #var \Symfony\Bridge\Twig\Extension\FormExtension $formExtension */
$formExtension = $this->twig->getExtension('form');
$engine = $formExtension->renderer->getEngine();
if ($engine instanceof TwigRendererEngine) {
$templates = (array)$controller[0]->getDefaultFormTwigTemplates();
$engine->addDefaultThemes($templates);
}
}
}
}
The listener will get the form template name (in string or array) supplied by controllers implementing TwigTemplateProvider interface. Then it will add it to the default theme list and pass it to the form renderer engine.
Now, wire them together by adding the followings to your services.yml:
parameters:
twig.form.engine.class: AppBundle\Form\Twig\TwigRendererEngine
services:
app.form.per_controller_template_listener:
class: AppBundle\EventListener\PerControllerFormTemplateListener
arguments: ["#twig"]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Here we set the %twig.form.engine.class% parameter to our own implementation, and added our event listener to the stack.
Using it is very simple. For example, in your base frontend controller, implement TwigTemplateProvider and add the following method:
public function getDefaultFormTwigTemplates()
{
return 'frontend/form_layout.html.twig';
}
Then this layout will be added to the form template stack when your front controller is executed.
I am experiencing an issue trying to reference a particular Twig template. I am using the render method that is part of the SF2 main controller, but I clearly not referencing/using it correctly.
This is my directory/file structure:
/src
/AyrshireMinis
/CommonBundle
/Controller
DefaultController.php
/Entity
Link.php
/Resources
/views
/Default
links.html.twig
and this is the method called by the router in DefaultContoller.php:
/**
* #Route("/links", name="ayrshireminis_links")
* #Template()
*/
public function linksAction()
{
$links = $this->getDoctrine()->getRepository('AyrshireMinisCommonBundle:Link')->findAll();
return $this->render('AyrshireMinisCommonBundle:Link:links.html.twig', array('links' => $links));
}
But this is the error I get:
Unable to find template
"AyrshireMinisCommonBundle:Link:links.html.twig".
I think it's because the template "AyrshireMinisCommonBundle:Link:links.html.twig" does not exist, try changing it to "AyrshireMinisCommonBundle:Default:links.html.twig"
I had exactly the same problem with the same directory structure. The equivalent of 'AyrshireMinisCommonBundle:Default:links.html.twig' didn't work (meaning that the directory still could not be found). It worked when I changed it to 'Default/links.html.twig'
(Using Symfony 2.3 and PHP 5.3)
My site I have some content can be voted (+/-). It is working fine now, when all content has its own voter.
Now I'm looking for a way to create a single voting bundle with a entity(votedModel,votedId, user, vote).
Basically the bundle is ready. My problem is how to use it. I'd like to be able to do something like:
class ... extends Controller {
function showAction(Request $request,$id) {
...
$voter=new Voter('myCOntentType',$id,$userid);
...
return $this->render('...', array('voter'=>$voter->getVoter(),...))
}
}
getVoter() would create the voter view.
but I'm stacked how exactly start. I tried to call for the other controller in this manner, but can't create the voter form.
It worked with $voter=$this->forward('VoterbundleNewAction', array('id=>$id,'user'=>$user)->getContent();But this wasn't I had in mind.
I think my approach is all wrong and I may need to do this as service. I cant find my way around.
You can use include or render in your twig template to get other templates' output. So you could create a template (say, voter.html.twig) that contains the HTML for your voting system, and in the Twig, in any place where you need a voter, you could use:
{% include "AcmeVoterBundle:Voter:voter.html.twig" %}
or
{% render "AcmeVoterBundle:Voter:voter" with {"item": item} %}
In the first example, you simply include another template (see also: http://symfony.com/doc/current/book/templating.html#including-other-templates), in the latter situation you actually execute another action method of a controller and the output of that is put into your current template (see also: http://symfony.com/doc/current/book/templating.html#embedding-controllers)