Saving current user id in repository - symfony

I try to save initial value for user field in UserService entity. The reason is, I use this entity in EasyAdminBundle and when I build a form, I want to set a default value for user_id (ManyToOne to User entity).
init entity manager in constructor,
I override save method.
I get user from security session context and set to user service object, persist and flush.
...but I still can't see a change during save.
class UserServiceRepository extends ServiceEntityRepository
{
protected $_em;
public function __construct(RegistryInterface $registry)
{
$this->_em = $this->entityManager;
parent::__construct($registry, UserService::class);
}
// I override save method:
public function save(UserService $userService)
{
// Get current user from security:
$user = $this->get('security.token_storage')->getToken()->getUser();
// set to useService...
$userService->setUser($user);
// and persist & flush:
$this->_em->persist($userService);
$this->_em->flush();
}

// I override save method:
You're overriding non-existent method in parent, there's no save method in ServiceEntityRepository nor EntityRepository. So what's the main point of what you are doing and why you're setting default user_id in service repository?
UPDATE:
services:
my.listener:
class: UserServiceListener
arguments:
- "#security.token_storage"
tags:
- { name: doctrine.event_listener, event: prePersist }
Listener:
class UserServiceListener
{
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function prePersist(LifeCycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof UserService) {
return;
}
$entity->setUser($this->token_storage->getToken()->getUser());
}
}

Related

Add dynamic filters or pagination in ContextBuilder using API Platform

I am trying to force filters or pagination dynamically using a ContextBuilder.
For example, I want to force pagination for the group public:read:
namespace App\Serializer;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
final class FooContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
public function __construct(SerializerContextBuilderInterface $decorated)
{
$this->decorated = $decorated;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if (($resourceClass === Foo::class
$context['operation_type'] ?? null) === 'collection' &&
true === $normalization
) {
if ((isset($context['groups']) &&
in_array('public:read', $context['groups'])
) {
$context['filters']['pagination'] = true;
}
}
return $context;
}
}
services.yml:
services:
...
App\Serializer\RouteContextBuilder:
decorates: 'api_platform.serializer.context_builder'
arguments: [ '#App\Serializer\RouteContextBuilder.inner' ]
autoconfigure: false
Unfortunately, it seems that $context['filters'] is built as a later stage as it is not available in the ContextBuilder yet. $context['filters'] is available later e.g. in a DataProvider.
I tried to change the decoration priority in services.yml without success:
services:
App\Serializer\RouteContextBuilder:
...
decoration_priority: -1
How can I add dynamic filters or pagination through the context? Is there another interface that can be decorated which is called a later stage of the normalization process and before the filters are applied?
The serialization process is executed after data retrieval this can't work. Use a data Provider.

Supply validation group context with Symfony / API Platform

As I said in the title I try to supply the validation context of Sf / Api platform.
More precisely I would like to have different validation groups depending on an entity value.
If i'm a User with ROLE_PRO : then i want validate:pro and
default as validation groups.
If i'm a User with ROLE_USER : then i want default as validation
group.
I tried to create an event based on the following api-platform event but I can't find a way to supply the ExecutionContextInterface with my validation groups
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['addGroups', EventPriorities::PRE_VALIDATE],
];
}
As you can see in api-platform documentation (https://api-platform.com/docs/core/serialization/#changing-the-serialization-context-dynamically) you can manipulate validation groups dynamically with a service.
First of all, in your api-platform configuration, you have to define default validation group:
App\Class\MyClass:
properties:
id:
identifier: true
attributes:
input: false
normalization_context:
groups: ['default']
You need to define a new service which implements SerializerContextBuilderInterface
class ContextBuilder implements SerializerContextBuilderInterface
{
private SerializerContextBuilderInterface $decorated;
private AuthorizationCheckerInterface $authorizationChecker;
public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker)
{
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
if (isset($context['groups']) && $this->authorizationChecker->isGranted('ROLE_PRO') && true === $normalization) {
$context['groups'][] = 'validate:pro';
}
return $context;
}
}
Also, you need to configure your new service with a decorator
App\Builder\ContextBuilder:
decorates: 'api_platform.serializer.context_builder'
arguments: [ '#App\Builder\ContextBuilder.inner' ]
What it's happening here is:
You're overriding the ContextBuilder. First of all you create the context from request and from configuration (first line of createFromRequest method) and after this, you modify the context depeding of which user is logged.
Thanks!

Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests

Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.

Override service for specific user role

I have 3 services which should override the default services only if the user has a specific role.
Or even better. Inject the current user/security in the new services.
The service then performs the check for the user role and calls the original service.
I tried to inject security.context into it. But then $security->getToken() returns null.
In the controllers it works fine. How can i get the current user in my service? This is what i want to do:
class AlwaysVisibleNavigationQueryBuilder extends NavigationQueryBuilder
{
public function __construct(\Sulu\Component\Content\Compat\StructureManagerInterface $structureManager, $languageNamespace, SecurityContext $security)
{
if (in_array('ROLE_SULU_ADMINISTRATOR', $security->getToken()->getRoles())) {
// Show unpublished content, too
$this->published = false;
}
parent::__construct($structureManager, $languageNamespace);
}
}
At the moment of creation of the service, the securityContext was not aware of the current user. The Security is filles when the application runs and not on dependency-resolution.
The following Code works.
class AlwaysVisibleNavigationQueryBuilder extends NavigationQueryBuilder
{
protected $security;
public function __construct(\Sulu\Component\Content\Compat\StructureManagerInterface $structureManager, $languageNamespace, SecurityContext $security)
{
$this->security = $security;
parent::__construct($structureManager, $languageNamespace);
}
public function build($webspaceKey, $locales)
{
$roles = $this->security->getToken()->getRoles();
if (in_array('ROLE_SULU_ADMINISTRATOR', $roles)) {
// Show unpublished content, too
$this->published = false;
}
return parent::build($webspaceKey, $locales);
}
}
Thanks to Matteo!

Override Symfony\Component\Console\Commande\Command.php

I am developping some kind of cronjob monitoring on our symfony2 application.
I created a CommandExecution entity with a 'completed' property.
I'm using console events to create and persist this entity.
My service :
kernel.listener.console:
class: Evo\CronBundle\EventListener\ConsoleListener
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: kernel.event_listener, event: console.command, method: onConsoleCommand }
- { name: kernel.event_listener, event: console.terminate, method: onConsoleTerminate }
- { name: kernel.event_listener, event: console.exception, method: onConsoleException }
and the ConsoleListener:onConsoleCommand() and ConsoleListener:onConsoleTerminate() methods called when a command starts and ends to execute :
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$command = $event->getCommand();
$commandEntity = $this->em->getRepository('EvoCronBundle:Command')->findOneBy(['name' => $command->getName()]);
$commandExecution = new CommandExecution();
$commandExecution->setCommand($commandEntity);
$this->em->persist($commandExecution);
$this->em->flush();
// here I want to pass my entity to the command, so I can get it back in the onConsoleTerminate() method
$command->setCommandExecution($commandExecution);
}
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
$command = $event->getCommand();
// here, retrieve the commandExecution entity passed in onConsoleCommand() method
$commandExecution = $command->getCommandExecution();
$commandExecution->setCompleted(true);
$this->em->flush();
}
As you can see in these methods, I would like to add a commandExecution property to the Symfony Component Console\Command\Command.php, so I can pass in my commandExecution entity and change its status.
Do I have to override this component ? If yes, how ? Or can I do it in a simpler way ?
Add the commandExecution property in your ConsoleListener
protected $commandExecution = null;
Then set it in your onConsoleCommand() method
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$command = $event->getCommand();
$commandEntity = $this->em->getRepository('EvoCronBundle:Command')->findOneBy(['name' => $command->getName()]);
$commandExecution = new CommandExecution();
$commandExecution->setCommand($commandEntity);
$this->em->persist($commandExecution);
$this->em->flush();
$this->commandExecution = $commandExecution;
}
Then you can access to it in your onConsoleTerminate() method
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
$command = $event->getCommand();
// here, retrieve the commandExecution entity passed in onConsoleCommand() method
$commandExecution = $this->commandExecution;
$commandExecution->setCompleted(true);
$this->em->flush();
}
Don't forget to test if commandExecution value is null in onConsoleTerminate() method

Resources