I am trying to call Twig function created in TwigExtension (Symfony 3.3). Problem is that I can't find what I did wrong and I am not sure why it is not working
Does someone knows where is the problem?
This is error I am getting:
Unknown "getCurrentLocale" function.
Here is my code:
Twig Extension:
<?php
namespace AppBundle\Extension;
use Symfony\Component\HttpFoundation\Request;
class AppTwigExtensions extends \Twig_Extension
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('getCurrentLocale', [$this, 'getCurrentLocale']),
];
}
public function getCurrentLocale()
{
dump ($this->request);
/*
* Some code
*/
return "EN";
}
public function getName()
{
return 'App Twig Repository';
}
}
Services:
services:
twig.extension:
class: AppBundle\Extension\AppTwigExtensions
arguments: ["#request"]
tags:
- { name: twig.extension }
Twig:
{{ attribute(country.country, 'name' ~ getCurrentLocale() ) }}
So what is your overall plan with the extension. Do you still need it when app.request.locale in twig returns the current locale? (which it does)
Also by default the #request service does not exist anymore.
In Symfony 3.0, we will fix the problem once and for all by removing the request service — from New in Symfony 2.4: The Request Stack
This is why you should get something like:
The service "twig.extension" has a dependency on a non-existent service "request".
So you made this service? Is it loaded? What is it? You can see all available services names matching request using bin/console debug:container request.
If you do need the request object in the extension, if you are planning to do more with you would want to inject the request_stack service together with $request = $requestStack->getCurrentRequest();.
Somehow the code, symfony version and the error message you posted don't correlate. Also in my test, once removing the service arguments it worked fine. Try it yourself reduce the footprint and keep it as simple as possible, which in my case was:
services.yml:
twig.extension:
class: AppBundle\Extension\AppTwigExtensions
tags:
- { name: twig.extension }
AppTwigExtensions.php:
namespace AppBundle\Extension;
class AppTwigExtensions extends \Twig_Extension {
public function getFunctions() {
return [
new \Twig_SimpleFunction('getCurrentLocale', function () {
return 'en';
}),
];
}
}
And take it from there, figure out when it goes wrong.
Related
What is the file/class that ultimately executes the Twig url() function, like here: Home in twig.
I know I can use a decorator to change functions with something like:
App\Services\MyRouter:
decorates: 'router'
arguments: ['#App\Services\MyRouter.inner']
and
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
$name = 'test'.$name;
return $this->router->generate($name, $parameters, $referenceType);
}
The above changes the route which affects other areas like loading controllers.
All I'm after is the final output in the twig document. I haven't been able to find the the correct service to decorate.
edit
Based off the answers I have been working with twig.extension.routing but now I just get an "Unknown "path" function." exception. My expectation would be for nothing to happen and my function returns the original method.
App\Service\TwigUrlDecorator:
decorates: 'twig.extension.routing'
arguments: ['#App\Service\TwigUrlDecorator.inner']
public: false
<?php
// src/Service/TwigUrlDecorator.php
namespace App\Service;
use Twig\Extension\AbstractExtension;
class TwigUrlDecorator extends AbstractExtension
{
public function getPath($name, $parameters = array(), $relative = false)
{
return parent::getPath($name, $parameters, $relative);
}
}
The url twig function is executed by Symfony\Bridge\Twig\Extension\RoutingExtension::getUrl().
You can find the class definition here, and the specific method here. The service is defined here, where you can see the service name is twig.extension.routing.
I guess you could decorate the extension, but considering how simple it is, it might be simpler just to define your own URL generating twig function by defining a new Twig Extension.
I am working on a Symfony 2.7 WebApp. One of the bundles I created includes a service that offer some user related stuff, e.g. userHasPurchases().
Problem is, that including a Twig Extesion breaks another service:
AppShopService
namespace AppShopBundle\Service;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
...
class AppShopService {
protected $user;
public function __construct(TokenStorageInterface $tokenStorage, ...) {
$this->user = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null;
...
}
public function userHasPurchases(User $user) {
$user = $user ? $user : $this->user;
$result = $user...
return result;
}
}
AppShopBundle\Resources\config\services.yml
services:
app_shop.service:
class: AppShopBundle\Service\AppShopService
arguments:
- "#security.token_storage"
- ...
So far everything works fine: The AppShopServices is created with the current user and userHasPurchases() work as expected.
Now I have add a Twig Extension to be able to use userHasPurchases() within my templates:
Twig Extension
namespace AppShopBundle\Twig;
use AppShopBundle\Service\AppShopService;
class AppShopExtension extends \Twig_Extension {
private $shopService;
public function __construct(AppShopService $shopService) {
$this->shopService = $shopService;
}
public function getName() {
return 'app_shop_bundle_extension';
}
public function getFunctions() {
$functions = array();
$functions[] = new \Twig_SimpleFunction('userHasPurchases', array(
$this,
'userHasPurchases'
));
return $functions;
}
public function userHasPurchases($user) {
return $this->shopService->userHasPurchases($user);
}
}
Including Extension in AppShopBundle\Resources\config\services.yml
services:
app_shop.service:
class: AppShopBundle\Service\AppShopService
arguments:
- "#security.token_storage"
- ...
app_shop.twig_extension:
class: AppShopBundle\Twig\AppShopExtension
arguments:
- "#app_shop.service"
tags:
- { name: twig.extension }
After icluding the Twig Extension, AppShopService and its method userHasPurchases does not work any more. Problem is, that the constructor of AppShopService does not set user anymore since $tokenStorage->getToken() now returns null.
How is this possible? I have changed nothing except including the Twig Extension. As soon as I remove the Twig Extension from services.yml everything works correctly again.
My only guess is, that the creation fo the Twig Extension is done before any security. But why?
Any idea what might be wrong here?
don't interact with the tokenStorage in the constructor but only in the userHasPurchases method.
namespace AppShopBundle\Service;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
...
class AppShopService {
protected $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, ...) {
$this->tokenStorage = $tokenStorage;
}
public function userHasPurchases(User $user) {
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
$result = $user...
return result;
}
}
Hope this help
i've got a problem in symfony2 at the moment and i don't know how i can solve it.
Within a self defined new twig extension I want to call a controller or or a view (a twig file).
How is the correct way to realize this? Can you help me? I've read many symfony2 internet pages but i didn't found a good programming approach for me.
For better understanding why i want to do something like this, here is an exmaple what is my idea:
I want to source out some html code into an separate view. This new view is embedded in another view by calling the twig extension.
So how can i realize this?
Thanxs for your help.
As you are using Symfony2, you can inject the templating service to your Twig extension and then call the ->render method.
The extension
<?php
namespace YourPackage\YourBundle\Twig\Extension;
use Symfony\Component\Templating\EngineInterface;
class Test_Extension extends \Twig_Extension
{
protected $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('my_test', array($this->myTest()), array('is_safe' => array('html')))
);
}
public function myTest()
{
// do some stuffs
$data = $this->templating->render("SomeBundle:Directory:file.html.twig");
// ...
return $data;
}
public function getName()
{
return 'test';
}
}
services.yml
# src/YourPackage/YourBUndle/Resources/config/services.yml
services:
test.test_extension:
class: YourPackage\YourBundle\Twig\Extension\TestExtension
arguments: ['#templating']
tags:
- { name: twig.extension }
I need to get doctrine working inside my helper, im trying to use like i normaly do in a controller:
$giftRepository = $this->getDoctrine( )->getRepository( 'DonePunctisBundle:Gift' );
But this gave me:
FATAL ERROR: CALL TO UNDEFINED METHOD
DONE\PUNCTISBUNDLE\HELPER\UTILITYHELPER::GETDOCTRINE() IN
/VAR/WWW/VHOSTS/PUNCTIS.COM/HTTPDOCS/SRC/DONE/PUNCTISBUNDLE/HELPER/UTILITYHELPER.PH
What Im missing here?
EDIT:
services file
services:
templating.helper.utility:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#service_container]
tags:
- { name: templating.helper, alias: utility }
Firts lines of helper file
<?php
namespace Done\PunctisBundle\Helper;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Templating\EngineInterface;
class UtilityHelper extends Helper {
/*
* Dependency injection
*/
private $container;
public function __construct( $container )
{
$this->container = $container;
}
The problem here is that your Helper class is not container-aware; that is, it has no idea about all the services Symfony has loaded (monolog, twig, ...and doctrine).
You fix this by passing "doctrine" to it. This is called Dependency Injection, and is one of the core things that makes Symfony awesome. Here's how it works:
First, give your Helper class a place for the Doctrine service to live, and require it in the Helper's constructor:
class UtilityHelper
{
private $doctrine;
public function __construct($doctrine)
{
$this->doctrine = $doctrine;
}
public function doSomething()
{
// Do something here
}
}
Then, you use services.yml to define how Symfony should construct that instance of Helper:
services:
helper:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#doctrine]
In this case, #doctrine is a placeholder that means "insert the Doctrine service here".
So now, in your Controller, or in anything else that is container-aware, you can get access to Doctrine through the Helper class like this:
class SomeController()
{
public function someAction()
{
$this->get("helper")->doctrine->getRepository(...);
}
}
EDIT
After looking at your edit, it appears that you're injecting the entire service container into the Helper class. That's not a best practice -- you should only inject what you need. However, you can still do it:
services.yml
services:
helper:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#service_container]
UtilityHelper.php
class UtilityHelper
{
private $container;
public function __construct($container)
{
$this->container = $container;
}
public function doSomething()
{
// This won't work, because UtilityHelper doesn't have a getDoctrine() method:
// $this->getDoctrine()->getRepository(...)
// Instead, think about what you have access to...
$container = $this->container;
// Now, you need to get Doctrine
// This won't work... getDoctrine() is a shortcut method, available only in a Controller
// $container->getDoctrine()->getRepository(...)
$container->get("doctrine")->getRepository(...)
}
}
I've included a few comments there that highlight some common pitfalls. Hope this helps.
In Helper, Services etc you cannot use it like in actions.
You need to pass it like argument to youre Helper via service description in conf file(services.yml or *.xml).
Example:
services:
%service_name%:
class: %path_to_youre_helper_class%
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: %name% }
And dont forget catch it in __construct of youre Helper.
Example:
use Doctrine\ORM\EntityManager;
....
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
You can use it like:
public function myMethod()
{
$repo = $this->em->getRepository('DonePunctisBundle:Gift');
}
For a Symfony 2.1 project, I'm trying to create a new annotation #Json() that will register a listener that will create the JsonResponse object automatically when I return an array. I've got it working, but for some reason the listener is always called, even on methods that don't have the #Json annotation. I'm assuming my approach works, since the Sensio extra bundle does this with the #Template annotation.
Here is my annotation code.
<?php
namespace Company\Bundle\Annotations;
/**
* #Annotation
*/
class Json extends \Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationAnnotation
{
public function getAliasName()
{
return 'json';
}
}
Here is my listener code.
<?php
namespace Company\Bundle\Listener\Response\Json;
class JsonListener
{
//..
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$request = $event->getRequest();
$data = $event->getControllerResult();
if(is_array($data) || is_object($data)) {
if ($request->attributes->get('_json')) {
$event->setResponse(new JsonResponse($data));
}
}
}
}
This is my yaml definition for the listener.
json.listener:
class: Company\Bundle\Listener\Response\Json
arguments: [#service_container]
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView }
I'm obviously missing something here because its being registered as a kernel.view listener. How do I change this so that it is only called when a #Json() annotation is present on the controller action?
Not pretend to be the definitive answer.
I'm not sure why your are extending ConfigurationAnnotation: its constructor accepts an array, but you don't need any configuration for your annotation. Instead, implement ConfigurationInterface:
namespace Company\Bundle\Annotations;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
/**
* #Annotation
*/
class Json implements ConfigurationInterface
{
public function getAliasName()
{
return 'json';
}
public function allowArray()
{
return false;
}
}
Sensio ControllerListener from SensionFrameworkExtraBundle will read your annotation (merging class with methods annotations) and perform this check:
if ($configuration instanceof ConfigurationInterface) {
if ($configuration->allowArray()) {
$configurations['_'.$configuration->getAliasName()][] = $configuration;
} else {
$configurations['_'.$configuration->getAliasName()] = $configuration;
}
}
Setting a request attribute prefixed with _. You are correctly checking for _json, so it should work. Try dumping $request->attributes in your view event listener. Be sure that your json.listener service is correctly loaded too (dump them with php app/console container:debug >> container.txt).
If it doesn't work, try adding some debug and print statements here (find ControllerListener.php in your vendor folder):
var_dump(array_keys($configurations)); // Should contain _json
Remember to make a copy of it before edits, otherwise Composer will throw and error when updating dependencies.