I want to add my own twig functionality and to add new twig extension in Symfony 2.
To do that i created these folders: src/Ptracker/TasksBundle/Twig and src/Ptracker/TasksBundle/Twig/Extension and put file myTwigExtension.php in it with this content:
<?php
namespace Ptracker\TasksBundle\Twig\Extension;
class MyTwigExtension extends \Twig_Extension {
public function getFilters() {
return array(
'var_dump' => new \Twig_Filter_Function('var_dump'),
'linkable' => new \Twig_Filter_Method($this, 'linkable'),
);
}
public function linkable($sentence, $expr) {
return 'it works!!';
}
public function getName()
{
return 'my_twig_extension';
}
}
?>
Also i added some code to src/Ptracker/TasksBundle/Resources/config/services.yml :
services:
ptracker.twig.extension:
class: Ptracker\TasksBundle\Twig\Extension\MyTwigExtension
tags:
- { name: twig.extension }
The point is that i ALWAYS get the same fatal error:
Fatal error: Class 'Ptracker\TasksBundle\Twig\Extension\MyTwigExtension' not found in /home/renat/www/ptracker/app/cache/dev/appDevDebugProjectContainer.php on line 1092
What am i doing wrong? I've spent several ours to fix this problem, tried to put extension file in different folders, changed namespace.. nothing helps.
Please help me :)
File names are case-sensitive on linux and it doesn't find anything because it tries to load ../MyTwigExtension.php. Rename your file to MyTwigExtension.php.
Related
I'm adding a custom twig extension service to a Drupal 8 module. My services file looks like this:
services:
analytics.my_twig_extension:
class: Drupal\analytics\TwigExtension\MyTwigExtension
tags:
- { name: twig.extension }
I get this error when running drush cr:
[warning] Drush command terminated abnormally. Check for an exit()
in your Drupal site.
When I remove the tags property in services file, like this:
services:
analytics.my_twig_extension:
class: Drupal\analytics\TwigExtension\MyTwigExtension
then drush cr works correctly, but my Twig extension functions are not not running at all.
The MyTwigExtension class:
<?php
namespace Drupal\analytics\TwigExtension;
use Twig_Extension;
use Twig_SimpleFilter;
class MyTwigExtension extends \TwigExtension {
public function __construct() {
}
public function getFunctions() {
return [
new \Twig_SimpleFunction('get_type', array($this, 'getType'))
];
}
public function getType($var) {
return gettype($var);
}
}
?>
Does anyone have any idea why this is happening?
Jacob, you're a dummy.
I fixed it by simply using extends \Twig_Extension instead of extends \TwigExtension. A coworker found the answer. Unfortunately, there was no indication in the logs that this was the problem.
I am new with Symfony and I've searched a lot on the net how to use core PHP functions like array functions (in_array , array_combine) , string functions (strpos , strlen) , date functions (date,timestamp),etc.. In Symfony Twig file?
Firstly, create Twig/Extension directory into your bundle. Sample: AppBundle/Twig/Extension
Then, create a class for your function name. I create a JsonDecode and i use every twig file this function;
namespace AppBundle\Twig\Extension;
class JsonDecode extends \Twig_Extension {
public function getName() {
return 'twig.json_decode';
}
public function getFilters() {
return array(
'json_decode' => new \Twig_Filter_Method($this, 'jsonDecode')
);
}
public function jsonDecode($string) {
return json_decode($string);
}
}
Then,
add lines into services.yml;
twig.json_decode:
class: AppBundle\Twig\Extension\JsonDecode
tags:
- { twig.extension }
that's enough.
You have to create a custom twig extension that you could use in your twig template.
You can follow the symfony documentation
I want to create a simple twig extension ({{imgWidth(...)}}) that calls getimagesize() and returns the width and height of an image on the server.
I followed the instuctions you can find here.
When I reload my page I only can see a blank page - the error.log tells me that
PHP Fatal error: Class 'Fms\MediaBundle\Twig\Extension\ImgsizeExtension' not found in /var/www/fms/app/cache/dev/appDevDebugProjectContainer.php on line 4773
The service in MediaBundle\Resources\config\services.yml looks like:
services:
twig.extension.imgsize:
class: Fms\MediaBundle\Twig\Extension\ImgsizeExtension
tags:
- name: twig.extension
The class is:
<?
// src/Fms/MediaBundle/Twig/Extension/ImgsizeExtension.php
namespace Fms\MediaBundle\Twig\Extension;
class ImgsizeExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('imgsize', array($this, 'imgWidth'))
);
}
public function imgWidth($mediaId = 0, $mediaSize = 'L')
{
// ...
return $mediaId;
}
public function getName()
{
return 'imgsize';
}
}
Clearing cache via console or manually didnt help too.
Change <? to <?php. I copied your code and in with this modification symfony finally finds this class.
I need to populate a variable with some HTML code and make it available to my base.html.twig file.
To achive this I have made a twig extension. This is my first time using twig extentions so im not sure if this is the correct way of doing things.
Here is what I have so far:
Extension code:
class GlobalFooterExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_Filter_Function('GlobalFooter', array($this, 'GlobalFooter')),
);
}
public function GlobalFooter()
{
$GlobalFooter = file_get_contents('http://mysite.co.uk/footer/footer.html.twig');
return $GlobalFooter;
}
public function getName()
{
return 'GlobalFooter_extention';
}
}
config.yml:
services:
imagine.twig.GlobalFooterExtension:
class: Imagine\GdmBundle\Twig\GlobalFooterExtension
tags:
- { name: twig.extension }
base.html.twig:
{{GlobalFooter}}
This give the following error:
Twig_Error_Runtime: Variable "GlobalFooter" does not exist in "ImagineGdmBundle:Default:product.html.twig" at line 2
Im sure im missing something really obvious. How do I make $GlobalFooter from my GlobalFooterExtension class available to my base.hmtl.twig file?
You want to set a global variable, not a function.
Just use getGlobals and return your variable:
class GlobalFooterExtension extends \Twig_Extension
{
public function getGlobals()
{
return array(
"GlobalFooter" => file_get_contents('http://mysite.co.uk/footer/footer.html.twig'),
);
}
public function getName()
{
return 'GlobalFooter_extention';
}
}
Or, if you want to lazy load the value of the variable, create a function and change your template to:
{{ GlobalFooter() }}
Besides this, if the footer file is on the same site, it's better to use the {% include '...' %} tag.
rename function getFilters to getFunctions
I want to create a Twig extension and use this:
{{ new_func(route-name) }}
To do the same thing as:
{{ render_esi(url(route-name)) }}
...but with some adjustments
It's nearly done but it's this line that needs to be changed, but I can't see how I can call an ESI from this code (outside of Twig):
return $environment->render($route); /// needs to receive route and render an ESI
-
namespace Acme\Bundle\MyBundle\Twig;
class NewTwigFunction extends \Twig_Extension
{
private $request;
public function __construct($container)
{
$this->request = $container->get('request');
}
public function getFunctions() {
return array(
'new_func' => new \Twig_Function_Method($this, 'newFunction', array('needs_environment' => true) )
);
}
public function newFunction(\Twig_Environment $environment, $route) {
$r = $this->request;
return $environment->render($route);
}
public function getName() {
return "new_func";
}
}
I'm not sure I follow why would you need this, but I think it's great as an example of an abstract question:
How do I track down & extend core functionality of Symfony2?
Finding the functionality
Seems that you're having trouble finding where is this render_esi executed, so let's tackle that!
This doesn't seem like a standard Twig feature, so it must be an extension, just like the one you're creating.
It should be located somewhere in Symfony2 core files, so we start looking into vendor/symfony/src folder. Since we already know that we're dealing with an extension of Twig, Component folder is out of the question (because Twig is a separate library from Symfony2 core components).
So we've narrowed it down to Bridge and Bundle. If we look inside them then we see Bundle/TwigBundle or Bridge/Twig. We also know that Symfony2 developers follow a strict code/architecture style, so we know exactly which folder to look for - Extension. Now it's just a matter of checking them both.
Long story short we find what we're looking for in vendor/symfony/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension, where we see a render_* function. Jackpot!
Extending the functionality
Before changing anything, we need to first emulate what's already there, so we create something like this:
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
class NewTwigFunction extends \Twig_Extension
{
private $handler;
public function __construct(FragmentHandler $handler)
{
$this->handler = $handler;
}
public function getFunctions()
{
return array(
'new_func' => new \Twig_Function_Method($this, 'newFunction', array('is_safe' => array('html')) )
);
}
public function newFunction($uri, $options = array())
{
return $this->handler->render($uri, 'esi', $options);
}
public function getName()
{
return "new_func";
}
}
Now when you call
{{ new_func(url(route-name)) }}
you should see same results as
{{ render_esi(url(route-name)) }}
But we still need to get rid of the url part.
Easy as pie, we just add the router service to our extension! Now our extension could look like this:
use Symfony\Component\Routing\Router;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
class NewTwigFunction extends \Twig_Extension
{
private $handler;
private $router;
public function __construct(FragmentHandler $handler, Router $router)
{
$this->handler = $handler;
$this->router = $router;
}
public function getFunctions()
{
return array(
'new_func' => new \Twig_Function_Method($this, 'newFunction', array('is_safe' => array('html')) )
);
}
public function newFunction($routeName, $options = array())
{
$uri = $this->router->generate($routeName);
return $this->handler->render($uri, 'esi', $options);
}
public function getName()
{
return "new_func";
}
}
and {{ new_func(route-name) }} should work as expected.
Hooking in-between
The way I understood it, you want almost the same functionality as render_esi, but with slight changes to output.
So that means that we need to hook somewhere in-between return and $this->handler->render($uri, $strategy, $options);.
How deep down the rabbit hole we need to go depends on the change.
For example, if you want to alter Response object before it's turned into actual html string, you need to find the spot where it's turned in the first place. A good bet would be to look into FragmentHandler:
protected function deliver(Response $response)
{
if (!$response->isSuccessful()) {
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->request->getUri(), $response->getStatusCode()));
}
if (!$response instanceof StreamedResponse) {
return $response->getContent();
}
$response->sendContent();
}
Got it! Now you just need to extend FragmentHandler::deliver() and pass your implementation of it into your twig extenion.
Tracking down configuration
You have to understand that Symfony2 core code is not that different from what you write in your everyday life, it still abides by its own rules.
For example, when normally creating a Twig extension in Symfony2 you need to configure it as a service, right? Well, Symfony2 core extensions are configured in the same way. You just need to find where the configuration files are located.
Following the logic from Extending the functionality we know for sure that they're not located in Component. Bridge is actually a name for a design pattern - not a place where you'd place your service configuration :)
So we're left with Bundle - and obviously that's where we find all the information we need: vendor/symfony/src/Bundle/TwigBundle/Resources/config/twig.xml
Now we simply look up how original HttpKernelExtension is configured and follow its lead:
<service id="twig.extension.httpkernel" class="%twig.extension.httpkernel.class%" public="false">
<argument type="service" id="fragment.handler" />
</service>
Transforming it into a more commonly used .yml format, our extension config could look like this:
new_func:
class: Acme\Bundle\MyBundle\Twig\NewTwigFunction
arguments:
- "#fragment.handler"
# Uncomment when implementing code from 2nd example
# - "#router"
tags:
- { name: twig.extension }
public: false