Gedmo\Translatable ignoring default locale that is set in config - symfony

I've decided to install "gedmo/doctrine-extensions" on Symfony to use Translatable.
It works fine, except that listener is ignoring default locale I've specified, always falling back to en_US.
Translatable is plugged in as service:
#config.yml
services:
gedmo.listener.translatable:
class: Gedmo\Translatable\TranslatableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setDefaultLocale, [ ru ] ]
- [ setTranslationFallback, [ true ] ]
And when I try to find() object in database it always fetches en_US instead of ru:
$test = $em->find('Vendor\Entity\Test', 1);
//outputs row with 'locale' = "en_US" from `ext_translations_test` table
Only if I specify language directly, like:
$test->setTranslatableLocale('ru');
$em->refresh($test);
It gives desired translation.
Is there any way to set default locale that will work?
UPDATE
Adding another call function in config.yml fixed the problem, altough now I'm not quite sure what setDefaultLocale() function actually does, as Gedmo\Translatable\TranslatableListener::$defaultLocale property provided with a most horrid commentary I've ever seen. Will try to find more...
services:
gedmo.listener.translatable:
class: Gedmo\Translatable\TranslatableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setDefaultLocale, [ ru ] ]
- [ setTranslatableLocale, [ ru ] ]
- [ setTranslationFallback, [ true ] ]

According to: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/symfony2.md
Note: if you noticed, there's Acme\DemoBundle\Listener\DoctrineExtensionListener you will need to create this listener class if you use loggable or translatable behaviors. This listener will set the locale used from request and username to loggable. So, to finish the setup create Acme\DemoBundle\Listener\DoctrineExtensionListener
Make sure you have setup the kernel listener as well.
namespace Acme\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DoctrineExtensionListener implements ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function onLateKernelRequest(GetResponseEvent $event)
{
$translatable = $this->container->get('gedmo.listener.translatable');
$translatable->setTranslatableLocale($event->getRequest()->getLocale());
}
public function onKernelRequest(GetResponseEvent $event)
{
$securityContext = $this->container->get('security.context', ContainerInterface::NULL_ON_INVALID_REFERENCE);
if (null !== $securityContext && null !== $securityContext->getToken() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$loggable = $this->container->get('gedmo.listener.loggable');
$loggable->setUsername($securityContext->getToken()->getUsername());
}
}
}
And add the following to your config file:
services:
extension.listener:
class: Acme\DemoBundle\Listener\DoctrineExtensionListener
calls:
- [ setContainer, [ #service_container ] ]
tags:
# translatable sets locale after router processing
- { name: kernel.event_listener, event: kernel.request, method: onLateKernelRequest, priority: -10 }
# loggable hooks user username if one is in security context
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

Related

Symfony - log to multiple log files in a service

I'm trying to inject multiple monolog handler into a service. Right now my parent class injects a logger and the children class injects another logger. My goal is it to be able to log specific actions to specific log files.
My service.yaml:
App\Services\PrinterManager:
arguments: ['#doctrine.orm.entity_manager','#logger', '', '', '', '','']
tags:
- { name: monolog.logger, channel: printerface}
App\Services\Printer\Printer:
autowire: true
autoconfigure: false
public: false
parent: App\Services\PrinterManager
arguments:
index_2: '#logger'
index_3: '#oneup_flysystem.printer_invoice_filesystem'
index_4: '#oneup_flysystem.printerface_content_filesystem'
index_5: '#oneup_flysystem.sftp_filesystem'
index_6: '#App\Services\PrinterApiService'
tags:
- { name: monolog.logger, channel: printerlog}
My monolog.yaml:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event, !printerface", "!printerlog"]
printerface:
type: stream
level: debug
channels: ["printerface"]
path: "%kernel.logs_dir%/printerface.log"
printerlog:
type: stream
level: debug
channels: ["printerlog"]
path: "%kernel.logs_dir%/printerlog.log"
But it seems that the current service configuration breaks the constructor and I get the following error:
The argument must be an existing index or the name of a constructor's parameter.
Is there any way to use two log files in a service?
I've not done it with a parent/child class, but with something a little simpler I'm using named parameters, this is what I have (with three different loggers):
# App/Subscribers/WebhookLoggingListener.php file
public function __construct(
LoggerInterface $logger,
LoggerInterface $mailgunLog,
LoggerInterface $dripLog) {
}
# services.yml
App\Subscribers\WebhookLoggingListener:
arguments:
$logger: "#logger"
$mailgunLog: "#monolog.logger.mailgun"
$dripLog: "#monolog.logger.drip"
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
If I was using the other loggers elsewhere I could also bind them to specific variable names:
services:
_defaults:
# ... other config
bind:
$dripLog: "#?monolog.logger.drip"
This is the method that Symfony is using to replace parent's arguments in a child service:
/**
* You should always use this method when overwriting existing arguments
* of the parent definition.
*
* If you directly call setArguments() keep in mind that you must follow
* certain conventions when you want to overwrite the arguments of the
* parent definition, otherwise your arguments will only be appended.
*
* #param int|string $index
* #param mixed $value
*
* #return self the current instance
*
* #throws InvalidArgumentException when $index isn't an integer
*/
public function replaceArgument($index, $value)
{
if (\is_int($index)) {
$this->arguments['index_'.$index] = $value;
} elseif (0 === strpos($index, '$')) {
$this->arguments[$index] = $value;
} else {
throw new InvalidArgumentException('The argument must be an existing index or the name of a constructor\'s parameter.');
}
return $this;
}
As you can see, the indexes must be eighter same the argument variable names in the parent's constructor with a prefixed $ or an integer indicating the related argument.
So I think you must define your child service as below:
App\Services\Printer\Printer:
autowire: true
autoconfigure: false
public: false
parent: App\Services\PrinterManager
arguments:
2: '#logger'
3: '#oneup_flysystem.printer_invoice_filesystem'
4: '#oneup_flysystem.printerface_content_filesystem'
5: '#oneup_flysystem.sftp_filesystem'
6: '#App\Services\PrinterApiService'
tags:
- { name: monolog.logger, channel: printerlog}
Update:
After I reproduced your problem, I figured out that the solution is as below. With this solution, the Symfony autowiring will work for the child service.
App\Services\Printer\Printer:
autowire: true
autoconfigure: false
public: false
parent: App\Services\PrinterManager
arguments:
$arg2: '#logger'
$arg3: '#oneup_flysystem.printer_invoice_filesystem'
$arg4: '#oneup_flysystem.printerface_content_filesystem'
$arg5: '#oneup_flysystem.sftp_filesystem'
$arg6: '#App\Services\PrinterApiService'
tags:
- { name: monolog.logger, channel: printerlog}
$arg2, $arg3, $arg4, $arg5 and $arg6 must be replaced by your class constructor's argument names.

circular reference injecting security service into voter service

I'm trying to inject ServiceA(used to fetch entities based on is_granted) into ServiceB (a voter) and getting a circular reference.
I believe because when ServiceA is loaded its authentication_checker dependency tries to load all the voters, which includes ServiceB, which requires ServiceA... etc.
Anyway to work around this?
YAML service mapping:
services:
app.security_service:
class: AppBundle\Service\appSecurityService
arguments: [ "#logger", "#doctrine", "#security.authorization_checker", "#security.token_storage" ]
app.entity_voter:
class: AppBundle\Security\ChargebackNotificationVoter
public: false
tags:
- { name: security.voter }
arguments: [ "#logger", "#doctrine", "#app.security_service" ]
An example of what I'm doing in ServiceA
public function getEntitiesForUser(UserInterface $user)
{
$user = $this->tokenStorage->getToken()->getUser();
if($this->authorizationChecker->isGranted('ROLE_SYSTEM_ADMIN')){
//If the user has ROLE_SYSTEM_ADMIN get all the entities
$entitiess = $this->managerRegistry->getRepository('AppBundle:Entities')->findAll();
}elseif($this->authorizationChecker->isGranted('ROLE_ORGANIZATION_ADMIN')){
//ElseIf the user has ROLE_ORGANIZATION_ADMIN get all the entitiess that belong to the organization
$entitiess = $user->getOrganization()->getEntities();
} elseif($this->authorizationChecker->isGranted('ROLE_USER')) {
$entitiess = $this->managerRegistry->getRepository('AppBundle:Entities')->findByUser($user);
} else {
//if ROLE_USER is missing return null
$entitiess = null;
}
return $entities;
}
..and the error I get
Circular reference detected for service "security.authorization_checker", path: "twig.controller.exception -> twig -> security.authorization_checker -> security.access.decision_manager -> ccp.chargebacknotification_voter -> ccp.security_service".
You can try to inject security.authorization_checker (resp. app.security_service) into app.security_service (resp. app.entity_voter) using setter method:
services:
app.security_service:
class: AppBundle\Service\appSecurityService
arguments: [ "#logger", "#doctrine", "#security.token_storage" ]
calls:
- [setAuthorizationChecker, ['#security.authorization_checker']]
app.entity_voter:
class: AppBundle\Security\ChargebackNotificationVoter
public: false
tags:
- { name: security.voter }
arguments: [ "#logger", "#doctrine" ]
calls:
- [setSecurityService, ['#app.security_service']]
I use Symfony3

Gedmo SoftdeleatableListener : No mapping found for field 'deletedAt'

I want to disable Gedmo\SoftDeleatable behaviour for some of my phpunit tests in Symfony2.
I wrote these lines to remove SoftDeleatableListener:
foreach ($em->getEventManager()->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
if ($listener instanceof \Gedmo\SoftDeleteable\SoftDeleteableListener) {
$em->getEventManager()->removeEventListener($eventName, $listener);
}
}
}
But none of the listeners was identified as being an instance of SoftDeleteableListener.
So I added these lines in app/config.yml:
doctrine:
..
orm:
..
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: false
and these lines in Acme/MyBundle/Resources/config/services.yml:
services:
..
gedmo.listener.softdeleteable:
class: Gedmo\SoftDeleteable\SoftDeleteableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
But when I run php app/console doctrine:schema:update --dump-sql,
I get the following error message:
No mapping found for field 'deletedAt' on class 'Acme\MyBundle\Entity\Account'.
Any idea ?
Looks like you still need to add the property to the class.
/**
* #var \DateTime
* #ORM\Column(type="datetime")
*/
protected $deletedAt;
I have used the Trait. Only this Trait does not define the deletedAt. Where the trait TimestampableEntity defines the columns.
use SoftDeleteable;
I hope this helps

Change ES data on nested object changes automatically

types:
product:
mappings:
title: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
status:
brand.name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
brand:
type: "nested"
properties:
status: ~
persistence:
driver: orm
model: MyBundle\Entity\Product\Product
provider:
query_builder_method: customProductQueryBuilderElastica
listener: ~
finder: ~
This is my mappings for type product. customProductQueryBuilderElastica contains code which populates only products which have active status and have related brand status active. It is working perfectly if i change products from my admin.
what i want to do is when i change my brand status to inactive, all related products should be removed from ES.
For that i have used brand as nested of product and created listener for it as explained here and now i am able to change brand status for every products in my ES automatically but i want to remove such products when brand status sets to inactive. How can this be achieved in better way?.
After many tries. i finally achieved what i want. I am posting my code here and try to help others.
Thanks to #maercky. i have taken reference to his answer which is given here
Here is my config.yml file.
types:
product:
mappings:
title: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
status:
brand.name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
brand:
type: "nested"
properties:
status: ~
persistence:
driver: orm
model: XXX\MyBundle\Entity\Product\Product
provider:
query_builder_method: customProductQueryBuilderElastica
listener: ~
finder: ~
This code will go to service.yml
fos_elastica.listener.brand.product:
class: 'XXX\MyBundle\Listener\ElasticaBrandListener'
arguments:
- #fos_elastica.object_persister.search.product
- ['postPersist', 'postUpdate', 'postRemove', 'preRemove']
- #fos_elastica.indexable
calls:
- [ setContainer, [ '#service_container', #fos_elastica.object_persister.search.product ] ]
tags:
- { name: 'doctrine.event_subscriber' }
and finally this is my Listener for Brand
<?php
namespace XXX\MyBundle\Listener;
use FOS\ElasticaBundle\Doctrine\Listener as BaseListener;
use Doctrine\Common\EventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use XXX\MyBundle\Entity\Supplier\Brand;
use FOS\ElasticaBundle\Persister\ObjectPersister;
class ElasticaBrandListener extends BaseListener
{
/** #var \Symfony\Component\DependencyInjection\ContainerInterface */
private $container;
private $objectPersisterProducts;
public function setContainer(ContainerInterface $container,ObjectPersister $objectPersisterProduct) {
$this->container = $container;
$this->objectPersisterProducts = $objectPersisterProduct;
}
/**
* #param Doctrine\Common\EventArgs $eventArgs
*/
public function postUpdate(EventArgs $eventArgs)
{
/** #var $brand Brand */
$brand = $eventArgs->getEntity();
if ($brand instanceof Brand) {
$this->scheduledForUpdate[] = $brand;
foreach ($brand->getProducts() as $product) {
$brand_status = $brand->getStatus();
$product_status = $product->getStatus();
if($brand_status == 'active' && $product_status == 'active'){
$this->objectPersisterProducts->replaceOne($product);
}else{
$this->objectPersisterProducts->deleteOne($product);
}
}
}
}
}
?>
All this works for me well and so i am contributing this for others.

How to add a tag on a aliased service?

I defined the following service:
my_project.widget_listing_content_resolver:
class: MyProject\Widget\ListingBundle\Resolver\WidgetListingContentResolver
arguments:
- "#router"
tags:
- { name: my_project.widget_content_resolver, alias: Listing }
And I want to declare an alias of this service, with a different tag:
my_project.widget_domain_operations_content_resolver:
alias: my_project.widget_listing_content_resolver
tags:
- { name: my_project.widget_content_resolver, alias: DomainOperations }
But in my ContentResolverChain, the service aliased "DomainOperations" is not present. Is there a way to solve this ?
EDIT:
I tried the following configuration:
my_project.widget_listing_content_resolver:
class: MyProject\Widget\ListingBundle\Resolver\WidgetListingContentResolver
arguments:
- "#router"
tags:
- { name: my_project.widget_content_resolver, alias: Listing }
- { name: my_project.widget_content_resolver, alias: DomainOperations }
It results that the "my_project.widget_listing_content_resolver" service is only tagged as "Listing". My problem now is: "How to tag a service with multiple tag aliases"
I found the solution to that problem. The service alias was tagged as espected, but the additionnal tag was not read by my CompilerPass:
This was the errored CompilerPass:
$taggedServices = $container->findTaggedServiceIds(
'my_project.widget_content_resolver'
);
foreach ($taggedServices as $id => $attributes) {
$definition->addMethodCall(
'addResolver',
array($attributes[0]['alias'], new Reference($id))
);
}
As you see, it took only the first alias found ($attributes[0])
I had to change it to:
$taggedServices = $container->findTaggedServiceIds(
'my_project.widget_content_resolver'
);
foreach ($taggedServices as $id => $attributes) {
foreach ($attributes as $attribute) {
$definition->addMethodCall(
'addResolver',
array($attribute['alias'], new Reference($id))
);
}
}

Resources