Getting the translation text on Symfony controller - symfony

I have translation file named as messages.en.yml file on the location <My-Bundle>/Resources/translations as follow:
company:
messages:
schedule:
success: Schedule saved successfully
failed: Something went wrong on saving schedule
Now I need to call this message by key on here:
$this->get('session')->getFlashBag()->add(
'success',
'%company.messages.schedule.success%'
);
I try many ways but not able to fix this.

try this
$this->get('session')->getFlashBag()->add(
'success',
$this->get('translator')->trans('company.messages.schedule.success')
);
also, need to enable in configuration:
framework:
default_locale: 'en'
translator:
fallbacks: ['en']
Check official documentation: Symfony Basic Translation

Unfortunately it is impossible to translate a key translation in the flash bag ...
You could create a custom service with as dependencies
Translator
The session
And then perform the translation yourself before adding the message to the flashBag
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Translation\Translator;
class FlashBagTranslator
{
/** #var Translator $translator */
private $translator;
/** #var FlashBag $flashBag */
private $flashBag;
public function __construct(Translator $translator, Session $session)
{
$this->translator = $translator;
$this->flashBag = $session->getFlashBag();
}
public function addMessage($type, $translationKey, array $parameters = [], $domain = null, $locale = null)
{
$message = $this->translator->trans($translationKey, $parameters, $domain, $locale);
if ($message === $translationKey) {
// Your translation isn't findable, do something :)
return false;
}
$this->flashBag->add($type, $message);
return true;
}
}
A little disappointing, isn't it ?

Related

Get rootDir inside beforeSend function in Symfony to intercept Sentry event

I have a service who intercepts the events of Sentry. I'm using a function called beforeSend.
I would to load a json file who contains the data to scrub or to keep. It's a service and I build my constructor with a similar way than others, but the "$this" context doesn't exist when I'm in the debugger in this function.
The kernel is in the Global variables, but I think it's not a good idea... I only would to get the root dir and it's all, but I don't find how to do this in this class... The constructor seems useless.
Someone could help me with a similar experience ?
EDIT :
Service :
namespace App\Services;
use Sentry\Event;
use Symfony\Component\HttpKernel\KernelInterface;
class SentryBeforeSendService
{
private static $rootDir;
public function __construct(KernelInterface $kernel)
{
self::$rootDir = $kernel->getRootDir();
}
/**
* Scrubs the value of all TARGET_PARAMETERS
* in the event's request.
*
* #param Event $event
*
* #return Event
*/
public function beforeSend(Event $event)
{
$rootDir = self::$rootDir;
$event->setRequest(self::scrubRequest($event->getRequest(), $rootDir));
try {
$composerData = json_decode(file_get_contents($rootDir.'/../composer.json'), true);
$version = $composerData['version'];
$event->setRelease($version);
} catch (\Exception $e) {
//do nothing
}
return $event;
}
/**
* Scrubs GET and POST parameters
*
* #param array $request
*
* #return array
*/
private static function scrubRequest(array $request, $rootDir)
{
// DO SOMETHING WITH $rootDir to scrub data with external file
}}
services.yml :
app.service.sentry_before_send:
class: 'App\Services\SentryBeforeSendService'
arguments: ['#kernel']
config_prod.yml :
sentry:
dsn: "%sentry_dsn%"
options:
environment: "%sentry_environment%"
# release: '%env(VERSION)%' #overridden from composer.json version in SentryBeforeSendService::beforeSend
before_send: 'App\Services\SentryBeforeSendService::beforeSend'
But it seems the construct never happened.
Thank you very much.
I was unable to inject a parameter, but I found a way to get the project_root from my method. Half victory ...
config_prod.yml:
sentry:
dsn: "%sentry_dsn%"
options:
environment: "%sentry_environment%"
# release: '%env(VERSION)%' #overridden from composer.json version in SentryBeforeSendService::beforeSend
before_send: 'App\Services\SentryBeforeSendService::beforeSend'
project_root: '%kernel.project_dir%'
Service :
<?php
namespace App\Services;
use Sentry\Event;
use Sentry\State\Hub;
class SentryBeforeSendService
{
private static $projectRoot;
/**
* Scrubs the value of all TARGET_PARAMETERS
* in the event's request.
*
* #param Event $event
*
* #return Event
*/
public function beforeSend(Event $event)
{
$sentryClient = Hub::getCurrent()->getClient();
self::$projectRoot = $sentryClient->getOptions()->getProjectRoot();
$event->setRequest(self::scrubRequest($event->getRequest()));
try {
$composerData = json_decode(file_get_contents(self::$projectRoot.'/composer.json'), true);
$version = $composerData['version'];
$event->setRelease($version);
} catch (\Exception $e) {
//do nothing
}
return $event;
}}
Hope it'll help someone else.
Thank you for answers.
You can inject the kernel.project_dir parameter in your service constructor with a named parameter:
In your services.yml file:
services:
_defaults:
bind:
string $kernelProjectDir: '%kernel.project_dir%'
Then in your service:
public function __construct(string $kernelProjectDir)
{

API Platform documentation for custom normalizer

I am using API Platform and I followed this tutorial to add a custom serialized field which relies on an external service. The avatar property needs to be exposed using the Packages class.
<?php
namespace App\Serializer;
use App\Entity\User;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpFoundation\UrlHelper;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class UserNormalizer implements ContextAwareNormalizerInterface
{
/**
* #var Packages
*/
private $packages;
/**
* #var UrlHelper
*/
private $urlHelper;
/**
* #var ObjectNormalizer
*/
private $normalizer;
public function __construct(Packages $packages, UrlHelper $urlHelper, ObjectNormalizer $normalizer)
{
$this->packages = $packages;
$this->normalizer = $normalizer;
$this->urlHelper = $urlHelper;
}
public function normalize($user, $format = null, array $context = [])
{
/** #var array */
$data = $this->normalizer->normalize($user, $format, $context);
$avatar = null;
if ($user->getAvatarFilename()) {
$path = $this->packages->getUrl('uploads/avatars/'.$user->getAvatarFilename());
$avatar = $this->urlHelper->getAbsoluteUrl($path);
}
$data['avatar'] = $avatar;
return $data;
}
public function supportsNormalization($data, $format = null, array $context = [])
{
return $data instanceof User;
}
}
The problem is that this property doesn't appear in the documentation as it's added by the custom normalizer. How can I add documentation for it (eg. type, example etc...)?
if this still relevant for you or somebody else:
You can add a custom field to the openapi model with this: https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification
You need to add $avatar field to your entity.
Like you suggest in the comments you could add a not mapped property to your entity and document it in the annotations, and yes it's hacky like they said here!... it is suggested in the SymfonyCast tutorials
Just remember the downside to this approach: our documentation has no
idea that this isMe field exists. If we refresh this page and open the
docs for fetching a single User... yep! There's no mention of isMe. Of
course, you could add a public function isMe() in User, put it in the
user:read group, always return false, then override the isMe key in
your normalizer with the real value. That would give you the custom
field and the docs. But sheesh... that's... getting kinda hacky.

Make security available for doctrine onFlush within functional test

I'm actually testing my api code written with:
symfony 4
api-platform
FOS User
JWT
I use codeption for my tests and everything is ok so far.
For several entities, I fire onFlush doctrine callback and it's working just fine when authenticated from my front application in react.
At this point I get my authenticated user in the callback via an injected security component.
However when doing the same things via codeception, even if onFlush is fired, I'm not able to retrieve my user neither the token via the security injection.
I tried to inject the token instead, also the entire service container, none has worked.
This is my OnFlush class:
{
/**
* #var Security
*/
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function onFlush(OnFlushEventArgs $args): void
{
$user = $this->security->getUser();
...
And here how I set my authorization header in codeception test:
$I->haveHttpHeader('Authorization', 'Bearer ' . $token);
$I->sendPUT(
'/entity/uuid.json',
[
'attribute' => $value
]
);
I would like to get the user having the specified token whe executing the test in the callback.
PS: Before executing the PUT test, I did the same thing with GET and just got the related entities, when I remove Authorization header I do get all users entities. It seems that it's not working only in callback.
Thanks
After a lot research, it's obviously a codeception problem.
I ended up making this particular test with phpunit as codeception couldn't load the service container in doctrine events.
If you try to edit your services.yaml file and to execute your tests, it works on first time as the service container is re-built (re-cached).
But once cached, it will always return an empty container (without tokenstrorage, security, ...).
Creating a helper method to provide the user wouldn't work neither, I'll leave the code here in case of need:
/**
* Create user or administrator and set auth cookie to client
*
* #param string $user
* #param string $password
* #param bool $admin
*/
public function setAuth(string $user, string $password, bool $admin = false): void
{
/** #var Symfony $symfony */
try {
$symfony = $this->getModule('Symfony');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Symfony\'');
}
/** #var Doctrine2 $doctrine */
try {
$doctrine = $this->getModule('Doctrine2');
} catch (ModuleException $e) {
$this->fail('Unable to get module \'Doctrine2\'');
}
$user = $doctrine->grabEntityFromRepository(User::class, [
'username' => $user
]);
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$symfony->grabService('security.token_storage')->setToken($token);
/** #var Session $session */
$session = $symfony->grabService('session');
$session->set('_security_main', serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$symfony->client->getCookieJar()->set($cookie);
}
Creating a phpunit test with below code would do the job just fine:
/**
* #param string $method
* #param string $url
* #param array $content
* #param bool $authorization
*/
protected static function performRequest (string $method, string $url, array $content = [], $authorization = false): void
{
$headers = [
'CONTENT_TYPE' => 'application/json',
];
if ($authorization)
{
$headers = array_merge($headers, [
'HTTP_AUTHORIZATION' => 'Bearer ' . self::$token
]);
}
self::$client->request(
$method,
'/api/' . $url,
[],
[],
$headers,
json_encode($content)
);
}
I got exactly the same problem. Here is my solution:
<?php
use Codeception\Stub;
use Codeception\Module\Doctrine2;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Security;
class YourEventSubscriberCest
{
/**
* #var Security
*/
protected Security $security;
/**
* #param FunctionalTester $I
*/
public function _before(FunctionalTester $I, Doctrine2 $doctrine2)
{
$user = new User();
$security = Stub::makeEmpty(Security::class, [
'getUser' => $user,
]);
$backup = $this->replaceSecurity($doctrine2, $security);
if ($backup instanceof Security) {
$this->security = $backup;
}
}
public function _after(FunctionalTester $I, Doctrine2 $doctrine2)
{
$this->replaceSecurity($doctrine2, $this->security);
}
protected function replaceSecurity(Doctrine2 $doctrine2, object $newSecurity): ?object
{
$listeners = $doctrine2->_getEntityManager()->getEventManager()->getListeners('onFlush');
foreach ($listeners as $listener) {
if ($listener instanceof YourEventSubscriber) {
$reflection = new \ReflectionObject($listener);
$property = $reflection->getProperty('security');
$property->setAccessible(true);
$oldSecurity = $property->getValue($listener);
$property->setValue($listener, $newSecurity);
return $oldSecurity;
}
}
}
}

How to use JMSSerializer with symfony 4.2

i am building an Api with symfony 4.2 and want to use jms-serializer to serialize my data in Json format, after installing it with
composer require jms/serializer-bundle
and when i try to use it this way :
``` demands = $demandRepo->findAll();
return $this->container->get('serializer')->serialize($demands,'json');```
it gives me this errur :
Service "serializer" not found, the container inside "App\Controller\DemandController" is a smaller service locator that only knows about the "doctrine", "http_kernel", "parameter_bag", "request_stack", "router" and "session" services. Try using dependency injection instead.
Finally i found the answer using the Symfony serializer
it's very easy:
first : istall symfony serialzer using the command:
composer require symfony/serializer
second : using the serializerInterface:
.....//
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
// .....
.... //
/**
* #Route("/demand", name="demand")
*/
public function index(SerializerInterface $serializer)
{
$demands = $this->getDoctrine()
->getRepository(Demand::class)
->findAll();
if($demands){
return new JsonResponse(
$serializer->serialize($demands, 'json'),
200,
[],
true
);
}else{
return '["message":"ooooops"]';
}
}
//......
and with it, i don't find any problems with dependencies or DateTime or other problems ;)
As I said in my comment, you could use the default serializer of Symfony and use it injecting it by the constructor.
//...
use Symfony\Component\Serializer\SerializerInterface;
//...
class whatever
{
private $serializer;
public function __constructor(SerializerInterface $serialzer)
{
$this->serializer = $serializer;
}
public function exampleFunction()
{
//...
$data = $this->serializer->serialize($demands, "json");
//...
}
}
Let's say that you have an entity called Foo.php that has id, name and description
And you would like to return only id, and name when consuming a particular API such as foo/summary/ in another situation need to return description as well foo/details
here's serializer is really helpful.
use JMS\Serializer\Annotation as Serializer;
/*
* #Serializer\ExclusionPolicy("all")
*/
class Foo {
/**
* #Serializer\Groups({"summary", "details"})
* #Serializer\Expose()
*/
private $id;
/**
* #Serializer\Groups({"summary"})
* #Serializer\Expose()
*/
private $title;
/**
* #Serializer\Groups({"details"})
* #Serializer\Expose()
*/
private $description;
}
let's use serializer to get data depends on the group
class FooController {
public function summary(Foo $foo, SerializerInterface $serialzer)
{
$context = SerializationContext::create()->setGroups('summary');
$data = $serialzer->serialize($foo, json, $context);
return new JsonResponse($data);
}
public function details(Foo $foo, SerializerInterface $serialzer)
{
$context = SerializationContext::create()->setGroups('details');
$data = $serialzer->serialize($foo, json, $context);
return new JsonResponse($data);
}
}

Symfony 2 twig bad credentials trans bug

Good day.
I'm having 2 projects in Symfony 2. In first, i'm using {{ error.message|trans }} to translate
"Bad credentials" authorization error message. It calls Translator class trans method from Component\Translation\Translator.php to translate this string.
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
if (null === $locale) {
$locale = $this->getLocale();
}
if (null === $domain) {
$domain = 'messages';
}
if (!isset($this->catalogues[$locale])) {
$this->loadCatalogue($locale);
}
return strtr($this->catalogues[$locale]->get((string) $id, $domain), $parameters);
}
It works correctly, and returns translated string from messages.domain.yml file. In second project, i'm using same settings, and trying to translate error string in twig in the same way. But it doesn't working, and the original 'Bad credentials' string is returned. After inspection, i found, that Component\Translation\IdentityTranslator.php class is creating(instead of Translator.php), and it's method trans is being called:
/**
* IdentityTranslator does not translate anything.
*
* #author Fabien Potencier <fabien#symfony.com>
*
* #api
*/
class IdentityTranslator implements TranslatorInterface
{
/// More code here
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
return strtr((string) $id, $parameters);
}
}
So, it just returns the original message. What could be potential source of such problem? Is it Symfony bug? (Both projects uses Symfony 2.4.4)

Resources