How to Embed Controllers in a Template using Symfony 4 directory structure - symfony

I did a fresh Symfony installation by using Symfony Flex and the new skeleton belong to the next Symfony 4 directory structure. Next, I'm going to embed a controller from a twig template.
I've tried to do it like this:
<div id="sidebar">
{{ render(controller(
'App:Article:recentArticles',
{ 'max': 3 }
)) }}
</div>
The same with How to Forward Requests to another Controller, I've tried:
$response = $this->forward('App:Something:fancy', array(
'name' => $name,
'color' => 'green',
));
But is not working.
There is way to do this?

When we using the short convention a:b:c to refer to the controller, it will only work if the controller belong to an installed bundle. For Symfony 4 structure your app source is not a bundle by default, so the above does not work.
For Symfony 4 approach you can refer to this controller using its fully-qualified class name and method:
App\Controller\ArticleController::recentArticlesAction
Embedding the controller into Twig templates:
{{ render(controller('App\\Controller\\ArticleController::recentArticlesAction')) }}

This is the correct way to do this:
{# templates/base.html.twig #}
{# ... #}
<div id="sidebar">
{{ render(controller(
'App\\Controller\\ArticleController::recentArticles',
{ 'max': 3 }
)) }}
</div>
Source: https://symfony.com/doc/master/templating/embedding_controllers.html

Related

reusable dynamic sidebar in Symfony 4 (Twig)?

I recently started using Symfony 4 and I am creating my first website with this wonderful framework right now.
I have a sidebar that should be displayed in about half of my routes and the content of the sidebar should be filled with some data from a database.
Currently I use DI in all this routes and pass the result of the injected repository to the template (which includes my sidebar.html.twig) for the route.
public function chalupaBatman(FancyRepository $repository)
{
$sidebarObjects = $repository->getSidebarObjects();
$this->render('controllername/chalupabatman.html.twig', [
'sidebarObjects' => $sidebarObjects,
]);
}
I am wondering if there is a way to avoid this for every route I define in my controllers.
So far I found this topic on stackoverflow.
The User Mvin described my problem in a perfect way and also provided some solutions.
However there is still no answer to "what is the best practice" part also the topic is from 2017; therefor, the way to solve this may have changed in Symfony 4.
I ended up with a TwigExtension solution. I'll describe how to achieve it and it would be great if you guys could provide some feedback.
Let me know if I produce massive overhead or miss something essential ;-)
Alright, first of all I created a TwigExtension via command-line
php bin/console make:twig-extension AppExtension
And then I modified the class to look like this:
<?php
namespace App\Twig;
use App\Repository\ArticleRepository;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions(): array
{
return [
new TwigFunction('article_sidebar', [$this, 'getArticleSidebar'], ['needs_environment' => true, 'is_safe' => ['html']]),
];
}
public function getArticleSidebar(\Twig_Environment $twig)
{
$articleRepository = $this->container->get(ArticleRepository::class);
$archive = $articleRepository->myAwesomeLogic('omegalul');
return $twig->render('/article/sidebar.html.twig', [
'archive' => $archive,
]);
}
public static function getSubscribedServices()
{
return [
ArticleRepository::class,
];
}
}
In order to activate Lazy Performance so our Repository and the additional Twig_Environment doesn't get instantiated everytime when we use Twig
we implement the ServiceSubscriberInterface and add the getSubscribedServices-method.
Therefor, our Repo and Twig_Environment only gets instantiated when we actually call {{ article_sidebar() }} inside a template.
{# example-template article_base.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="row">
<div class="col-10">
{% block article_body %}{% endblock %}
</div>
<div class="col-2">
{{ article_sidebar() }}
</div>
</div>
{% endblock %}
Now I am able to define my templates for the article-routes like this:
{# example-template /article/show.html.twig #}
{% extends 'article_base.html.twig' %}
{% block article_body %}
{# display the article here #}
{% endblock %}

How do I pass variables to theme template via custom controller drupal 8

I currently have a theme I purchased installed on my Drupal website.
Inside the theme folder there is a templates folder that contains:
page.html.twig
page--front.html.twig
a snippet from page.html.twig looks as follows:
<footer class="site-footer">
<div class="layout-container">
{% if page.footer_first or page.footer_second or page.footer_third %}
<div class="footer-top">
<div class="footer-top-inner">
{{ page.footer_first }}
{{ page.footer_second }}
{{ page.footer_third }}
</div>
</div>
{% endif %}
{% if page.footer_left or page.footer_right %}
<div class="footer-bottom">
<div class="footer-bottom-inner">
{{ page.footer_left }}
{{ page.footer_right }}
</div><!-- /.footer-bottom-inner -->
</div>
{% endif %}
</div>
</footer>
I have a custom module I created (business_listing) that loads different .html.twig templates I add these templates in business_listing.module:
function business_listing_theme($existing, $type, $theme, $path) {
return [
// business-listing-detail.html.twig
'business_listing_detail' => [
'variables' => ['data' => [], 'slides' => [], 'paths' => [], 'page' => []],
],
]
}
Basically I would like to know how I can add the markup for the footer dynamically from my custom modules controller.
I have a page called business-listing-detail.html.twig inside my custom modules templates folder. From what I understand business-listing-detail.html.twig uses/somehow extends page.html.twig in my theme/templates. What I would now like to know is how I can add the sections like {{page.footer_left}} to my business-listing-detail.html.twig or to page.html.twig using my controller? I have tried adding the footer using the following: `
$page['footer_left'] = "This should appear in the .footer-bottom div?";
$build = [];
$build['business_listing_detail'] = [
'#theme' => 'business_listing_detail',
'#data' => $record,
'#slides' => $slides,
'#paths' => $this->paths,
'#page' => $page
];`
in the controller function associated to my business-listing-detail page the hopes that page.html.twig will detect this page.footer_left and render display the footer, however this did not work. The variable exists in business-listing-detail.html.twig but the {% if page.footer_left %} in my themes page.html.twig is not fired. Please please please, any help or advice would be greatly appreciated <3
Basically all I am trying to do, is dynamically render a template for a specific page in my custom module, that allows me to implement/send markup to the sections/regions defined in my theme eg. featured_top, content_top & page.content
Kind regards,
Matt
It seems like you might be going down the wrong path... I'd highly suggest you read up about the Drupal 8 Block system, and then investigate custom blocks.

How to change template of controller rendered in twig file (Symfony 3)?

I know that we can render the content of a controller in twig file like this:
{{ render(controller('FOSUserBundle:Security:login',{"baseTemplate": true})) }}
However, I don't know if we can pass the new template so that the controller will use it instead of the default. Anyone tried to override template in this way?
I don't really understand the issue here
If you do
{{ render(controller('FOSUserBundle:Security:login',{"baseTemplate": true})) }}
You could aswell do:
{{ render(controller('FOSUserBundle:Security:login',{"template": "your_template.html.twig"})) }}
Or
{{ render(controller('FOSUserBundle:Security:login',{"templateNumber": "4"})) }}
Where templateNumber is used in a condition inside your controller ?

Service to get/set current entity

I have a Project entity, which has a controller defining many routes:
projects/1
projects/1/foo
projects/1/bar
I need a service to provide the current project. The use case is that I have dependencies in my base twig templates which need to know the current project. i.e. a dropdown project selector that is outside the context of the template the current controller is serving.
I've tried creating a service getting route info with $container->get('router.request_context');, however, that only provides a path. I don't want to have to parse the path string if I don't have to.
What is the most proper approach?
If I understood you correctly the solution for your problem is rendering/embedding controller. Of course it's simplest, yet somehow acceptable solution for rendering parts of html with some custom logic apart from current template.
You can read about rendering/embedding controllers.
Some snippets...
Define controller:action (obviously the logic in my example is pretty straight forward):
/**
* Generate select input field.
*
* #Route("/widget", name="project_widget")
* #Method("GET")
*/
public function widgetAction()
{
$repo = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:Project');
// #NOTICE: Wee need master request info, because our widget
// is rendered and called as subrequest.
$masterRequest = $this->get('request_stack')->getMasterRequest();
// #NOTICE: You need second parameter as default value in case there is no id in masterRequest route.
$currentProjectId = $masterRequest->get('id', 0);
$currentProject = $repo->find($currentProjectId);
$projects = $repo->findAll();
return $this->render('project/widget.html.twig', [
'currentProject' => $currentProject,
'projects' => $projects,
]);
}
Then you need to create the template project/widget.html.twig for it:
<div class="widget_project_selection">
{% if projects is not empty %}
<select name="widget_project_selection" id="widget_project_selection">
<option value="">None</option>
{% for project in projects %}
<option value="{{ project.id }}"
{# #NOTICE: If this is current project, select it #}
{{- currentProject and project.id == currentProject.id
? 'selected="selected"'
: '' -}}
>
{{- project.title -}}
</option>
{% endfor %}
</select>
{% else %}
<span>{{ 'Sadly, no projects yet :('|trans }}</span>
{% endif %}
</div>
and at last (but not least) render it somewhere like in base.html.twig:
{{- render(controller('AppBundle:Project:widget')) -}}
I've created for you a Github repo as reference. It's a small Symfony app with similar setup. You can even run it if you like, don't forget about dependencies and database update thou. Entry point is /app_dev.php/project/
Take a look at widgetAction, widget template and example usage in base.html.twig.
EDIT: But that's not everything. You said you need a service. If for some reason rendering/embedding controller is not an option for you or you really whant to use a Service (as in Dependency Container) you can extend Twig and use the full power of services.
I've also implemented a Twig Filter as example to show you the real power of Twig Extensions in here and here (usage in templates).
Check out Twig Extension and Extending Twig for more info about Twig Extensions.
Also check out service.yml for service and extension definitions - if you are not using Symfony3.3+, there will be some additional work to do - defining service and extension directly.
In your controller, you can use type hinting to load the "current" entity via the route.
For example:
#myrouter.yml
current_project:
path: /projects/{project}
Separately, your controller...
//mycontroller.php
public function myControllerAction(Request $request, Project $project)
{
//$project is entity (assuming valid) loaded via the route above
return $this->render('mytemplate.twig', ['project' => $project]);
}

Make controller only give a value to template, not render it - Symfony2

I have a twig template with the navbar and all other templates (the pages) include this template. I have a value in it which should be equal to all pages. How to set this value?
I tries something like this in a controller:
public function setNotificationsAction() {
$this->setNotifications();
return $this->render('AcmeMyBundle::navbar.html.twig', array(
'debts' => $this->notifications,
));
}
and then this in the template:
<span class="badge badge-important">
{% render(controller('AcmeMyBundle:DebtsLoansController:setNotifications')) %}
{{ debts }}
</span>
The result I want it like this:
<span class="badge badge-important">
3
</span>
but the number should be different and the controller should tell it.
I also tried to create a function which returns the value and to call it in the way like above.
I also tried this syntax
{{ render(controller('AcmeMyBundle:DebtsLoansController:setNotifications')) }}
but it isn't working, too.
I get the following mistake:
The function "controller" does not exist in AcmeMyBundle::navbar.html.twig at line 6
Do you have any idea how to achive this and not to have to edit each controller and each template :S Thanks very much in advance!
Well, I would suggest creating your own Twig extension. Something around the lines of:
<span class="badge">
{{ acme_notifications() }}
</span>
namespace Acme\DemoBundle\Twig\AcmeDemoExtension
class AcmeDemoExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
'acme_notifications' => new \Twig_Function_Method($this, 'getNotifications');
);
}
public function getNotifications()
{
$notifications = ...;
return $notifications;
}
}
Read more about creating your own Twig extension in the Symfony2 documentation.
You don't need the controller part :
{% render "AcmeBundle:MyController:MyAction" %}
Be aware however, that a render is a completely new request going through the whole Symfony lifecycle and thus can impact performance if you abuse it.
Edit : And as #Wouter J has pointed out : prior to Symfony 2.2 use above notation. After Symfony 2.2 the following has to be used :
{{ render(controller('AcmeArticleBundle:Article:recentArticles', { 'max': 3 })) }}

Resources