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 }
Related
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 }
I would like to change the default behaviour of the #Template annotation which automatically renders the template named as the controller action.
So in an ArticleController.php
/**
* #Route("/new", name="article_new")
* #Method("GET")
* #Template()
*/
public function newAction()
{
// ...
return array();
}
would render Article/new.html.twig.
I want to change this to referr to the name of the route the action was called with so you could have multiple routes for an action each rendering a different template.
This is the way I currently do it (without #Template):
/**
* #Route("/new", name="article_new")
* #Route("/new_ajax", name="article_new_ajax")
* #Method("GET")
*/
public function newAction()
{
// ...
$request = $this->getRequest();
$route = $request->attributes->get('_route');
$template = 'AcmeDemoBundle:' . $route . '.html.twig';
return $this->render($template, array(
// ...
));
}
I wonder now if there is a way to change the behaviour of #Template to do exactly that. Is there a way to customize the annotations or just some aproach to make it more automated?
Any ideas?
I have now found a solution using the kernelView event. This is independet of the #Template annotation. The kernelView event fires whenever a controller action doesn't return a response object.
(This solution is based on Symfony 2.4)
event listener service:
services:
kernel.listener.route_view:
class: Acme\DemoBundle\Templating\RouteView
arguments: ["#request_stack", "#templating"]
tags:
- { name: kernel.event_listener, event: kernel.view }
event listener class:
namespace Acme\DemoBundle\Templating;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
class RouteView
{
protected $controller;
protected $route;
protected $templating;
function __construct(RequestStack $requestStack, $templating)
{
$this->controller = $requestStack->getCurrentRequest()->attributes->get('_controller');
$this->route = $requestStack->getCurrentRequest()->attributes->get('_route');
$this->templating = $templating;
}
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$controllerAction = substr($this->controller, strrpos($this->controller, '\\') + 1);
$controller = str_replace('Controller', '', substr($controllerAction, 0, strpos($controllerAction, '::')));
$template = 'AcmeDemoBundle:' . $controller . ':' . str_replace(strtolower($controller) . '_', '', $this->route) . '.html.twig';
$response = $this->templating->renderResponse($template, $event->getControllerResult());
$event->setResponse($response);
}
}
Now the controller behaves like this:
/**
* #Route("/new", name="article_new") -> Article:new.html.twig
* #Route("/new_ajax", name="article_new_ajax") -> Article:new_ajax.html.twig
* #Method("GET")
*/
public function newAction()
{
// ...
return array();
}
FOSRestBundle includes similar functionality to #Template but on class-level since my pull request if you use the #View annotation on class-level.
This can be useful if want to your template-filenames to reflect the action-names but not the route-names ( as opposed to what was asked for in the question ).
The rendered template will be i.e. ...
<controller-name>/<action-name>.html.twig
... for HTML views.
Example: AcmeBundle\Controller\PersonController::create() will render
AcmeBundle/Resources/views/Person/create.html.twig
Before the PR you had to annotate every method.
Annotating a method still gives the possibility to override template,template-variable and status-code though.
example:
/**
* #FOSRest\View(templateVar="testdata", statusCode=201)
*/
class PersonController implements ClassResourceInterface
{
public function newAction()
{
return $this->formHandler->createForm();
// template: Person/new.html.twig
// template variable is 'form'
// http status: 201
}
public function helloAction()
{
return "hello";
// template: Person/hello.html.twig
// template variable 'testdata'
// http status: 201
}
/**
* #FOSRest\View("AnotherBundle:Person:get", templatevar="person")
*/
public function getAction(Person $person)
{
return $person;
// template: AnotherBundle:Person:get
// template variable is 'person'
// http status: 201
}
/**
* #FOSRest\View("AnotherBundle:Person:overview", templatevar="persons", statusCode=200)
*/
public function cgetAction()
{
return $this->personManager->findAll();
// template: AnotherBundle:Person:overview
// template variable is 'persons'
// http status: 200
}
// ...
I am using sonata media bundle.
and I was wondering how can I access the media url in twig.
I just want the url, I do not need to show the media.
Any suggestions?
You have to use the path media helper:
{% path media, 'small' %}
In the above code, media is an instance of the media entity, and small is the chosen format.
http://sonata-project.org/bundles/media/master/doc/reference/helpers.html#twig-usage
But if you do not want to render the media right there and just store the url in a variable, you need to ask the media provider for the public url.
This was my case, that I needed to pass the url to another template.
I did it creating a custom function in my Twig Extension (see here: http://symfony.com/doc/current/cookbook/templating/twig_extension.html).
Provided that you have the container available in your extension service with $this->container, you can do like this:
public function getMediaPublicUrl($media, $format)
{
$provider = $this->container->get($media->getProviderName());
return $provider->generatePublicUrl($media, $format);
}
Register the function in the extension:
public function getFunctions() {
....
'media_public_url' => new \Twig_Function_Method($this, 'getMediaPublicUrl'),
....
);
}
And call your new helper form your template:
{% set img_url = media_public_url(media, 'small') %}
for instance
regards
#javigzz's is perfect in case of default context. I used custom context, so had to handle $format first taking into account context name:
$provider = $this->container->get($media->getProviderName());
$format = $provider->getFormatName($media, $format);
$url = $provider->generatePublicUrl($media, $format);
Additional Note
Since injecting container is not the best practice, it is better to get provider from the provider pool:
class Foo {
public function __construct(Sonata\MediaBundle\Provider\Pool $pool) {
$this->pool = $pool;
}
public function getUrl($media, $format) {
$provider = $this->pool->getProvider($media->getProviderName());
$format = $provider->getFormatName($media, $format);
$url = $provider->generatePublicUrl($media, $format);
return $url;
}
}
Since #javigzz's answer did not work for me, here is a twig extension that works with the latest version of sonata_media:
namespace Socialbit\SonataMediaTwigExtensionBundle\Twig;
use Sonata\CoreBundle\Model\ManagerInterface;
use Symfony\Component\DependencyInjection\Container;
Class:
/**
* Description of MediaPathExtension
*
* #author thomas.kekeisen
*/
class MediaPathExtension extends \Twig_Extension
{
/**
*
* #var type Container
*/
protected $container;
/**
*
* #var type ManagerInterface
*/
protected $mediaManager;
public function __construct(Container $container, $mediaManager)
{
$this->container = $container;
$this->mediaManager = $mediaManager;
}
public function getFunctions()
{
return array
(
'media_public_url' => new \Twig_Function_Method($this, 'getMediaPublicUrl')
);
}
/**
* #param mixed $media
*
* #return null|\Sonata\MediaBundle\Model\MediaInterface
*/
private function getMedia($media)
{
$media = $this->mediaManager->findOneBy(array(
'id' => $media
));
return $media;
}
public function getMediaPublicUrl($media, $format)
{
$media = $this->getMedia($media);
$provider = $this->container->get($media->getProviderName());
return $provider->generatePublicUrl($media, $format);
}
public function getName()
{
return 'SocialbitSonataMediaTwigExtensionBundleMediaPathExtension';
}
}
services.yml:
services:
socialbit.sonatamediatwigextensionbundle.mediapathextension:
class: Socialbit\SonataMediaTwigExtensionBundle\Twig\MediaPathExtension
public: false
arguments:
- #service_container
- #sonata.media.manager.media
tags:
- { name: twig.extension }
The usage will be the same:
{% set img_url = media_public_url(media, 'reference') %}
{{ dump(img_url) }}
You can use: {% path media, 'reference' %}
#Blauesocke - tried your solution and had exactly the same result for file provider with using both
{% set img_url = media_public_url(media, 'reference') %}
{{ dump(img_url) }}
and
{% path sonata_admin.value, 'reference' %}
I have a loop in Twig template, which returns multiple values. Most important - an ID of my entry. When I didn't use any framework nor template engine, I used simply file_exists() within the loop. Now, I can't seem to find a way to do it in Twig.
When I display user's avatar in header, I use file_exists() in controller, but I do it because I don't have a loop.
I tried defined in Twig, but it doesn't help me. Any ideas?
If you want want to check the existence of a file which is not a twig template (so defined can't work), create a TwigExtension service and add file_exists() function to twig:
src/AppBundle/Twig/Extension/TwigExtension.php
<?php
namespace AppBundle\Twig\Extension;
class FileExtension extends \Twig_Extension
{
/**
* Return the functions registered as twig extensions
*
* #return array
*/
public function getFunctions()
{
return array(
new Twig_SimpleFunction('file_exists', 'file_exists'),
);
}
public function getName()
{
return 'app_file';
}
}
?>
Register your service:
src/AppBundle/Resources/config/services.yml
# ...
parameters:
app.file.twig.extension.class: AppBundle\Twig\Extension\FileExtension
services:
app.file.twig.extension:
class: %app.file.twig.extension.class%
tags:
- { name: twig.extension }
That's it, now you are able to use file_exists() inside a twig template ;)
Some template.twig:
{% if file_exists('/home/sybio/www/website/picture.jpg') %}
The picture exists !
{% else %}
Nope, Chuck testa !
{% endif %}
EDIT to answer your comment:
To use file_exists(), you need to specify the absolute path of the file, so you need the web directory absolute path, to do this give access to the webpath in your twig templates
app/config/config.yml:
# ...
twig:
globals:
web_path: %web_path%
parameters:
web_path: %kernel.root_dir%/../web
Now you can get the full physical path to the file inside a twig template:
{# Display: /home/sybio/www/website/web/img/games/3.jpg #}
{{ web_path~asset('img/games/'~item.getGame.id~'.jpg') }}
So you'll be able to check if the file exists:
{% if file_exists(web_path~asset('img/games/'~item.getGame.id~'.jpg')) %}
I've created a Twig function which is an extension of the answers I have found on this topic. My asset_if function takes two parameters: the first one is the path for the asset to display. The second parameter is the fallback asset, if the first asset does not exist.
Create your extension file:
src/Showdates/FrontendBundle/Twig/Extension/ConditionalAssetExtension.php:
<?php
namespace Showdates\FrontendBundle\Twig\Extension;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ConditionalAssetExtension extends \Twig_Extension
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Returns a list of functions to add to the existing list.
*
* #return array An array of functions
*/
public function getFunctions()
{
return array(
'asset_if' => new \Twig_Function_Method($this, 'asset_if'),
);
}
/**
* Get the path to an asset. If it does not exist, return the path to the
* fallback path.
*
* #param string $path the path to the asset to display
* #param string $fallbackPath the path to the asset to return in case asset $path does not exist
* #return string path
*/
public function asset_if($path, $fallbackPath)
{
// Define the path to look for
$pathToCheck = realpath($this->container->get('kernel')->getRootDir() . '/../web/') . '/' . $path;
// If the path does not exist, return the fallback image
if (!file_exists($pathToCheck))
{
return $this->container->get('templating.helper.assets')->getUrl($fallbackPath);
}
// Return the real image
return $this->container->get('templating.helper.assets')->getUrl($path);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'asset_if';
}
}
Register your service (app/config/config.yml or src/App/YourBundle/Resources/services.yml):
services:
showdates.twig.asset_if_extension:
class: Showdates\FrontendBundle\Twig\Extension\ConditionalAssetExtension
arguments: ['#service_container']
tags:
- { name: twig.extension }
Now use it in your templates like this:
<img src="{{ asset_if('some/path/avatar_' ~ app.user.id, 'assets/default_avatar.png') }}" />
I've had the same problem as Tomek. I've used Sybio's solution and made the following changes:
app/config.yml => add "/" at the end of web_path
parameters:
web_path: %kernel.root_dir%/../web/
Call file_exists without "asset" :
{% if file_exists(web_path ~ 'img/games/'~item.getGame.id~'.jpg') %}
Hope this helps.
Here is my solution, using SF4, autowire and autoconfigure:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Symfony\Component\Filesystem\Filesystem;
class FileExistsExtension extends AbstractExtension
{
private $fileSystem;
private $projectDir;
public function __construct(Filesystem $fileSystem, string $projectDir)
{
$this->fileSystem = $fileSystem;
$this->projectDir = $projectDir;
}
public function getFunctions(): array
{
return [
new TwigFunction('file_exists', [$this, 'fileExists']),
];
}
/**
* #param string An absolute or relative to public folder path
*
* #return bool True if file exists, false otherwise
*/
public function fileExists(string $path): bool
{
if (!$this->fileSystem->isAbsolutePath($path)) {
$path = "{$this->projectDir}/public/{$path}";
}
return $this->fileSystem->exists($path);
}
}
In services.yaml:
services:
App\Twig\FileExistsExtension:
$projectDir: '%kernel.project_dir%'
In templates:
# Absolute path
{% if file_exists('/tmp') %}
# Relative to public folder path
{% if file_exists('tmp') %}
I am new to Symfony so every comments are welcome!
Also, as initial question is about Symfony 2, maybe my answer is not relevant and I would better ask a new question and answer by myself?
Improving on Sybio's answer, Twig_simple_function did not exist for my version and nothing here works for external images for example. So my File extension file is like this:
namespace AppBundle\Twig\Extension;
class FileExtension extends \Twig_Extension
{
/**
* {#inheritdoc}
*/
public function getName()
{
return 'file';
}
public function getFunctions()
{
return array(
new \Twig_Function('checkUrl', array($this, 'checkUrl')),
);
}
public function checkUrl($url)
{
$headers=get_headers($url);
return stripos($headers[0], "200 OK")?true:false;
}
Just add a little comment to the contribution of Sybio:
The Twig_Function_Function class is deprecated since version 1.12 and
will be removed in 2.0. Use Twig_SimpleFunction instead.
We must change the class Twig_Function_Function by Twig_SimpleFunction:
<?php
namespace Gooandgoo\CoreBundle\Services\Extension;
class TwigExtension extends \Twig_Extension
{
/**
* Return the functions registered as twig extensions
*
* #return array
*/
public function getFunctions()
{
return array(
#'file_exists' => new \Twig_Function_Function('file_exists'), // Old class
'file_exists' => new \Twig_SimpleFunction('file_exists', 'file_exists'), // New class
);
}
public function getName()
{
return 'twig_extension';
}
}
The rest of code still works exactly as said Sybio.
For instance my bundle namespace is Facebook\Bundle\FacebookBundle\Extension.
Using this how can I create a twig extension ?
It's all here: How to write a custom Twig Extension.
1. Create the Extension:
// src/Facebook/Bundle/Twig/FacebookExtension.php
namespace Facebook\Bundle\Twig;
use Twig_Extension;
use Twig_Filter_Method;
class FacebookExtension extends Twig_Extension
{
public function getFilters()
{
return array(
'myfilter' => new Twig_Filter_Method($this, 'myFilter'),
);
}
public function myFilter($arg1, $arg2='')
{
return sprintf('something %s %s', $arg1, $arg2);
}
public function getName()
{
return 'facebook_extension';
}
}
2. Register an Extension as a Service
# src/Facebook/Bundle/Resources/config/services.yml
services:
facebook.twig.facebook_extension:
class: Facebook\Bundle\Twig\AcmeExtension
tags:
- { name: twig.extension }
3. Use it
{{ 'blah'|myfilter('somearg') }}
You can also create twig functions by using the getFunctions()
class FacebookExtension extends Twig_Extension
{
public function getFunctions()
{
return array(
'myFunction' => new Twig_Filter_Method($this, 'myFunction'),
);
}
public function myFunction($arg1)
{
return $arg1;
}
Use your function like this:
{{ myFunction('my_param') }}
The Twig_Filter_Method class is DEPRECATED since Symfony 2.1
Please use the Twig_SimpleFilter class instead as showed in the following example:
\src\Acme\Bundle\CoreBundle\Twig\DatetimeExtension.php
<?php
namespace Acme\Bundle\CoreBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DatetimeExtension extends \Twig_Extension
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFilters()
{
return array(
'dateFormat' => new \Twig_SimpleFilter('dateFormat', array($this, 'dateFormat')),
'datetimeFormat' => new \Twig_SimpleFilter('datetimeFormat', array($this, 'datetimeFormat'))
);
}
/**
* #param mixed $date
* #return string
*/
public function dateFormat($date)
{
$format = $this->container->getParameter('acme_core.date_format');
return $this->format($date, $format);
}
/**
* #param mixed $date
* #return string
*/
public function datetimeFormat($date)
{
$format = $this->container->getParameter('acme_core.datetime_format');
return $this->format($date, $format);
}
/**
* #param mixed $date
* #param string $format
* #throws \Twig_Error
* #return string
*/
private function format($date, $format)
{
if (is_int($date) || (is_string($date) && preg_match('/^[0-9]+$/iu', $date))) {
return date($format, intval($date, 10));
} else if (is_string($date) && !preg_match('/^[0-9]+$/', $date)) {
return date($format, strtotime($date));
} else if ($date instanceof \DateTime) {
return $date->format($format);
} else {
throw new \Twig_Error('Date or datetime parameter not valid');
}
}
public function getName()
{
return 'datetime_extension';
}
}
\src\Acme\Bundle\CoreBundle\Resources\config\services.yml
services:
acme_core.twig.datetime_extension:
class: Acme\Bundle\CoreBundle\Twig\DatetimeExtension
arguments: [#service_container]
tags:
- { name: twig.extension }
Usage example:
{{ value|datetimeFormat }}
Symfony documentation: http://symfony.com/doc/master/cookbook/templating/twig_extension.html
Twig documentation: http://twig.sensiolabs.org/doc/advanced.html#id3
None of the given answers worked for Symfony 3.4 and above.
For Symfony 3.4, 4.x
// src/TwigExtension/customFilters.php
namespace App\TwigExtension;
use Twig\TwigFilter;
class customFilters extends \Twig_Extension {
public function getFilters() {
return array(
new TwigFilter('base64_encode', array($this, 'base64_en'))
);
}
public function base64_en($input) {
return base64_encode($input);
}
}
And then in your twig template you can do
{{ 'hello world' | base64_encode }}
Thats it. For detailed explanation, you could check out the reference.
Reference: DigitalFortress