getUser method from Symfony Security can't get my User Repository - symfony

My problem is the $this->getUser(); method (called from Controller) from Symfony Security can't get my User Repository.
The "App\Model\ODM\Repository\UserRepository" document repository implements "Doctrine\Bundle\MongoDBBundle\Repository\ServiceDocumentRepositoryInterface", but its service could not be found. Make sure the service exists and is tagged with "doctrine_mongodb.odm.repository_service".
I have a CompilerPass that get my Repositories and add them to Container Definitions depending on some env value.
I add the Repo with "generic" Interfaces (valid for both ODM and ORM) as keys AND with the Repo FQCN as keys, so they should be accessible via both the generic Interface and the true Repo name, but I'm working only with ODM for now.
Also, I "tag" my repo with doctrine_mongodb argument, to extends the ServiceDocumentRepository baseClass.
I don't include the compilerPass because the class is quite "heavy", but if needed I'll include it.
Here is my UserRepo
class UserRepository extends BaseRepository implements RepositoryInterface, UserRepositoryInterface
{
/**
* UserRepository constructor.
*
* #param ManagerRegistry $managerRegistry
*/
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry, User::class);
}
...
My BaseRepo (just a few methods in here, just the extends is interesting)
abstract class BaseRepository extends ServiceDocumentRepository implements RepositoryInterface
{
...
It's working great "in-app", I could create a User on a registration page, but using $guardHandler->authenticateUserAndHandleSuccess() method after register, i'm redirected to my homepage where the $this->getUser(); is, and then I have the error message posted above.
After some research, it seems the problem is in Doctrine\Bundle\MongoDBBundle\Repository\ContainerRepositoryFactory
The container of this class doesn't seem to have any of my Repo definition.
I surely missed something but I don't know where.
I remember making something similar few years ago but extending DocumentRepository and not ServiceDocumentRepository.
If you need any other information, let me know.

I feel really dumb about this question now that I found the solution ...
It was LITTERALY in the error message, I just had to add doctrine_mongodb.odm.repository_service to my Repo's Definition, in my Compiler Pass
...
$definition->addArgument(new Reference('doctrine_mongodb'));
$definition->addTag('doctrine_mongodb.odm.repository_service');
...
Thanks to the people who looked at my problem and sorry for wasting your time.
I Hope this answer could help others peoples that register Repo in a CompilerPass
(I will set this answer as the solution asap, in 2 days)

Did you enable autowire in your services.yml?
services:
_defaults:
autowire: true
autoconfigure: true
App\Repository\:
resource: '../src/Repository/*'

Ill put my solution in case someone else has this problem and the one above doesn't work. For me what worked was to put the whole path to the repository when it was declared in the Document class. Like so:
// /src/Document/User
/**
* #MongoDB\Document(collection="users", db="database", repositoryClass=\App\Repository\UserRepository::class)
* #MongoDBUnique(fields="email")
*/
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
I didn'nt have to add anything in my Compiler Pass. I added the full path to the repository class in its declaration and it instantly worked. Hope that it helps.

Related

Symfony and Api-Platform: Use Doctrine and other Services outside controllers

Hello Stackoverflow
We have a problem with Symfony and API-PLATFORM. We tried a lot of things, but couldn't find any solution. We had difficulties using the answers already given on stackoverflow and the git issues they have that are related to this question. This is why this is not a duplicate in any form, since those sadly didn't help us.
The problem is as follows. We have the standard structure in our src folder of api-platform.
- src
-- Annotation
-- Controller
-- Doctrine
-- Emails
-- Entity
-- EventListener
-- EventSubscriber
-- Filters
-- Repository
-- Security
-- Utils
So here is were our problem starts. We have a few classes which are just some utilities for easy use. Those are in the utils folder. This question is about a class in the Utils folder named Phone. (namespace App\Utils\Phone). The thing here is, that we need to use the EntityManager in this Utils class, because we need to store some of the data (Temporarily). We'll do this with the following code (Which works in controllers)
$em = $this->getDoctrine()->getManager();
$em->persist($smsStore);
$em->flush();
Now the error hits: Doctrine doesn't even exist here. It cannot be found. The question here is: How can we use the Doctrine EntityManager outside of the Controllers? We tried extending by using the default AbstractController, but this cannot be correct and doesn't work.
The same thing happens when we want to use our security class to get the current user. ($this->security->getUser()). Even when we import it like with a use. use Symfony\Component\Security\Core\Security;.
We hope we were clear with this question. We're a bit desperate because we're still looking for a solution after a few days. We use PHP version 7.1.3 and API-PLATFORM version 1.1 (composer.json)
EDIT 1: The does not exist is about an undefined method (getDoctrine). We tried a few things to implement this. For example using the AbstractController to extend the class. Another solution that worked is really pass the DoctrineManager with the function. This is however not a good practice.
EDIT 2: The AuthController snippet as requested. It shows how we use the Phone Utils. Could it be that I need to implement the EntityInterface at this function surrounding the following code?
$phone = new Phone();
$phone->overwriteUser($this->getUser());
$phone->newRecipient("4917640733908");
$phone->setBody("Hello, this is a test.");
Entity manager could be handle with the dependency injection(DI) pattern. This pattern is fully integrated in Symfony. This is the fun and magic aspect of Symfony.
All classes implementing an interface are available with dependency injections. With Symfony4+, the autoconfigure is activated. It seems to be complex, but for the final developer, it's pretty easy. Here is a simple example to do it with your Phone classes which seems to be a model where you have implemented you business logic.
class Phone
{
private $entityManager;
//The entity manager will be provided to your constructor with the dependency injection.
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function save($smsStore)
{
//you own logic go here
//here is the magic part, the entityManager is available, connected, ready to use.
$this->entityManager->persist($smsStore);
$em->flush();
}
}
When you'll call the Utils classes method, the DI will construct it automatically and you will be able to use its property, the entity manager in this case.
If you want to use you class in a controller, you can do it by adding the phone class in its constructor:
class AuthController extends AbstractController
{
public function __construct(Phone $phoneUtils)
{
$this->phoneUtils = $phoneUtils;
}
public function someAction()
{
//your own logic here
//here is the magic part, the phone Utils is ready, it imports the entitymanager
//when you want to save your smsStore, simply call it
$this->phoneUtils->save($smsStore);
}
}
You are using Api-Platform, no problem! You can reproduce this design pattern in other classes like your DataPersisters, or in your EventListeners. If you look at this example in the ApiPlateform documentation, you can see how author get the Swiftmailer manager in his event listener. Simply replace the swiftmailer by EntityManagerInterface or the Phone utils classes.
(In a controller, it exists a simpliest way to retrieve the phone utils class)
class AuthController extends AbstractController
{
//Do That: ADD Phone $phoneUtils as an argument in the method
public function someAction(Phone $phoneUtils)
{
//your own logic here
//Do not do that: $phoneUtils = new Phone();
//Magic! when you want to save your smsStore, simply call it
$phoneUtils->save($smsStore);
}
}
If you want to get an autowired class (like the security layer), you could do it. To find which classes could be autowired, there are some tips:
symfony console debug:autowiring
symfony console debug:autowiring Security
symfony console debug:autowiring AuthorizationChecker
It will return all the classes corresponding to your search.
(If you do not use the symfony executable, you could replace it by php bin/console debug:autowiring)

Symfony 4 - Custom Folder structure and Services

I'm looking to implement a folder structure recommended by Nikola Posa.
The structure I would like is something like what is below.
src/
Domain/
User/
UserEntity.php
UserController.php
Pages/
DefaultPageController.php
The idea is to logically group/namespace features or similar content. I seem to be getting this error:
The file "../src/Controller" does not exist (in: /Users/dev/Sites/web/html/sandbox/php/crud/config) in /Users/dev/Sites/web/html/sandbox/php/crud/config/services.yaml (which is loaded in resource "/Users/dev/Sites/web/html/sandbox/php/crud/config/services.yaml").
I'm not sure how important it is to wire up these as services. If I comment out the App\Controller property of the services.yaml, it goes away.
How can I load controllers in service.yaml with a src/Domain/Feature/FeatureController.php structure?
You could of course go old school and just define each controller service individually:
# config/services.yaml
Domain\Feature\FeatureController:
tags: ['controller.service_arguments']
However, once you get used to autowire then spelling out each service is a pain. As an alternative you can use the autoconfigure capability to add the controller tag to selected classes. Start by declaring an empty interface and have your controllers implement it:
interface ControllerInterface {}
class SomeController implements ControllerInterface
Then adjust src/Kernel.php
# src/Kernel.php
class Kernel {
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(ControllerInterface::class)
->addTag('controller.service_arguments');
Of course this just takes care of the controller issue. You will probably encounter a number of other autowire related issues.
I would go and create a Controller interface like Cerad suggest. However, since Symfony 3.3 you don't have to touch the kernel:
services:
_instanceof:
YourApp\Ui\ControllerInterface:
public: true
tags: ['controller.service_arguments']
Et voila.

Doctrine lifecycleCallbacks strange behaviour

I have defined lifecycleCallbacks in yaml as follows:
lifecycleCallbacks:
prePersist: [setCreatedAtValue]
preUpdate: [setUpdatedAtValue]
The above has generated entities with the respective functions as follows:
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
if($this->created_at == null)
{
$this->created_at = new \DateTime();
}
}
Which looks all fine, right? However, when I try to open the sonata admin page, I get the following error
[Semantical Error] The annotation "#ORM\PrePersist" in method AppBundle\Entity\Article::setCreatedAtValue() was never imported. Did you maybe forget to add a "use" statement for this annotation?
I have never encountered this before and a bit confused about what to do. I am using symfony 2.7.6, Doctrine ORM version 2.5.1, Sonata Admin 2.3.7
Any help will be greatly appreciated
Since you defined your callbacks using yaml, you donĀ“t need to define them again using annotations. Just remove the comments with the #ORM\PrePersist block before the function and everything will be fine.
If you wanted to use annotations to define your doctrine properties, you would need to import them before you can use them. To do so you would need to add this line at the beginning of your file:
use Doctrine\ORM\Mapping as ORM;
Same issue came with me.
In my case everything worked well until I did not Serialize my object in JsonResponse.
So problem was that previously I was not using that entity class (which was giving error) for sending JsonResponse, as soon as I tried to prepare JsonResponse containing that class, JsonResponse failed to serialize my class as it hadn't implemented Serializable interface.
This will happen if you will fetch objects with methods like findAll or findBy which returns Entity objects instead of php array.
So I just skipped those native methods and write doctrine query for fetching data.
You can also implement Serializable interface.

Doctrine EntityManagerDecorator

I've created custom decorator for EntityManager and now when I'm doing doctrine->getManager(), then I can get my custom manager class, but inside repository class I still have native EntityManager how can I fix this. Or maybe there is another way to set something inside repository classes from container?
Decorator calls getRepository on $wrapped(EntityManager) and then $wrapped pass $this inside RepositoryFactory $this == $wrapped == EntityManager
My solution is:
public function getRepository($className)
{
$repository = parent::getRepository($className);
if ($repository instanceof MyAbstractRepository) {
$repository->setDependency();
}
return $repository;
}
There are a couple of approaches:
Copy the static EntityManager::createRepository code to your entity manager class and adjust it accordingly. This is fragile since any change to the EntityManager code might break your code. You have to keep track of doctrine updates. However, it can be made to work.
A second approach is to define your repositories as services. You could then inject your entity manager in the repository. Bit of a hack but it avoids cloning the createRepository code.
The third approach is the recommended approach. Don't decorate the entity manager. Think carefully about what you are trying to do. In most cases, Doctrine events or a custom base repository class can handle your needs. And it saves you from fooling around with the internals.
One option would be to override the entity manager service classes or parameters via a compiler pass.

Injecting the Doctrine Entity Manager in services - Bad practice?

Using https://insight.sensiolabs.com to scan / check my code, I get the following warning:
The Doctrine Entity Manager should not be passed as an argument.
Why is it such a bad practice to inject the Entity Manager in a service? What is a solution?
With respect to the comment that repositories cannot persist entities.
class MyRepository extends EntityRepository
{
public function persist($entity) { return $this->_em->persist($entity); }
public function flush () { return $this->_em->flush (); }
I like to make my repositories follow more or less a "standard" repository interface. So I do:
interface NyRepositoryInterface
[
function save($entity);
function commit();
}
class MyRepository extends EntityRepository implements MyRepositoryInterface
{
public function save ($entity) { return $this->_em->persist($entity); }
public function commit() { return $this->_em->flush (); }
This allows me to define and inject non-doctrine repositories.
You might object to having to add these helper functions to every repository. But I find that a bit of copy/paste is worth it. Traits might help here as well.
The idea is move away from the whole concept of an entity manager.
I am working on a quite a large project currently and have recently started following the approach with repositories that can mutate data. I don't really understand the motivation behind having to inject EntityManager as a dependency which is as bad as injecting ServiceManager to any class. It is just a bad design that people try to justify. Such operations like persist, remove and flush can be abstracted into sth like AbstractMutableRepository that every other repository can inherit from. So far it has been doing quite well and makes code more readable and easier to unit test!
Show me at least one example of a service that has EM injected and unit test for it looked correctly? To be able to unit test sth that has EM injected is more about testing implementation than anything else. What happens is then you end up having so many mocks set up for it, you cannot really call it a decent unit test! It is a code coverage hitter, nothing more!

Resources