I have 2 deprecations in my Symfony 3 project when try to define a custom service in service.yml that I would like to solve but I don't get the way...
This my code FollowingExtension.php
<?php
namespace AppBundle\Twig;
use Symfony\Bridge\Doctrine\RegistryInterface;
class FollowingExtension extends \Twig_Extension {
protected $doctrine;
public function __construct(RegistryInterface $doctrine) {
$this->doctrine = $doctrine;
}
public function getFilters() {
return array(
new \Twig_SimpleFilter('following', array($this, 'followingFilter'))
);
}
public function followingFilter($user, $followed){
$following_repo = $this->doctrine->getRepository('BackendBundle:Following');
$user_following = $following_repo->findOneBy(array(
"user" => $user,
"followed" => $followed
));
if(!empty($user_following) && is_object($user_following)){
$result = true;
}else{
$result = false;
}
return $result;
}
public function getName() {
return 'following_extension';
}
}
And this is my services.yml:
following.twig_extension:
class: AppBundle\Twig\FollowingExtension
public: false
arguments:
$doctrine: "#doctrine"
tags:
- { name: twig.extension }
I would appreciate the help they gave me in trying to solve my problem.
First problem solution
It looks like because of registering services with duplicate name, as declared in your class:
`return 'following_extension';`
Make sure that you only have one twig service named following_extension. If you are sure that only one twig service named following_extension, you probably register more than one service using that class.
Second problem solution
Replace
`use Symfony\Bridge\Doctrine\RegistryInterface;`
with
`use Doctrine\Common\Persistence\ManagerInterface;`
and also replace
`public function __construct(RegistryInterface $doctrine) {`
with
`public function __construct(ManagerInterface $doctrine) {`
Finally solved the problem and i want share the info...
Reading the documentation of Symfony found this How to Create Service Aliases and Mark Services as Private and them I declared my service this way:
in service.yml:
following.twig_extension: '#AppBundle\Twig\FollowingExtension'
and FollowingExtension.php
<?php
namespace AppBundle\Twig;
use Doctrine\Common\Persistence\ManagerRegistry;
class FollowingExtension extends \Twig_Extension {
private $managerRegistry;
public function __construct(ManagerRegistry $managerRegistry) {
$this->managerRegistry = $managerRegistry;
}
public function getFilters() {
return array(
new \Twig_SimpleFilter('following', array($this, 'followingFilter'))
);
}
public function followingFilter($user, $followed){
$following_repo = $this->managerRegistry->getRepository('BackendBundle:Following');
$user_following = $following_repo->findOneBy(array(
"user" => $user,
"followed" => $followed
));
if(!empty($user_following) && is_object($user_following)){
$result = true;
}else{
$result = false;
}
return $result;
}
}
Thanks for helping me and I'm sorry if my english is bad.
Related
I have a CrudController for my entity, Participant. I want to add a custom action, sendAcknowledgementEmail. The EasyAdmin docs doesn't mention anything about the custom function parameters or return values.
I have the following code
public function configureActions(Actions $actions): Actions
{
$send_acknowledgement_email = Action::new('sendAcknowledgementEmail', 'Send Acknowledgement Email', 'fa fa-send')
->linkToCrudAction('sendAcknowledgementEmail');
return $actions
->add(Crud::PAGE_INDEX, $send_acknowledgement_email)
->add(Crud::PAGE_EDIT, $send_acknowledgement_email)
;
}
public function sendAcknowledgementEmail() //Do I need parameters?
{
//How do I get the Entity?
//What should I return?
}
So far, EasyAdmin detects the custom function but I get an error "The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned null. Did you forget to add a return statement somewhere in your controller?"
How do I continue from here?
The v3.x of the bundle is quite new and the documentation is not perfect yet.
Based on Ceochronos answer, here is my implementation for a clone action.
public function configureActions(Actions $actions): Actions
{
$cloneAction = Action::new('Clone', '')
->setIcon('fas fa-clone')
->linkToCrudAction('cloneAction');
return $actions
->add(Crud::PAGE_INDEX, $cloneAction);
}
public function cloneAction(AdminContext $context)
{
$id = $context->getRequest()->query->get('entityId');
$entity = $this->getDoctrine()->getRepository(Product::class)->find($id);
$clone = clone $entity;
// custom logic
$clone->setEnabled(false);
// ...
$now = new DateTime();
$clone->setCreatedAt($now);
$clone->setUpdatedAt($now);
$this->persistEntity($this->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $clone);
$this->addFlash('success', 'Product duplicated');
return $this->redirect($this->get(CrudUrlGenerator::class)->build()->setAction(Action::INDEX)->generateUrl());
}
After browsing through the EasyAdmin AbstractCrudController I came up with the following working code.
In order to get the current object you need the parameter AdminContext
For my use case I want to return to the CrudController index action, so for that I can do a redirect.
Note: you need to inject the CrudUrlGenerator service in your constructor controller.
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
// Your logic
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current function looks like this:
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
$participant->sendAcknowledgementEmail();
$this->addFlash('notice','<span style="color: green"><i class="fa fa-check"></i> Email sent</span>');
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current working code
<?php
namespace App\Controller\Admin;
use App\Service\WebinarService;
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
use Symfony\Contracts\Translation\TranslatorInterface;
// ...
class ParticipantCrudController extends AbstractCrudController
{
private CrudUrlGenerator $crudUrlGenerator;
private WebinarService $webinar_service;
private TranslatorInterface $translator;
public function __construct(CrudUrlGenerator $crudUrlGenerator, WebinarService $webinar_service, TranslatorInterface $translator)
{
$this->crudUrlGenerator = $crudUrlGenerator;
$this->webinar_service = $webinar_service;
$this->translator = $translator;
}
// ...
public function sendAcknowledgementEmail(AdminContext $context): Response
{
$participant = $context->getEntity()->getInstance();
try {
$this->webinar_service->sendAcknowledgementEmail($participant);
$this->addFlash('notice', 'flash.email.sent');
} catch (Exception $e) {
$this->addFlash('error', $this->translator->trans('flash.error', ['message' => $e->getMessage()]));
}
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl()
;
return $this->redirect($url);
}
}
I want to set session value in Symfony2 view both in PHP and TWIG . I can get the session value in view like this
$app->getSession()->get('whatever');
But didn't know how to set the session value in view . Kindly any one help .
This is your twig extension you can put this code under YourBundle/Twig/SessionExtension.php
namespace YourBundle\Twig;
use Symfony\Component\HttpFoundation\Session\Session;
class SessionExtension extends \Twig_Extension
{
private $session;
public function __construct(Session $session) {
$this->session = $session;
}
public function getFilters()
{
return array(
new \Twig_SimpleFilter('setSession', array($this, 'setSession')),
);
}
public function setSession($key, $val){
$this->session->set($key, $val);
return true;
}
public function getName()
{
return 'session_extension';
}
}
Add to service for twig;
yourbundle.twig.session_extension:
class: YourBundle\Twig\SessionExtension
arguments:
session: "#session"
tags:
- { name: twig.extension }
Now , you can use in twig;
{{ 'test'|setSession('myKey', 'myValue') }}
Ok, I was trying to create twig extension with dependencies on other service (security.context) and got some troubles. So, here is my service declaration:
acme.twig.user_extension:
class: Acme\BaseBundle\Twig\UserExtension
arguments: ["#security.context"]
tags:
- { name: twig.extension }
and here's my class
// acme/basebundle/twig/userextension.php
namespace Acme\BaseBundle\Twig;
use Symfony\Component\Security\Core\SecurityContext;
use Acme\UserBundle\Entity\User;
class UserExtension extends \Twig_Extension
{
protected $context;
public function __construct(SecurityContext $context){
$this->context = $context;
}
public function getFunctions()
{
return array(
'getAbcData' => new \Twig_SimpleFunction('getAbcData', $this->getAbcData()),
);
}
public function getAbcData()
{
if ( !is_object($user = $this->context->getToken()->getUser()) || !$user instanceof User){ return null; }
return array(
'data_array' => $user->getData(),
);
}
public function getName()
{
return 'user_extension';
}
}
Finally, I have an error:
FatalErrorException: Error: Call to a member function getUser() on a non-object in \src\Acme\BaseBundle\Twig\UserExtension.php line 27
I guess that security.context service is not initialized yet, then i get an error.
Could anyone tell, please, is there are ways to load service manually, or any better solutions for an issue?
Thanks a lot.
I use Symfony 2.5.*
UPD:
I've also found this notice in symfony docs
Keep in mind that Twig Extensions are not lazily loaded. This means that there's a higher chance that you'll get a CircularReferenceException or a ScopeWideningInjectionException if any services (or your Twig Extension in this case) are dependent on the request service. For more information take a look at How to Work with Scopes.
Actually, I have no idea about how to do it correct..
You are calling $this->getAbcData() when constructing Twig_SimpleFilter. But you have to pass a callable as argument.
public function getFunctions() {
return array (
'getAbcData' => new \Twig_SimpleFunction( 'getAbcData', array( $this, 'getAbcData' ))
);
}
Leo is also right. You should check first if getToken() is returning an object before trying getToken()->getUser().
You can also pass the user to the function as a parameter in twig: {{ getAbcData(app.user) }}. This way the function is more generic and could be used for any user, not just the currently logged in one.
This should probably work. The error message means that getToken() is not an object so you have to test if getToken() is an object before testing if getUser() is also is an object.
public function getAbcData()
{
$token = $this->context->getToken();
if (!is_object($token) || !is_object($token->getUser())) {
return null;
}
return array(
'data_array' => $user->getData(),
);
}
You need to change your twig extension to have the container not the security context passed into the constructor.
Twig_Extensions are special in that the normal rule of don't pass in the container but instead pass in only what you need often doesn't apply as it causes problems due to scope issues.
So change your extension to be like this.
// acme/basebundle/twig/userextension.php
namespace Acme\BaseBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\SecurityContext;
use Acme\UserBundle\Entity\User;
class UserExtension extends \Twig_Extension
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container){
$this->container = $container;
}
public function getFunctions()
{
return array(
'getAbcData' => new \Twig_SimpleFunction('getAbcData', $this->getAbcData()),
);
}
public function getAbcData()
{
if ( !is_object($user = $this->container->get('security.context')->getToken()->getUser()) || !$user instanceof User){ return null; }
return array(
'data_array' => $user->getData(),
);
}
public function getName()
{
return 'user_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 building a site where the user can choose a country, state and city he wants.
Once he selects these parameters he goes to a page like this: en.example.com/spain/madrid/madrid/
The problem is, every time I want to build a new url, I must pass these 3 variables and I was wondering if I could do something to make them just like the _locale variable which symfony itself passes it to the parameters.
Thanks
After searching more I found this post: http://blog.viison.com/post/15619033835/symfony2-twig-extension-switch-locale-current-route
I just used the idea and made the changes I needed and this is the final code for my extension
<?php
namespace Comehoy\CoreBundle\Twig\Extension;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
class PathExtension extends \Twig_Extension
{
private $request;
private $router;
public function __construct(Router $router) {
$this->router = $router;
}
public function onKernelRequest(GetResponseEvent $event) {
if ($event->getRequestType() === HttpKernel::MASTER_REQUEST) {
$this->request = $event->getRequest();
}
}
public function getFunctions()
{
return array(
'l10n_path' => new \Twig_Function_Method($this, 'getPath')
);
}
public function getPath($name, $parameters = array())
{
$parameters = array_merge($parameters, [
'country' => $this->request->get('country'),
'state' => $this->request->get('state'),
'city' => $this->request->get('city'),
]);
return $this->generator->generate($name, $parameters, false);
}
public function getName()
{
return 'twig_my_path_extension';
}
}
And as for the configuration its the same as the post
services:
twig.localized_route_extension:
class: Acme\CoreBundle\Twig\PathExtension
tags:
- { name: twig.extension }
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
arguments: [#router]
And for the routes that I use country, state and the city I put them in the prefix to avoid repeating them in each route.
acme_core:
resource: "#AcmeCoreBundle/Controller/"
type: annotation
prefix: /{country}/{state}/{city}
Hope it helps someone else.