How to override path() method for twig, when autowire is enabled?
For example:
class ExampleRoutingExtension extends RoutingExtension
{
public function getPath($name, $parameters = array(), $relative = false)
{
return '/testEndpoint';
}
}
there is no problem with autowiring with this class(constructor creates object), but getPath() method does not overrides parent getPath()
Create your Twig extension :
class AppExtension extends \Twig_Extension
{
public function getFunctions(): array
{
return [
new \Twig_SimpleFunction('path', [$this, 'path']),
];
}
public function path(string $name, array $parameters = array(), bool $relative = false): string
{
return '/testEndpoint';
}
}
And enabled with services.yml :
App\Twig\YourExtension:
tags:
- { name: twig.extension }
You can inject service (example : routing) in __construct()
Related
I am trying to register (read the docs) a Twig extension and everything seems to be correct except it's not being found in the Twig file.
Getting the following error:
The function "getPostCount" does not exist in AcmeDemoBundle:Page:index.html.twig at line 17
Can someone show me what I am doing wrong?
services.yml
acme.twig.acme_extension:
class: Acme\DemoBundle\Twig\PostExtension
tags:
- { name: twig. extension }
arguments:
em: "#doctrine.orm.entity_manager"
PostExtension.php
class PostExtension extends \Twig_Extension
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function getFilters()
{
return array(
);
}
public function getFunctions()
{
return array(
'getPostCount' => new \Twig_Function_Method($this,'getPostCount')
);
}
public function getPostCount($year, $month)
{
return $this->$em->getRepository('AcmeDemoBundle:Post')
->getPostCountsByMonth($year, $month);
}
public function getName()
{
return 'post_extension';
}
}
Twig
{{ getPostCount('2014', 'July') }}
In services.yml:
Remove the extra space in twig.extension.
tags:
- { name: twig.extension }
I have problems with adding Twig extensions.
I have Bundle controllers extending custom BaseController class:
class DefaultController extends BaseController
And there's my BaseController class (only part of it).
class BaseController extends Controller {
public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null)
{
parent::setContainer($container);
$this->onContainerSet();
}
public function onContainerSet()
{
// many other tasks
$this->get('twig')->addExtension(new \Twig_Extension_StringLoader());
$this->get('twig.loader')->addPath('../app');
$function = new \Twig_SimpleFunction('stars', function ($number, $maximum_stars = 5) {
$this->get('twig')->addGlobal('star_number',sprintf("%.1f",$number));
$this->get('twig')->addGlobal('star_max',$maximum_stars);
$full_stars = floor($number);
$half_stars = ($number - $full_stars) * 2;
$empty_stars = $maximum_stars - $full_stars - $half_stars;
$this->get('twig')->addGlobal('full_stars_number',$full_stars);
$this->get('twig')->addGlobal('half_stars_number',$half_stars);
$this->get('twig')->addGlobal('empty_stars_number',$empty_stars);
echo $this->renderView(
'views/stars.html.twig'
);;
});
$function2 = new \Twig_SimpleFunction('inurl', function ($anchor, $code) {
echo ''.$anchor."";
});
$this->get('twig')->addFunction($function);
$this->get('twig')->addFunction($function2);
}
}
The problem:
When I clear cache directory I have first message:
CRITICAL - Uncaught PHP Exception LogicException: "Unable to register
extension "string_loader" as extensions have already been
initialized." at ...\vendor\twig\twig\lib\Twig\Environment.php line
660 Context: {"exception":"Object(LogicException)"}
But when I reload page (cache folder is already created) it works fine (no exception).
However if I comment line:
// $this->get('twig')->addExtension(new \Twig_Extension_StringLoader());
and clear cache directory I have exception:
CRITICAL - Uncaught PHP Exception LogicException: "Unable to add
function "stars" as extensions have already been initialized." at
...\vendor\twig\twig\lib\Twig\Environment.php line 946 Context:
{"exception":"Object(LogicException)"}
So it seems that when cache directory doesn't exist from some reason adding any Twig extensions doesn't work (extensions have already been initialized) as I would like but when cache directory is already created everything works fine.
Question - how to solve it in the simplest way?
Create your class in YourBundle\Twig
class YourExtension extends \Twig_Extension
{
/**
* #var Router
*/
protected $router;
function __construct(Router $router)
{
$this->router = $router;
}
/**
* #return array
*/
public function getFilters()
{
return [
new \Twig_SimpleFilter('my_filter', [$this, 'myFilter'], ['is_safe' => ['html']]),
];
}
/**
* #return string
*/
public function myFilter(User $user)
{
return 'FILTERED: ' . $user->getName();
}
/**
* #return string
*/
public function getName()
{
return 'my_filter_extension';
}
}
Then, register your extension as a service: ( in this case I inject router as an argument )
yourbundle.twig.my_filter_extension:
class: Acme\YourBundle\Twig\YourExtension
arguments: [#router]
tags:
- { name: twig.extension }
If you want to enable Twig_Extension_StringLoader, add to your services:
yourbundle.twig.extension.loader:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
Twig_Extension_StringLoader is not loaded by default.
What I finally did to achieve result (maybe someone will have similar problem in the future):
In config.yml I've added:
services:
yourbundle.twig.extension.loader:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
yourbundle.twig.stars_extension:
class: Mnab\Twig\Stars
tags:
- { name: 'twig.extension' }
yourbundle.twig.inurl_extension:
class: Mnab\Twig\InternalUrl
tags:
- { name: 'twig.extension' }
in my BaseController I only left from question code:
$this->get('twig.loader')->addPath('../app');
but also added:
$this->get('twig')->addGlobal('internal_links',$this->internalLinks);
to use it in Twig extension
And I've create 2 classes:
<?php
//InternalUrl.php
namespace Mnab\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
class InternalUrl extends \Twig_Extension {
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('inurl', array($this, 'inUrlFunction'), array('needs_environment' => true, 'is_safe' => array('html'))),
);
}
public function inUrlFunction(\Twig_Environment $env, $anchor, $code)
{
return ''.$anchor."";
}
public function getName()
{
return 'inurl_extension';
}
}
and
<?php
// Stars.php
namespace Mnab\Twig;
class Stars extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('stars', array($this, 'starsFunction'), array('needs_environment' => true, 'is_safe' => array('html'))),
);
}
public function starsFunction(\Twig_Environment $env, $number, $maximum_stars = 5)
{
$env->addGlobal('star_number',sprintf("%.1f",$number));
$env->addGlobal('star_max',$maximum_stars);
$full_stars = floor($number);
$half_stars = ($number - $full_stars) * 2;
$empty_stars = $maximum_stars - $full_stars - $half_stars;
$env->addGlobal('full_stars_number',$full_stars);
$env->addGlobal('half_stars_number',$half_stars);
$env->addGlobal('empty_stars_number',$empty_stars);
return $env->render(
'views/stars.html.twig'
);
}
public function getName()
{
return 'stars_extension';
}
}
Now it seems to work regardless of cache is created or not. So it seems to better register services when you want to use Twig Extensions than registering Extensions in Controller.
I'm trying to set a twig filter working this way: {{entities|fieldnames}} that will return an array containing the properties names of a entity object.
My problem, after reading and trying for hours, is that I'm not able to execute $this->container->get("helpers") from the Twig Extension php. It seems that I'm not linking the service container properly... Help, please ;)
Error: Call to a member function get() on a non-object in /Users/a77/Dropbox/06.Proyectos/2011 U-Vox/DEV U-Vox/Uvox Web/src/Acme/DemoBundle/Extension/FieldnamesTwigExtension.php line 38
Or if construct without =null
Error
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Acme\DemoBundle\Extension\FieldnamesTwigExtension::__construct() must be an instance of Acme\DemoBundle\Extension\Container, none given, called in
services.yml
services:
helpers:
class: Acme\DemoBundle\Services\Helpers
twig.extension.acme.demo:
class: Acme\DemoBundle\Twig\Extension\DemoExtension
arguments: [twig.loader]
acme.demo.listener:
class: Acme\DemoBundle\EventListener\ControllerListener
arguments: [twig.extension.acme.demo]
fieldnames:
class: Acme\DemoBundle\Extension\FieldnamesTwigExtension
arguments: [#service_container]
Extension\FieldnamesTwigExtension.php
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
namespace Acme\DemoBundle\Extension;
class FieldnamesTwigExtension extends \Twig_Extension {
private $container;
public function __construct(Container $container=null)
{
$this->container = $container;
//var_dump ($container); exit; // prints null !!!
}
protected function get($service)
{
return $this->container->get($service);
}
public function getFilters() {
return array(
'fieldnames' => new \Twig_Filter_Method($this, 'fieldnamesFilter'),
);
}
public function getName() {
return 'fieldnames_twig_extension';
}
public function fieldnamesFilter($obj) {
if (is_array($obj)) {
$first = $obj[0];
// GET (HELPERS) NOT WORKING :
$fieldnames = $this->container->get("helpers")->getFieldnames($first);
return $fieldnames;
}
return null;
}
public function twig_array_get_function($array, $name) {
return $array[$name];
}
}
Helpers.php
namespace Acme\DemoBundle\Services;
class Helpers {
public function sum($n1, $n2) {
return $n1 + $n2;
}
public function getFieldnames($entities) {
$reflect = new \ReflectionClass($entities[0]);
$props = $reflect->getProperties();
$fieldnames = Array();
foreach ($props as $prop) {
$fieldnames[] = $prop->getName();
}
return $fieldnames;
}
}
AcmeDemoExtension.php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder; use
Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use
Symfony\Component\HttpKernel\DependencyInjection\Extension; use
Symfony\Component\Config\FileLocator; use
Symfony\Component\DependencyInjection\Definition; // Added
class AcmeDemoExtension extends Extension {
public function load(array $configs, ContainerBuilder $container) {
$definition = new Definition('Acme\DemoBundle\Extension\AccessTwigExtension');
$definition->addTag('twig.extension');
$container->setDefinition('access_twig_extension', $definition);
$definition2 = new Definition('Acme\DemoBundle\Extension\FieldnamesTwigExtension');
$definition2->addTag('twig.extension');
$container->setDefinition('fieldnames_twig_extension', $definition2);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
}
public function getAlias() {
return 'acme_demo';
}
}
I tried your code on my application and had the same error. To resolve it :
Acme\DemoBundle\DependencyInjection\AcmeDemoExtension.php
Remove this lines :
$definition2 = new Definition('Acme\DemoBundle\Extension\FieldnamesTwigExtension');
$definition2->addTag('twig.extension');
$container->setDefinition('fieldnames_twig_extension', $definition2);
Acme\DemoBundle\Resources\config\services.yml
Replace this lines:
fieldnames:
class: Acme\DemoBundle\Extension\FieldnamesTwigExtension
arguments: [#service_container]
By :
fieldnames:
class: Acme\DemoBundle\Extension\FieldnamesTwigExtension
arguments: [#service_container]
tags: [{ name: twig.extension }]
You are making this too hard. You should be injecting helpers directly into you twig extension. You should also be doing your tagging in services.yml. Not in the dependency injection extension.
http://symfony.com/doc/current/cookbook/templating/twig_extension.html
services.yml
fieldnames:
class: Acme\DemoBundle\Extension\FieldnamesTwigExtension
arguments: [#helpers]
tags:
- { name: twig.extension }
And adjust the rest of your code accordingly.
In your Extension\FieldnamesTwigExtension.php, is this normal that your namespace is called after use instruction ?
Which error Symfony return ?
How can one access the Request object inside Twig Extension?
namespace Acme\Bundle\Twig;
use Twig_SimpleFunction;
class MyClass extends \Twig_Extension
{
public function getFunctions()
{
return array(
new Twig_SimpleFunction('xyz', function($param) {
/// here
$request = $this->getRequestObject();
})
);
}
public function getName() {
return "xyz";
}
}
As requested in the comments, here's the prefered way of injecting a request into any service. It works with Symfony >= 2.4.
Injecting the request and putting our service in the request scope is no longer recommended. We should use the request stack instead.
namespace AppBundle\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
class MyClass extends \Twig_Extension
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getFunctions()
{
$requestStack = $this->requestStack;
return array(
new \Twig_SimpleFunction('xyz', function($param) use ($requestStack) {
$request = $requestStack->getCurrentRequest();
})
);
}
public function getName()
{
return "xyz";
}
}
app/config/services.yml
app.twig_extension:
class: AppBundle\Twig\MyExtension
arguments:
- '#request_stack'
tags:
- { name: twig.extension }
Docs:
the request stack API
the request stack announcement
Register your extension as a service and give it the container service:
# services.yml
services:
sybio.twig_extension:
class: %sybio.twig_extension.class%
arguments:
- #service_container
tags:
- { name: twig.extension, priority: 255 }
Then retrieve the container by your (twig extension) class constructor and then the request:
<?php
// Your class file:
// ...
class MyClass extends \Twig_Extension
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var Request
*/
protected $request;
/**
* Constructor
*
* #param ContainerInterface $container
*/
public function __construct($container)
{
$this->container = $container;
if ($this->container->isScopeActive('request')) {
$this->request = $this->container->get('request');
}
}
// ...
Note that testing the scope is usefull because there is no request when running console command, it avoids warnings.
That's it, you are able to use the request !
I would suggest setting 'needs_environment' => true for your Twig_SimpleFunction, which then will add \Twig_Environment as first argument of your function. Then in your function you can find the request like this:
$request = $twig->getGlobals()['app']->getRequest();
So the whole function will look like this:
...
public function getFunctions() {
return [
new \Twig_SimpleFunction('xyz', function(\Twig_Environment $env) {
$request = $twig->getGlobals()['app']->getRequest();
}, [
'needs_environment' => true,
]),
];
}
...
I'm getting an error:
"twig extension FatalErrorException: Error: Class Acme\Bundle\MyBundle\Twig not found in app/cache/dev/appDevDebugProjectContainer.php"
I've cleared the cache but this does nothing.
I'm just trying to test setting it up and then I can put in all my logic.
--
A file named MyTwigExtensions.php
namespace Acme\Bundle\MyBundle\Twig;
class MyTwigExtensions extends \Twig_Extension
{
public function getFunctions() {
return array(
new Twig_SimpleFunction('link', 'generate_link')
);
}
public function generate_link($params) {
return "THE-LINK-HERE";
}
public function getName() {
return "link";
}
}
In services.yml
services:
my_extension.twig.extension:
class: Acme\Bundle\MyBundle\Twig
arguments: []
tags:
- { name: twig.extension }
You have to enter fully qualified name of the extension class.
services:
my_extension.twig.extension:
class: Acme\Bundle\MyBundle\Twig\MyTwigExtensions # <--- here
arguments: []
tags:
- { name: twig.extension }
for service reprensetation format is sth like this. addArrowInCode below is the name of the method used in twig:
twig.extension.addArrowInCode:
class: Acme\DemoBundle\Twig\AddArrowInCodeExtension
tags:
- { name: twig.extension }
and for this extension you should have like...
class AddArrowInCodeExtension extends \Twig_Extension
{
function addArrowInCodeFilter($code, $separator = '⇒')
{
// do sth setting final
return $final;
}
/**
* Returns a list of filters to add to the existing list.
*
* #return array An array of filters
*/
public function getFilters()
{
return array(
'addArrowInCode' => new Twig_Filter_Method($this, 'addArrowInCodeFilter', array('needs_environment' => false)),
);
}
public function getName()
{
return 'addArrowInCode';
}
}
hope it helps
Please check this code.
namespace Acme\Bundle\MyBundle\Twig;
class MyTwigExtensions extends \Twig_Extension
{
public function getFunctions() {
return array(
new Twig_SimpleFunction('link', array($this, 'generate_link')) // <== changed here
);
}
public function generate_link($params) {
return "THE-LINK-HERE";
}
public function getName() {
return "link";
}
}
In services.yml
services:
my_extension.twig.extension:
class: Acme\Bundle\MyBundle\Twig\MyTwigExtensions # <== changed here
arguments: []
tags:
- { name: twig.extension }