In Symfony 3.4, base.html.twig I have a navbar showing number of the current user's messages. I use a repository entity function to do this. This function must be call every time when template base.html.twig is rendering but I don't want to put this function in all controllers how to do this by event listener before rendering base.html.twig? Override base controller ?
base.html.twig :
....
{{ include top_bar_nav.html.twig }}
....
A custom Twig extension is the correct way:
example in twig:
{{ number_of_current_users() }}
create twig extension like this:
<?php
namespace AppBundle\Twig;
use Doctrine\ORM\EntityRepository;
class UserExtension extends \Twig_Extension
{
/**
* #var EntityRepository
*/
private $userRepository;
/**
* #param EntityRepository $repository
*/
public function __construct(EntityRepository $repository)
{
$this->userRepository = $repository;
}
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('number_of_current_users', array($this, 'numberOfCurrentUsers')),
);
}
/**
* #param $sku
*
* #return string
*/
public function numberOfCurrentUsers()
{
return $this->userRepository->getNumberOfCurrentUsers();
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'user';
}
}
and register it like this:
app.twig.users:
class: AppBundle\Twig\UserExtension
arguments: ['INJECT YOUR USER REPOSITORY HERE']
public: false
tags:
- { name: twig.extension }
Related
I am new to Drupal 8 my question is:
is there a way to create a php function in .theme file and calling it from a twig template file?
One way is to use global preprocess in .theme file.
function MYTHEME_preprocess(array &$variables, $hook) {
//this is a global hook, its variables are available in any template file
$variables['test'] = 'today';
}
{{ test }} will render 'today'.
Another way is create own custom Twig functions in a custom module.
Reference -https://drupal.stackexchange.com/questions/271770/how-to-call-a-function-in-a-twig-file
Can call like this on twig templates {{ getRoleValues('admin') }}
src/MyTwigExtension.php
<?php
namespace Drupal\MyTwigModule;
/**
* Class DefaultService.
*
* #package Drupal\MyTwigModule
*/
class MyTwigExtension extends \Twig_Extension {
/**
* {#inheritdoc}
* This function must return the name of the extension. It must be unique.
*/
public function getName() {
return 'role_values';
}
/**
* In this function we can declare the extension function
*/
public function getFunctions() {
return array(
new \Twig_SimpleFunction('getRoleValues',
[$this, 'getRoleValues'],
['is_safe' => ['html']]
)),
}
/**
* Twig extension function.
*/
public function getRoleValues($roles) {
$value = 'not-verified';
if ($roles == "admin") {
$value = 'verified';
}
return $value;
}
}
src/MyTwigModule.services.yml
services:
MyTwigModule.twig.MyTwigExtension:
class: Drupal\MyTwigModule\MyTwigExtension
tags:
- { name: twig.extension }
Because I have many controllers, after the user is logged in, I want to transfer some user settings to Twig. But I don't want to make it in each controller:
Eg:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$user = $this->getUser();
$settings = $user->getSettings();
// ...
}
}
Is there a possibility to make it at a higher level from the call of the DefaultController?
Solution 1
Use app.user variable in Twig which is globally accessible:
{{ app.user.username }}
More info: https://symfony.com/doc/current/templates.html#the-app-global-variable
Solution 2
Write custom Twig function:
// src/Twig/UserExtension.php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Symfony\Component\Security\Core\Security;
class UserExtension extends AbstractExtension
{
Security $security
public function __construct(Security $security)
{
// Avoid calling getUser() in the constructor: auth may not
// be complete yet. Instead, store the entire Security object.
$this->security = $security;
}
public function getFunctions()
{
return [
new TwigFunction('get_user_settings', [$this, 'getUserSettings']),
];
}
public function getUserSettings()
{
$user = $this->security->getUser();
return $user->getSettings();
}
}
Usage in Twig:
{{ get_user_settings().setting1 }}
More info: https://symfony.com/doc/current/templating/twig_extension.html
How to inject current logged user: https://symfony.com/doc/current/security.html#b-fetching-the-user-from-a-service
Ok, maybe I don't describe it clearly. This is the solution I have searched for:
<?php
namespace AppBundle\Subscriber;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class ControllerSubscriber implements EventSubscriberInterface
{
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
/**
* #var EntityManagerInterface
*/
private $em;
/**
* ControllerSubscriber constructor.
*
* #param TokenStorageInterface $tokenStorage
* #param EntityManagerInterface $em
*/
public function __construct(TokenStorageInterface $tokenStorage, EntityManagerInterface $em)
{
$this->tokenStorage = $tokenStorage;
$this->em = $em;
}
/**
* #param FilterControllerEvent $event
*/
public function onKernelController(FilterControllerEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if (!$token = $this->tokenStorage->getToken()) {
return;
}
if (!$user = $token->getUser()) {
return;
}
// if no user
if (!$user instanceof UserInterface) {
return;
} else {
// .... do something
}
}
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
// must be registered before (i.e. with a higher priority than) the default Locale listener
KernelEvents::CONTROLLER => array(array('onKernelController', 1)),
);
}
}
services.yml
app.controller.subscriber:
class: AppBundle\Subscriber\ControllerSubscriber
arguments: ["#security.token_storage","#doctrine.orm.default_entity_manager"]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
In Symfony2 it was straightforward to override the RoutingExtension, that I could inject some extra parameters.
I'm using a dynamic domain to route to different parts of my application.
{subdomain}.domain.com
However, I don't want to have to specify subdomain every time I call path or url in twig.
I could create my own unique filter name, but I'd rather not.
Previously, we could put this in the services.yaml file and it would work.
services:
twig.extension.routing:
class: AppBundle\Twig\Extension\RoutingExtension
public: false
arguments:
- '#router'
- '#request_stack'
- '%domain%'
Symfony2 Twig overriding default path function
With Symfony Flex, all I get is Unable to register extension "App\TwigExtension\TwigRoutingExtension" as it is already registered.
This is how to override Twig's routing path/url functions in Symfony 5.x using AbstractExtension:
namespace App\Twig;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class MyRoutingExtension extends AbstractExtension
{
public function __construct(
private UrlGeneratorInterface $generator
){}
public function getFunctions(): array
{
return [
new TwigFunction('path', [$this, 'getPath']),
new TwigFunction('url', [$this, 'getUrl']),
];
}
public function getPath(string $name, array $parameters = [], bool $relative = false): string
{
return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
}
public function getUrl(string $name, array $parameters = [], bool $schemeRelative = false): string
{
return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL);
}
}
In SF4 it's even simpler:
first composer require symfony/twig-bundle twig/extensions
Normally autowiring is enable so you can simply do:
<?php
namespace App\Twig;
use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class TestExtension extends RoutingExtension
{
public function __construct(UrlGeneratorInterface $generator)
{
parent::__construct($generator);
}
public function getPath($name, $parameters = array(), $relative = false)
{
return parent::getPath($name, $parameters, $relative);
}
}
If you want to setup a service forget about arguments definition it's boring :). Assuming your %domain% is available in your parameter do something like this:
<?php
namespace App\Twig;
use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class TestExtension extends RoutingExtension
{
/** #var RouterInterface $router */
protected $router;
/** #var RequestStack $stack */
protected $stack;
/** #var mixed $domain */
protected $domain;
/**
* TestExtension constructor.
*
* #param RouterInterface $router
* #param RequestStack $stack
* #param ParameterBagInterface $bag
*/
public function __construct(RouterInterface $router, RequestStack $stack, ParameterBagInterface $bag)
{
$this->router = $router;
$this->stack = $stack;
$this->domain = $bag->get('domain');
}
public function getPath($name, $parameters = array(), $relative = false)
{
return parent::getPath($name, $parameters, $relative);
}
}
I'm trying to show a menu of my Bundles, but I need show only the Bundles that are active, how can I get the active Bundles in Twig?
Thanks and Regards!
The list of bundle is stored in the kernel.
You have to create a twig extension BundleExtension and pass the kernel as dependency:
<?php
namespace MyBundle\Twig\Extension;
use Symfony\Component\HttpKernel\KernelInterface;
class BundleExtension extends \Twig_Extension
{
protected $kernel;
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
/**
* {#inheritdoc}
* #see Twig_Extension::getFunctions()
*/
public function getFunctions()
{
return array(
'getBundles' => new \Twig_SimpleFunction('getBundles', array($this, 'getBundles')),
);
}
public function getBundles()
{
return $this->kernel->getBundles();
}
/**
* {#inheritdoc}
* #see Twig_ExtensionInterface::getName()
*/
public function getName()
{
return 'get_bundles';
}
}
Register it as a service:
services:
bundle_extension:
class: MyBundle\Twig\Extension\BundleExtension
arguments: ['#kernel']
tags:
- { name: twig.extension }
And now in your twig template:
{% set bundles = getBundles() %}
{% for bundle in bundles %}
{{ bundle.getName()}}<br/>
{% endfor %}
I'm sure this is a common ask, I need softdeletable and similar filters off in SonataAdmin, until now I've been doing:
use Sonata\AdminBundle\Admin\Admin as BaseAdmin;
class Admin extends BaseAdmin
{
/**
* {#inheritdoc}
*/
public function configure()
{
/**
* This executes everywhere in the admin and disables softdelete for everything, if you need something cleverer this should be rethought.
*/
$filters = $this->getModelManager()->getEntityManager($this->getClass())->getFilters();
if (array_key_exists('approvable', $filters->getEnabledFilters())) {
$filters->disable('approvable');
}
if (array_key_exists('softdeleteable', $filters->getEnabledFilters())) {
$filters->disable('softdeleteable');
}
}
}
Which causes a number of problems, one, it needs the conditionals because the admin classes are configured twice, once to build the nav, and again to build interfaces, two, the admin classes are instantiated frontend on a cold (APC maybe?) cache, which is pretty uncool.
Where are you meant to put this logic?
You can use a Event Listener. For example:
Service:
filter.configurator:
class: AppBundle\Filter\Configurator
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_listener, event: kernel.controller }
Listener class:
namespace AppBundle\Filter;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\ORM\EntityManagerInterface;
use Sonata\AdminBundle\Controller\CoreController;
use Sonata\AdminBundle\Controller\CRUDController;
use Sonata\AdminBundle\Controller\HelperController;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* Class Configurator
*
* #author Andrey Nilov <nilov#glavweb.ru>
*/
class Configurator
{
/**
* #var Registry
*/
private $em;
/**
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* onKernelRequest
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
$controllerClass = $controller[0];
$isAdminController =
$controllerClass instanceof CRUDController ||
$controllerClass instanceof CoreController ||
$controllerClass instanceof HelperController
;
if ($isAdminController) {
$this->em->getFilters()->disable('softdeleteable');
}
}
}