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.
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 }
Is there a way in Twig to add a space where the word has camel case letters.
For instance: helloWorldHowAreYouDoing would be hello World How Are You Doing
Thanks!
From php version 5.5 the preg_replace_callback function is required as the "/e" modifier is now deprecated.
Create an extension method and call it from the twig page as a filter.
Extension Class:
<?php
// src/AppBundle/Twig/AppExtension.php
namespace AppBundle\Twig;
class yourExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('camelToSpace', array($this, 'convertCamelCaseToHaveSpacesFilter')),
);
}
/*
* Converts camel case string to have spaces
*/
public function convertCamelCaseToHaveSpacesFilter($camelCaseString)
{
$pattern = '/(([A-Z]{1}))/';
return preg_replace_callback(
$pattern,
function ($matches) {return " " .$matches[0];},
$camelCaseString
);
}
public function getName()
{
return 'app_extension';
}
}
Where the function convertCamelCaseToHaveSpacesFilter will do the work if you pass it a camel case string.
In Twig page:
<body>
{% set yourString = 'helloWorldHowAreYouDoing' %}
{{ yourString|camelToSpace }}
</body>
This should also work for Pascal casing but it may require triming the string afterwards.
If autowiring is not enabled, remember to register your extension class. In services.yml:
services:
app.twig_extension:
class: RouteToExtensionClass\yourExtension
public: false
tags:
- { name: twig.extension }
You should create Twig Extension and create a function to do that.
Something like this would do it..
/**
* Convert under_score to title Case
*
* #param $underscore
* #return string
*/
public static function convertUnderscoreToTitleCase($underscore)
{
return preg_replace('/(?:^|_)(.?)/e', "strtoupper(' $1')", $underscore);
}
It's not there out of the box. You can create twig extension to do that:
http://symfony.com/doc/current/cookbook/templating/twig_extension.html
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 one class which extends \Twig_Extension like below :
class MYTwigExtension extends \Twig_Extension
{
protected $doctrine;
protected $router;
public function __construct(RegistryInterface $doctrine , $router)
{
$this->doctrine = $doctrine;
$this->router = $router;
}
public function auth_links($user , $request)
{
// Some other codes here ...
// HOW TO GENERATE $iconlink which is like '/path/to/an/image'
$html .= "<img src=\"$iconlink\" alt=\"\" /> ";
echo $html;
}
}
My question is How to generate Asset links in a Twig Extension ? I would like a replacement for ASSET helper in my class. Bassically I have no idea what I have to inject or use here ! Thanks in advance.
<img src="{{ asset('img/icons/modules/timesheet.png') }}" alt="" />
You can use the templating.helper.assets service directly.
use Symfony\Component\DependencyInjection\ContainerInterface;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
and use it like so:
$this->container->get('templating.helper.assets')->getUrl($iconlink);
Injecting just the templating.helper.assets directly does not work in this case because the twig extension cannot be in the request scope. See the documentation here: https://symfony.com/doc/2.3/cookbook/service_container/scopes.html#using-a-service-from-a-narrower-scope
I didn't want to deal with the Dependency Injection Container. This is what I did:
use Twig_Environment as Environment;
class MyTwigExtension extends \Twig_Extension
{
protected $twig;
protected $assetFunction;
public function initRuntime(Environment $twig)
{
$this->twig = $twig;
}
protected function asset($asset)
{
if (empty($this->assetFunction)) {
$this->assetFunction = $this->twig->getFunction('asset')->getCallable();
}
return call_user_func($this->assetFunction, $asset);
}
I've looked at Twig_Extension class code, and found this initRuntime method there, to be overriden in our custom Extension class. It receives the Twig_Environment as an argument! This object has a getFunction method, which returns a Twig_Function instance. We only need to pass the function name (asset, in our case).
The Twig_Function object has a getCallable method, so we finally can have a callable asset function.
I've gone a bit further creating an asset method for my own extension class. Anywhere else on it, I can simply call $this->asset() and obtain the same result as {{ asset() }} in the templates.
EDIT: The getFunction call at initRuntime throws a scope exception when clearing the cache. So I moved it to the custom asset method. It works fine.
Here's a simple and clean way for Symfony 2.8:
services.yml:
app.twig_extension:
class: Path\To\AcmeExtension
arguments:
assets: "#templating.helper.assets"
In the TWIG extension:
use Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper;
class AcmeExtension
{
protected $assets;
public function __construct(AssetsHelper $assets)
{
$this->assets = $assets;
}
}
Then you can use it in any function of the extension like this:
$this->assets->getUrl('myurl');
In Symfony 5.3 that worked for me:
(just do what the assets extension does and inject Packages)
use Symfony\Component\Asset\Packages;
use Twig\Extension\AbstractExtension;
use Twig\Extension\ExtensionInterface;
class AppExtension extends AbstractExtension implements ExtensionInterface
{
public function __construct(Packages $packages)
{
$this->packages = $packages;
}
// ... your other methods
private function asset($path, $packageName = null)
{
return $this->packages->getUrl($path, $packageName);
}
}
In Symfony 2.8 that works for me:
# services.yml
services:
app.twig_extension:
class: AppBundle\Twig\AppTwigExtension
public: false
arguments:
- #templating.helper.assets
tags:
- { name: twig.extension }
AppTwigExtension class:
namespace AppBundle\Twig;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper;
/**
* Class AppTwigExtension
* #package AppBundle\Twig
*/
class AppTwigExtension extends \Twig_Extension
{
const IMG_PATH = 'bundles/app/images/';
private $assetsHelper;
public function __construct(AssetsHelper $assetsHelper)
{
$this->assetsHelper = $assetsHelper;
}
public function getFilters()
{
return array(
new \Twig_SimpleFilter('img', array($this, 'imagePathFilter'))
);
}
/**
* Get image path relatively to host
* Usage in Twig template: {{ 'my_image.png'|img }} - equal to
* {{ asset('bundles/app/images/my_image.png') }} in Twig template:
*
* #param string $imageName (e.g. my_image.png)
* #return string
*/
public function imagePathFilter($imageName)
{
return $this->assetsHelper->getUrl(self::IMG_PATH . $imageName);
}
public function getName()
{
return 'app_twig_extension';
}
}
I've a mixed array like this one (mobile numbers and entities):
$targets = array();
$targets[] = '+32647651212';
$targets[] = new Customer();
In my Twig template i have to call getMobile() if target is a Customer or just print the number if it's actually a number (string).
Is there something like instanceof operator in Twig?
<ul>
{% for target in targets %}
<li>{{ target instance of MyEntity ? target.getMobile : target }}</li>
{% else %}
<li>Nothing found.</li>
</ul>
In \Twig_Extension you can add tests
public function getTests()
{
return [
'instanceof' => new \Twig_Function_Method($this, 'isInstanceof')
];
}
/**
* #param $var
* #param $instance
* #return bool
*/
public function isInstanceof($var, $instance) {
return $var instanceof $instance;
}
And then use like
{% if value is instanceof('DateTime') %}
UPDATE 10-2021
Please be aware this answer has been written for symfony 3, and twig 2.
If you use a more recent version, please refer to the answer of #Garri Figueroa on this post.
As you can see in the twig documentation the class \Twig_Extension, \Twig_SimpleTest are now deprecated.
If you use a more recent version of symfony (I recommend it), please use the new class AbstractExtension, TwigFunction, etc
https://symfony.com/doc/5.3/templating/twig_extension.html
OLD VERSION : symfony 3.4
Here a nice way to do instanceof operator in twig with Extension :
1) Create your extention file where you want
(ex: src/OC/YourBundle/Twig/InstanceOfExtension.php )
With \Twig_Extension you can do many things, filter, fonction, but now we will create a Test.
So we implement function getTests(), and inside it we create a new \Twig_SimpleTest
The 1st arugment is the name of test you create, and the seconde a callable.
(can be a function() {}).
<?php
namespace OC\YourBundle\Twig;
class InstanceOfExtension extends \Twig_Extension {
public function getTests() {
return array(
new \Twig_SimpleTest('instanceof', array($this, 'isInstanceOf')),
);
}
public function isInstanceOf($var, $instance) {
$reflexionClass = new \ReflectionClass($instance);
return $reflexionClass->isInstance($var);
}
}
2) Register it in services.yml
(ex: src/OC/YourBundle/Resources/config/services.yml)
services:
[...may you have other services ...]
app.twig_extension:
class: OC\YourBundle\Twig\InstanceOfExtension
public: false
tags:
- { name: twig.extension }
3) Then use it in twig like this
{{ myvar is instanceof('\\OC\\YourBundle\\Entity\\YourEntityOrWhatEver') }}
Source from Adrien Brault => https://gist.github.com/adrienbrault/7045544
My solution for Symfony 4.3
1) Create the AppExtension class in src/Twig folder. (The class is automatically detected).
2) Extend the AbstractExtension class:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigTest;
class AppExtension extends AbstractExtension
{
public function getTests()
{
return [
new TwigTest('instanceof', [$this, 'isInstanceof'])
];
}
/**
* #param $var
* #param $instance
* #return bool
*/
public function isInstanceof($var, $instance) {
return $var instanceof $instance;
}
}
3) Then use same code of valdas.mistolis answer:
{% if value is instanceof('DateTime') %}
4) Thanks valdas.mistolis and symfony documentation i got my own solution:
Twig Extension templating
Since PHP 5.5.0 you can compare class names next way:
{{ constant('class', exception) is constant('\\Symfony\\Component\\HttpKernel\\Exception\\HttpException') }}
This snippet can help in particular cases when you need strict comparison of class names. If you need to check implementation of interface or to check inheritance would be better to create twig extension described above.
Another solution :
class A {
...
public function isInstanceOfB() {
return $this instanceof B;
}
}
class B extends A {}
class C extends A {}
then in your twig :
{{ a.isInstanceOfB ? ... something for B instance ... : ... something for C instance ... }}
OR
{% if a.isInstanceOfB %}
... do something for B instance ...
{% else %}
... do something for C instance ...
{% endif %}
Another example when iterating through Symfony forms:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigTest;
use App\Document\Embeded\Image;
use App\Document\Embeded\Gallery;
use App\Document\Embeded\Article;
class AppExtension extends AbstractExtension
{
public function getTests()
{
return [
new TwigTest('image', [$this, 'isImage']),
new TwigTest('gallery', [$this, 'isGallery']),
new TwigTest('article', [$this, 'isArticle']),
];
}
public function isImage($var) {
return $var instanceof Image;
}
public function isGallery($var) {
return $var instanceof Gallery;
}
public function isArticle($var) {
return $var instanceof Article;
}
}
Twig
{% if form.vars.data is gallery %}
This is a Gallery
{% elseif form.vars.data is article %}
This is an Article
{% endif %}
Other solution without ReflectionClass with a twig filter :
First you need a TwigExtension class. Then, add a function as twig filter or twig function,
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class TwigExtension extends AbstractExtension
/**
* #return array|\Twig_Filter[]
*/
public function getFilters()
{
return [
new TwigFilter('is_instance_of', [$this, 'isInstanceOf'])
];
}
/**
* #param $object
* #param $class
* #return bool
*/
public function isInstanceOf($object, $class): bool
{
return is_a($object, $class, true);
}
}
And in twig template :
{% if true is same as (object|is_instance_of('ClassName')) %}
// do some stuff
{% endif %}
Check http://php.net/manual/fr/function.is-a.php
Use default filter in Twig like this:
{{ target.mobile|default(target) }}
Quite old, but I can't see here one more good possibility to achive this:
Enity:
public function __toString()
{
return 'NameOfYourEntity';
}
Twig:
{{ entity }}