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
Related
I'm using the {{ dump(foo) }} function in Twig to debug my templates. However, if the template is throwing errors after the dump() function, you will only see Symfony's debugging page informing you of the error. You can obviously comment out the offending lines of code in the Twig template, but is there a way to kill the execution of the template immediately after so that the output of the dump() function is the last thing printed on the screen. Naively I'm thinking of something like {{ dump(foo) }} {{ die() }}. Any ideas on how you could achieve this?
You could create a simple twig extension that handled this.
Your twig file..
namespace Acme\SomeBundle\Twig;
class DevExtension extends \Twig_Extension
{
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('die', 'die'),
);
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'acme_dev';
}
}
Your services file (YAML)..
services:
acme.twig.dev_extension:
class: Acme\SomeBundle\Twig\DevExtension
tags:
- { name: twig.extension }
Additionally you could pass in the current environment and then either die or fail silently depending on the environment in case you have left the die in your code for some reason.
Your twig extension..
class DevExtension extends \Twig_Extension
{
/**
* #string
*/
private $environment;
/**
* #param string $environment
*/
public function __construct($environment)
{
$this->environment = $environment;
}
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('die', array($this,'killRender')),
);
}
/**
* #param string|null $message
*/
public function killRender($message = null)
{
if ('dev' === $this->environment) {
die($message);
}
return '';
}
...
}
Your services file..
services:
acme.twig.dev_extension:
class: Acme\SomeBundle\Twig\DevExtension
arguments:
- %kernel.environment%
tags:
- { name: twig.extension }
I don't think you should stop PHP execution inside your twig template (even though this is possible using a custom Twig extension). The result would not be what you'd expect because there is a lot more happening between rendering your template and sending it to the browser. If you simply stop execution all this will not happen any more and I'd suspect that you'll get a simple white page.
Perhaps it's a better approach to dump the variable inside the controller. Doing that will send the dump output to the web profiler toolbar which is available even on symfony's error page.
Oh and well, what about just using a comment ({# ... #}) to disable the non-working part of your template?
I want to create event listener that add some results of db query to all symfony actions
for example:
class BlogController extends Controller
{
/**
* #Route("/blog/")
* #Template()
*/
public function indexAction()
{
....
return array(
'entries' => $posts
);
}
}
This controller is passing entries variable to the view, I want to create listener that take the returned value of all actions and inject another index to the returned array to be (for example)
array(
'entries' => $posts,
'categories' => $categories
);
so I can call the $categories var from any where in my application views
I hope my question is clear to you guys. Thanks in advance.
You should consider creating a global variable or twig extension to make categories available in your templates, you can't do that by using events (since the template is parsed inside the controller, not before/after it)
This approach, although valid and commonly used in some frameworks, is not very common in Symfony as it suits more MVC than HMVC architecture.
I would suggest you a different one with the same result:
Instead of adding parameter to every controller return, render another controller which returns just a subview of what you're trying to show. Simple example:
// article/index.html.twig
<div class="category-bar">{{ render(controller('MyVendorMyBundle:CategoryController:bar')) }}</div>
<div class="article-list">
{% for article in articles %>
{# Print article here #}
{% endfor %}
</div>
// CategoryController
class CategoryController extends Controller
{
/**
* #Template
*/
public function barAction()
{
return ['categories' => $this->fetchCategoriesSomehow()];
}
}
So when you render your article list action, twig will fire a subrequest to render categories bar above it.
Furthermore, if you don't like making subrequests, nothing stops you from creating a twig extension service which would fetch categories and render template for you.
In most cases I would go with #Wouter J's suggestion and create a twig extension or a global variable.
However, what you want to do is actually possible (regardless if that's the right solution or not).
The #Template annotation has a vars attribute, which lets you to specify which atttributes from the request should be passed to the template:
/**
* #ParamConverter("post", class="SensioBlogBundle:Post")
* #Template("SensioBlogBundle:Post:show.html.twig", vars={"post"})
*/
public function showAction()
{
}
Note, that request attributes can be set by you:
$request->attributes->set('categories', []);
So, you could implement a listener which would set the categories attribute on the request and than configure the vars on the #Template annotation:
/**
* #Template("SensioBlogBundle:Post:show.html.twig", vars={"categories"})
*/
public function showAction(Post $post)
{
}
Have a look at the TemplateListener from the SensioFrameworkExtraBundle for more insight. The listener defines template vars on kernel.controller and uses them to render the view on kernel.view.
You could avoid defining vars on the annotation if your listener was registered after the TemplateListener::onController(). It would have to add categories to the _template_vars request attribute.
Use Twig extension to create function that will return list of available categories
<?php
class CategoriesExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('getCategories', [$this, 'getCategoriesList'])
];
}
/**
* #return null|string
*/
public function getCategoriesList()
{
return CategoryQuery::create()->find();
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'list_categories';
}
}
You can pass parameter to function if You would like do some conditions on query.
The trick is to get the twig service in your listener and then use addGlobal to add your categories
namespace Cerad\Bundle\CoreBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class MyEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array(
array('doCategories', -1100),
);
}
public function doCategories(FilterControllerEvent $eventx)
{
// Query your categories
$categories = array('cat1','cat2');
// Make them available to all twig templates
$twig = $this->container->get('twig');
$twig->addGlobal('categories',$categories);
}
# services.yml
cerad_core__my__event_listener:
class: '%cerad_core__my__event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
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.
I'm using Symfony 2.0.19. I'm trying to create a hyperlink to an external URL, which is retrieved from a database.
I tried doing this
<td>{{dominio.url}}</td>
but the path I get is a relative path to the URL inside the base URL example "localhost/web/www.tralalalala.com" instead of just "www.tralalalala.com".
How do I do this?
Here's a concrete example of what Pierrickouw is suggesting:
Create a Twig extension or filter under src/Twig, and call it for example ExternalLinkFilter. Define the class as follows:
<?php
namespace AppBundle\Twig;
class ExternalLinkFilter extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('external_link', array($this, 'externalLinkFilter')),
);
}
/* source: http://stackoverflow.com/a/2762083/3924118 */
public function externalLinkFilter($url)
{
if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {
$url = "http://" . $url;
}
return $url;
}
public function getName()
{
return 'external_link_filter';
}
}
?>
Now, you should register this class as a service in config/services.yml as follows:
services:
# other services
app.twig.external_link:
class: AppBundle\Twig\ExternalLinkFilter
public: false
tags:
- { name: twig.extension }
Now you can simply use the filter called external_link as you would use any Twig's default one, e.g.:
...
{{check.hostname}}
...
I suggest you to build your own Twig filter.
If your url aldready have http://, don't add it, otherwise, add it.
Check here for how-tos.
A symfony 5 version of nbro's answer will be:
(This will take care of all kind of url that is read from the db)
source: https://symfony.com/doc/current/templating/twig_extension.html
in src/Twig create the file
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class ExternalLinkFilter extends AbstractExtension
{
public function getFilters()
{
return array(
new TwigFilter('external_link', array($this, 'externalLinkFilter')),
);
}
/* source: http://stackoverflow.com/a/2762083/3924118 */
public function externalLinkFilter($url)
{
if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {
$url = "http://" . $url;
}
return $url;
}
public function getName()
{
return 'external_link_filter';
}
}
?>
You don't need to register it as a service. Symfony 5 will notice the presence of that Twig extension
Use it in twig template as suggested by #nbro
{{check.hostname}}