Symfony: Inject Service into User Object [duplicate] - symfony

This question already has answers here:
Symfony 2.0 getting service inside entity
(3 answers)
Closed 6 years ago.
How can I inject a service into the current user object?
With current user object I mean this:
//in controller
$user = $this->get('security.token_storage')->getToken()->getUser();
//later I want to do this in the user class:
function getData() {
$data = $this->getSomeData();//normal function
$data += $this->getMyService()->getSomeMoreData();//invoke service function
}

This is very bad practice as your Entity classes should be simple objects that are responsible only for manipulating their own data.
If, however, you are determined to do this you can create the container property in your User class and inject it in the Doctrine postLoad event, see the documentation.
I would stress again that you shouldn't do this though. I've seen this done before and it leads to over-complex classes that can have multiple responsibilities and are impossible to test.

Related

Differences between different methods of Symfony service collection

For those of you that are familiar with the building of the Symfony container, do you know what is the differences (if any) between
Tagged service Collector using a Compiler pass
Tagged service Collector using the supported shortcut
Service Locator especially, one that collects services by tags
Specifically, I am wondering about whether these methods differ on making these collected services available sooner or later in the container build process. Also I am wondering about the ‘laziness’ of any of them.
It can certainly be confusing when trying to understand the differences. Keep in mind that the latter two approaches are fairly new. The documentation has not quite caught up. You might actually consider making a new project and doing some experimenting.
Approach 1 is basically an "old school" style. You have:
class MyCollector {
private $handlers = [];
public function addHandler(MyHandler $hamdler) {
$handlers[] = $handler;
# compiler pass
$myCollectorDefinition->addMethodCall('addHandler', [new Reference($handlerServiceId)]);
So basically the container will instantiate MyCollector then explicitly call addHandler for each handler service. In doing so, the handler services will be instantiated unless you do some proxy stuff. So no lazy creation.
The second approach provides a somewhat similar capability but uses an iterable object instead of a plain php array:
class MyCollection {
public function __construct(iterable $handlers)
# services.yaml
App\MyCollection:
arguments:
- !tagged_iterator my.handler
One nice thing about this approach is that the iterable actually ends up connecting to the container via closures and will only instantiate individual handlers when they are actually accessed. So lazy handler creation. Also, there are some variations on how you can specify the key.
I might point out that typically you auto-tag your individual handlers with:
# services.yaml
services:
_instanceof:
App\MyHandlerInterface:
tags: ['my.handler']
So no compiler pass needed.
The third approach is basically the same as the second except that handler services can be accessed individually by an index. This is useful when you need one out of all the possible services. And of course the service selected is only created when you ask for it.
class MyCollection {
public function __construct(ServiceLocator $locator) {
$this->locator = $locator;
}
public function doSomething($handlerKey) {
/** #var MyHandlerInterface $handler */
$handler = $serviceLocator->get($handlerKey);
# services.yaml
App\MyCollection:
arguments: [!tagged_locator { tag: 'app.handler', index_by: 'key' }]
I should point out that in all these cases, the code does not actually know the class of your handler service. Hence the var comment to keep the IDE happy.
There is another approach which I like in which you make your own ServiceLocator and then specify the type of object being located. No need for a var comment. Something like:
class MyHandlerLocator extends ServiceLocator
{
public function get($id) : MyHandlerInterface
{
return parent::get($id);
}
}
The only way I have been able to get this approach to work is a compiler pass. I won't post the code here as it is somewhat outside the scope of the question. But in exchange for a few lines of pass code you get a nice clean custom locator which can also pick up handlers from other bundles.

How to update/replace an existing object/entity in Doctrine without using merge?

Currently I am working with Doctrine 2 within a Symfony 2.8 project. The online Data can be sync with mobile apps. When the online project receives data from the mobile apps, the received entities are de-serialized from JSON and merged into the online database. Using $entityManger->merge($receivedEntity) automatically ensures that new entities are inserted and existing entities are updated.
This works fine EntityManager::merge will no longer be supported in Doctrine 3.
To prepare for this I would like to handle the merging manually:
public function mergeEntity($receivedEntity, $class) {
$repo = $this->em->getRepository($class);
$existingEntity = $repo->findOneById($receivedEntity->getId());
if (!$existingEntity) {
// Insert NEW entity
$this->em->persist($receivedEntity);
$this->em->flush();
} else {
// Update EXISTING entity
$existingEntity->setProperty1($receivedEntity->getProperty1());
$existingEntity->setProperty2($receivedEntity->getProperty2());
...
$existingEntity->setPropertyN($receivedEntity->getPropertyN());
$this->em->persist($existingEntity);
$this->em->flush();
}
}
While this work, it is quite cumbersome and not very flexible. Every time the entity class changes, e.g. a property is added, removed or updated, the merge method would have to updated as well. Additionally the method only works for one specific class and every entity class would need its own merge method.
While reflections could be used to limit these problems it is still far more complex than the existing EntityManager::merge...
Isn't it possible somehow to replace an existing entity with a new "version"?
// Is something like this possible/available?
$this->em->replaceEntity($receivedEntity);
In Short: Is creating completely custom update/replace methods the correct way to update existing entities? Or are there any build in features (beside merge) which can be used to achieve this?

How to map entity associations with doctrine 2 to retrieve specific data?

Introduction:
The title of the question is a bit generic because I was not able to make a more specific one, but now I will try to give a better description of my problem.
It's my first big project with Symfony (>=3.*) and Doctrine ORM (>=2.5) and I hope to get some tips about what to keep in mind to improve my understanding about modelling entity associations.
Minimized Use Case (ps: CodeStyled words are Doctrine Entities):
I have the AccountType entity where are defined 4 account types.
A User can register his credentials and must choose one AccountType.
I have 5 profile types in the relative entities ProfileOne, ProfileTwo, ProfileThree, ProfileFour, ProfileFive.
The User with AccountType:A can create only 1 ProfileOne and only 1 ProfileTwo.
The User with AccountType:B can create unlimited ProfileOne and ProfileTwo.
The User with AccountType:C can create unlimited ProfileFour.
The User with AccountType:D can create only 1 ProfileFive.
Actual Entity Associations:
User have a unidirectional OneToOne with AccountType.
The Question (UPDATED):
I'm forced to manage the logic outside (es: in a repository) or exist a way to map entities to retrieve the right data based on the AccountType (as showed in the use-case)?
Maybe I've to create a ProfileAccountA, ProfileAccountB, ProfileAccountC and a ProfileAccountD, where to store the relative associations based on the AccountType to then be able to have something like $profile = $user->getProfile() where inside the function getProfile() I manage the logic to returns the right data (like in a Factory class)? If Yes, is this a common and valid approach or there are better alternatives for this use-case?
Create a class for each account type (eg.: AccountTypeA, AccountTypeB, etc.). The properties, associations, and rules that tell which and how many profiles a User can have should be encapsulated in those classes.
Your associations will look like this: User has an (oneToOne) Account that has one or more (oneToMany) Profile
You will probably want an AccountInterface:
interface AccountInterface
{
public function getProfiles(): Collection;
}
An example of an Account class. It's better naming them accordingly to their nature (FreeAccount, MasterAccount...):
class MasterAccount implements AccountInterface
{
private $masterProfile;
private $expirationDate;
private $anotherAccountRelatedProperty;
public function getProfiles(): Collection
{
return new ArrayCollection([$this->masterProfile]);
}
}
Any property, association, or behavior related to the account should live in these classes.
In order to get the User profiles it should delegate to the account:
//User class
private $account;
public function getProfiles(): Collection
{
return $this->account->getProfiles();
}
This is a decent OOP approach to be used as guideline in your situation.

Where should EntityManager::persist() and EntityManager::flush() be called

I'm developing a medium scale application using Symfony2 and Doctrine2. I'm trying to structure my code according to the SOLID principles as much as possible. Now here is the question:
For creating new Entities, I use Symfony Forms with proxy objects i.e: I don't bind the form directly to my Entity, but to some other class that will passed to some service which will take the needed action based on the received data, i.e: the proxy class serves as a DTO to that service which I will call the Handler. Now considering the Handler doesn't have a dependency on the EntityManager, where should I do calls to EntityManager::persist() and EntityManager::flush()? I am usually comfortable with putting flush in the controller but I'm not so sure about persist since the controller shouldn't assume anything about what the Handler does, and maybe Handler::handle (the method that the form data is passed to) does more than just persist a new Entity to the database. One Idea is to create some interfaces to encapsulate flush and persist and pass them around, which will act as wrappers around EntityManager::flush() and EntityManager::persist(), but I'm not so sure about it since EntityManager::flush() might create unwanted consequences. So Maybe I should just create an interface around persist.
So My question is where and how to make the call to persist and flush, in order to get the most Solid code? Or am I just overcomplicating things in my quest of best practices?
If you have a service that will handle tasks upon your entities, to me, the right way is to inject EntityManager into your service definition and do persist and flush operation inside it.
Another way to proceed, if you want to keep separate that logic, is to create an EventSubscriber and raise a custom event from your "entity service" when you're ready to do persist and flush operations
My 2 cents:
about flush, as it calls the DB, doing it like you already do when needed in your controllers sounds good to me.
about presist, it should be called in your Handler when your entity is in a "ready to be flushed" state. A Persister interface with only the persist method as a dependency of your Handlers, and a DoctrinePersister implementation injected in them looks OK.
Another option here - you can implement save() method in your entity repository class and make persistence there. Inject your entity repository as dependency into your Handler class.
If you don't want to couple your service and business logic to the EntityManager (good job), SOLID provides a perfect solution to separate it from your database logic.
//This class is responsible for business logic.
//It knows nothing about databases
abstract class CancelOrder
{
//If you need something from the database in your business logic,
//create a function that returns the object you want.
//This gets implemented in the inherited class
abstract protected function getOrderStatusCancelled();
public function cancel($order)
{
$order->setOrderStatus($this->getOrderStatusCancelled());
$order->setSubmittedTime(new DateTime());
//and other business logic not involving database operations
}
}
//This class is responsible for database logic. You can create a new class for any related CRUD operations.
class CancelOrderManager extends CancelOrder
{
public function __construct($entityManager, $orderStatusRepository)...
public function getOrderStatusCancelled()
{
return $this->orderStatusRepository->findByCode('cancelled');
}
public function cancel($order)
{
parent::cancel($order);
$this->entityManager->flush();
}
}

Accessing Symfony2 global parameter in entity class

I have a value stored in my parameters.ini file, and I need to access it during the prepersist method of my model.
Normally I use $this->container->getParameter('value');, but the container is not available in the entity.
Is there a way to get parameters within an entity class?
P.S. The value is an API key for a service I am pulling info from during prepersist. Best practice is to keep keys/passwords in parameters.ini
Best practice is to use a service to persist your entity. This one would inject the container and set your parameter when you call your updateMyEntity() service method.
Inside your controller (or whatever you want):
$user = new User('foo');
$user->setSomeProperty('bar');
$userService->update($user);
Inside the UserService:
public function update(User $user) {
$user->setSomeParameter($this->container->getParameter('value'));
$this->em->persist($user);
}
In addition to Florent's answer, Entities are meant to be purely data objects. They should not know about any other variables or services within your application. I'm more curious about why your entity needs to know anything about an API key that is system-wide. With very little background information, I'd say you should rethink what you are trying to do.
You need a service to interact with the API, ideally configured through the container. I don't see what that has to do with an entity.

Resources