How we can inject 50 or 100 Injections in laravel ,because if we inject that much,it will not be a good way. It Slows the process.
//Just for example : -
public function __construct(
RoleManagement $role_management,
UserRole $userRole,
User $user
RoleManagement $role_management,
UserRole $userRole,
User $user ,
RoleManagement $role_management,
UserRole $userRole,
User $user ,
RoleManagement $role_management,
UserRole $userRole,
User $user,
RoleManagement $role_management,UserRole $userRole,User $user)
{
$this->role_management = $role_management;
$this->userRole = $userRole;
$this->user = $user;
$this->role_management = $role_management;
$this->userRole = $userRole;
$this->user = $user;
$this->role_management = $role_management;
$this->userRole = $userRole;
$this->user = $user;
}
Actually, it wouldn't really slow the process as far as I know (the container is compiled, so it comes down to passing parameters during a function call and a few assignation, which is not specifically slower).
Your problem here is a bad design as stated by aynber, your class/method should not depend on that many collaborators.
You can refer more specifically to the S of SOLID (Single Responsibility Principle) and to the 8th object calisthenics.
We do not have details on the actual content of the method, but it all comes down to splitting the process in different classes that can be injected one in another.
Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
For Symfony projects using Doctrine ORM, I'm used to Anemic Domain Models, with business logic handled in Symfony services. For projects involving heavy business logic, I'm wondering if using Rich Domain Models (which I'm not familiar with) would not be a better solution. I'm not looking for a comparison of RDM vs ADM, as there is already plenty of resources available online to figure out pros and cons of each solution. I'm rather wondering if RDM are suited for Symfony projects. To make my mind, I would like a glimpse of how I should implement RMD in a real world application.
The main questions I have are:
Does RDM really suit Symfony philosophy and best practices?
Does RDM not break the SOLID principle by having models doing too much?
How dependencies are managed by the models?
I'll give a theoretical example to express my concerns in a clearer way.
Lets say we are working on a REST API. We want to implement an user registration. The user registration relies on an external service provider (using an API) and the user creation should result in the creation of some other related entities (we will use a Dummy entity here) and the populate of ElasticSearch indexes in an asynchronous manner using Symfony Messenger.
The happy flow would look like this:
Validation of the request body
Call to the external service provider API to create the user on their side
Insertion of the user data in database
Create user related entities
Populate the ElasticSearch indexes
Send a confirmation email to the user
For practical reasons, the code below is simplified and therefore may not be 100% accurate nor functional. It only aims to demonstrate the differences between the two designs.
Anemic Domain Model implementation
<?php
// src/Entity/User.php
namespace App\Entity;
class User
{
private $id;
private $externalId;
private $email;
private $plainPassword;
private $password;
private $lastName;
private $firstName;
// Include more properties, getters and setters, ...
}
<?php
// src/Service/UserRegistrationService.php
namespace App\Service;
class UserRegistrationService
{
private $passwordEncoder;
private $externalProviderClient;
private $em;
private $bus;
private $mailer;
private $elasticSearch;
private $dummyService;
public function __construct(PasswordEncoder $passwordEncoder, ExternalProviderClient $externalProviderClient, EntityManager $em, MessageBusInterface $bus, Mailer $mailer, ElasticSearchService $elasticSearch, DummyService $dummyService)
{
$this->passwordEncoder = $passwordEncoder;
$this->externalProviderClient = $externalProviderClient;
$this->em = $em;
$this->bus = $bus;
$this->mailer = $mailer;
$this->elasticSearch = $elasticSearch;
$this->dummyService = $dummyService;
}
public function register(User $user): User
{
$password = $this->passwordEncoder->encode($user->getPlainPassword());
$externalId = $this->externalProviderClient->registerUser([
'email' => $user->getEmail(),
'firstName' => $user->getFirstName(),
'lastName' => $user->getLastName(),
]);
$user
->setExternalId($externalId)
->setPassword($password)
->setPlainPassword(null)
;
$this->em->persist($user);
$this->em->flush();
$this->bus->dispatch(new UserMessage($user));
return $user;
}
public function completeRegistration(User $user)
{
$dummy = $this->dummyService->createUserDependencies($user);
$this->elasticSearch->populateUser($user);
$this->elasticSearch->populateDummy($dummy);
$this->mailer->sendConfirmationEmail($user);
}
}
<?php
// src/Controller/UserController.php
namespace App\Controller;
class UserController
{
public function registerAction(Request $request, UserRegistrationService $userRegistrationService, Serializer $serializer)
{
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if (!$form->isValid()) {
// Process errors
}
$userRegistrationService->register($user);
return $this->serializer->serialize($user);
}
}
As you can see, the business logic relies on a lot of services, and this is just one of the tiniest and most basic functionality the API has to offer.
Rich Domain Model implementation
<?php
// src/Entity/User.php
namespace App\Entity;
class User
{
private $id;
private $externalId;
private $email;
private $plainPassword;
private $password;
private $lastName;
private $firstName;
// Include more properties, getters and setters, ...
}
<?php
// src/Model/UserRegistration.php
namespace App\Model;
class UserRegistration
{
private $email;
private $plainPassword;
private $lastName;
private $firstName;
private function __construct(string $email, string $plainPassword, string $lastName, string $firstName)
{
$this->email = $email;
$this->plainPassword = $plainPassword;
$this->lastName = $lastName;
$this->firstName = $firstName;
}
// Getters
public static function createFromRequest(Validator $validator, Request $request)
{
$requestData = $request->request->all();
$userRegistration = new self(requestData['email'], $requestData['plainPassword'], $requestData['lastName'], $requestData['firstName']);
$violations = $validator->validate($userRegistration);
if (count($violations) > 0) {
throw new \Exception(); // handle errors
}
return $userRegistration;
}
}
<?php
// src/Model/User.php
namespace App\Model;
class User
{
private $id;
private $externalId;
private $email;
private $oassword;
private $lastName;
private $lastName;
private function __construct(string $id, string $externalId, string $email, string $password, string $lastName, string $firstName)
{
$this->id = $id;
$this->externalId = $externalId;
$this->email = $email;
$this->password = $password;
$this->lastName = $lastName;
$this->firstName = $firstName;
}
// Getters
public static function createFromUserRegistration(Validator $validator, PasswordEncoder $passwordEncoder, ExternalProviderClient $externalProviderClient, EntityManager $em, MessageBusInterface $bus, UserRegistration $userRegistration)
{
$password = $passwordEncoder->encodePassword($userRegistration->getPlainPassword());
$externalId = $externalProviderClient->registerUser([
'email' => $userRegistration->getEmail(),
'firstName' => $userRegistration->getFirstName(),
'lastName' => $userRegistration->getLastName(),
]);
$userEntity = (new \App\Entity\User())
->setExternalId($externalId)
->setEmail($userRegistration->getEmail())
->setPassword($password)
->setLastName($userRegistration->getLastName())
->setFirstName($userRegistration->getFirstName())
;
$em->persist($userEntity);
$em->flush();
$id = ;
$user = self::buildFromUserEntity($validator, $userEntity);
$bus->dispatch(new UserMessage($user));
return $user;
}
public static function buildFromUserEntity(Validator $validator, \App\Entity\User $userEntity)
{
$user = new self(
$userEntity->getId(),
$userEntity->getExternalId(),
$userEntity->getEmail(),
$userEntity->getPassword(),
$userEntity->getLastName(),
$userEntity->getFirstName()
);
$violations = $validator->validate($user);
if (count($violations) > 0) {
throw new \Exception(); // handle errors
}
return $user;
}
public function completeRegistration(EntityManager $em, ElasticSearch $elasticSearch, Mailer $mailer)
{
$dummy = new Dummy($this);
$dummy->save($em);
$this->populateElasticSearch($elasticSearch);
$dummy->populateElasticSearch($elasticSearch);
$this->sendConfirmationEmail($mailer);
}
public function populateElasticSearch(ElasticSearch $elasticSearch)
{
$this->elasticSearch->populate($this);
}
public function sendConfirmationEmail(Mailer $mailer)
{
$this->mailer->sendConfirmationEmail($this);
}
public function serialize(Serializer $serializer)
{
return $serializer->serialize($user);
}
}
<?php
// src/Controller/UserController.php
namespace App\Controller;
class UserController
{
public function registerAction(Request $request, Validator $validator, PasswordEncoder $passwordEncoder, ExternalProviderClient $externalProviderClient, EntityManager $em, MessageBusInterface $bus, Serializer $serializer)
{
$userRegistration = UserRegistration::createFromRequest($validator, $request);
$user = User::createFromUserRegistration($validator, $passwordEncoder, $externalProviderClient, $em, $bus, $userRegistration);
return $user->serialize($serializer);
}
}
The main problems I see with this implementation are:
Duplicate code between user RMD and entity
Models will get bigger and bigger with each new functionality
Managing dependencies can quickly be a mess
What are your thoughts on this? Is there things I am doing wrong?
I hope you are aware, that this is (obviously) mostly opinion-based, thus you might agree or disagree with it.
I think you have a major misunderstanding of what the Rich Domain Model and the Anemic Domain Model is. But let's start with a few assumptions:
In my understanding, entities in symfony (more precisely in Doctrine's ORM) already are models in most cases. Thus having an additional User model is just unnecessary. And even if you wanted to split, I wouldn't put the exact same fields into the model, but just have the entity as a field. If you happen to copy all of the entities functions to the model, you're doing it wrong. Since by default all fields of an entity should be private, there is no reason to not treat it as a model. (I mean, it already works with objects and not their ids in relations...).
A User model/entity should never be concerned with sending emails, IMHO, because it breaks separation of concerns. Instead there should be something modelling the process in which the email is sent. I find this article describes rather cleanly, how this works. Pay attention to the changes to Shipment and to the CheckoutService -> Checkout.
Ironically, your "Anemic Domain Model"s UserRegistrationService is quite good. The user entity is a bit anemic in that implementation though and it should probably validate the User entity, but apart from that, the service could be renamed to UserRegistration and would fit very fine into RDM. (I agree, that the validation is already being done by the form (a convenience really), but there might be validations that are not about the consistency of the user in itself, but about the user as part of the collection of users in your database / model, or something else).
To summarize: In my view, Symfony can do RDM, and rather well. But the real crux (as always) is to actually choose/design the best model.
Essentially: Anemic means you don't have one place where all things are consistently done, but instead is split in a way which risks consistency/integrity or split separation of concerns to independent units. RDM on the contrary keeps it clustered in semantically sensible places. It doesn't change the fact, that you still want separation of concerns!
Now to answer your questions:
Does RDM really suit Symfony philosophy and best practices?
why not. depends on the modelling though, which might be adapted to fit Symfony's best practices.
Does RDM not break the SOLID principle by having models doing too much?
not generally, if done right. Your implementation definitely does break SOLID, but RDM doesn't have to. Nobody is saying that having a UserRegister and UserCancel and UserUpdate service/model would be wrong. RDM is about keeping the stuff that semantically belongs together in a business process/unit also together in code (which doesn't negate separation of concern or single purpose).
How dependencies are managed by the models?
Since in my view, business processes are models, and will act as services, dependencies are just as they are handled in services. Entities on the other hand should not need services ever*. (there might be some very very special circumstances, in which case you might want to actually have a service (a factory maybe) that manages the creation/updates of the entity)
What are your thoughts on this? Is there things I am doing wrong?
your implementation is, let's say 'unfortunate' in that it isn't RDM in my understanding and (as you realize yourself) breaks SOLID all over the place. So yes to the second part of the question.
For historical reasons, my pattern of running databases using Symfony is mixed. That is, the query uses DBAL and the insert uses ORM. Now you need to write a lot of data to the database. The flush in ORM can help me achieve business at the lowest cost.
All flush operations have been removed from the project. Put it in the __destruct of the controller.
However, doing so will cause DBAL to not find the latest changed data. Of course, these data ORMs can be obtained normally.
This is a very difficult problem. I hope to get guidance.
class BaseController extends Controller
{
public function __destruct()
{
$this->getDoctrine()->getManager()->flush();
}
public function indexAction()
{
$model = new CompanyModel();
$model->install(['company_name' => '1234']);
$model->update(['company_name' => 'abcd'], $model->lastInsertId);
}
}
class CompanyModel extends BaseController
{
public function validate($data, $id = false)
{
$this->entityManager = $this->getDoctrine()->getManager();
if(empty($id)){
$this->company_class = new Company();
}else{
if(!$this->is_exist($id)){
return false;
}
$this->company_class = $this->entityManager->getRepository(Company::class)->find($id);
}
if(array_key_exists('company_name', $data)){
$this->company_class->setCompanyName($data['company_name']);
}
if(self::$error->validate($this->company_class)){
return false;
}
return true;
}
public function insert($data)
{
if(!$this->validate($data)){
return false;
}
$this->company_class->setCreateAt(new \DateTime());
$this->entityManager->persist($this->company_class);
//$this->entityManager->flush();
$this->lastInsertId = $this->company_class->getId();
return true;
}
public function update($data, $id)
{
if(empty($id)){
self::$error->setError('param id is not null');
return false;
}
if(!$this->validate($data, $id)){
return false;
}
$this->company_class->setUpdateAt(new \DateTime());
//$this->entityManager->flush();
return true;
}
public function is_exist($id)
{
return $this->get('database_connection')->fetchColumn('...');
}
}
The final result of executing indexAction company_name is 1234; $ model-> update() was not executed successfully. The reason is that the $this-> is_exist() method that took the DBAL query did not find the ORM insert but did not flush the message.
Unchanging conditions,run
$this->entityManager->getRepository(Company::class)->find($id);
Is successful。
The problem is not the entity manager or dbal, as far as I can tell, but the usage of an anti-pattern, which I would call ... entanglement. What you should strive for is separation of concerns. Essentially: Your "CompanyModel" is an insufficient and bad wrapper for the EntityManager and/or EntityRepository.
No object should know about the entity manager. It should only be concerned with holding the data.
The entity manager should be concerned with persistence and ensuring integrity.
The controller is meant to orchestrate one "action", that can be adding one company, editing one company, batch-importing/updatig many companies.
Services can be implemented, when actions become to business-logic-heavy or when functionality is repeated.
(Note: the following code samples could be made way more elegant with using all the features that symfony provide, like ParamConverters, the Form component, the Validation component, I usually wouldn't write code this way, but I assume everything else would go way over your head - no offence.)
handling actions in the controller
controller actions (or service actions, really) are when you look at your problem from the task perspective. Like "I want to update that object with this data"). That's when you fetch/create that object, then give it the data.
use Doctrine\ORM\EntityManagerInterface;
class BaseController extends Controller {
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function addAction() {
$company = new Company(['name' => '1234']); // initial setting in constructor
$this->em->persist($company);
// since you have the object, you can do any changes to it.
// just change the object
$company->update(['name' => 'abcd']); // <-- don't need id
// updates will be flushed as well!
$this->em->flush();
}
public function editAction($id, $newData) {
$company = $this->em->find(Company::class, $id);
if(!$company) {
throw $this->createNotFoundException();
}
$company->update($newData);
$this->em->flush();
}
// $companiesData should be an array of arrays, each containing
// a company with an id for update, or without an id for creation
public function batchAction(array $companiesData) {
foreach($companies as $companyData) {
if($companyData['id']) {
// has id -> update existing company
$company = $this->em->find(Company::class, $companyData['id']);
//// optional:
// if(!$company) { // id was given, but company does not exist
// continue; // skip
// // OR
// $company = new Company($companyData); // create
// // OR
// throw new \Exception('company not found: '.$companyData['id']);
// }
$company->update($companyData);
} else {
// no id -> create new company
$company = new Company($companyData);
$this->em->persist($company);
}
}
$this->em->flush(); // one flush.
}
}
the base controller should handle creating objects, and persisting it, so very basic business logic. some would argue, that some of those operations should be done in an adapted Repository for that class, or should be encapsulated in a Service. And they would be right, generally.
the entity handles it's internal state
Now, the Company class handles its own properties and tries to stay consistent. You just have to make some assumptions here. First of all: the object itself shouldn't care if it exists in the database or not. it's not its purpose! it should handle itself. Separation of concerns! The functions inside the Company entity should concern simple business logic, that concerns its INNER state. It doesn't need the database, and it should not have any reference to the database, it only cares about it's fields.
class Company {
/**
* all the database fields as public $fieldname;
*/
// ...
/**
* constructor for the inital state. You should never want
* an inconsistent state!
*/
public function __construct(array $data=[]) {
$this->validate($data); // set values
if(empty($this->createAt)) {
$this->createAt = new \DateTime();
}
}
/**
* update the data
*/
public function update(array $data) {
$this->validate($data); // set new values
$this->updateAt = new \DateTime();
}
public function validate(array $data) {
// this is simplified, but you can also validate
// here and throw exceptions and stuff
foreach($array as $key => $value) {
$this->$key = $value;
}
}
}
some notes
Now, there should be NO use case, where you get an object to persist and at the same time an update - with an id - that refers to the new object ... unless that object was given the id beforehand! HOWEVER. If you persist an object, that has an ID and you call $this->em->find(Company::class, $id) you would get that object back.
if you have many relations, there are always good ways to solve this problem without destroying separation of concerns! you should never inject an entity manager into an entity. the entity should not manage its own persistence! nor should it manage the persistence of linked objects. handling persistence is the purpose of the entity manager or entity repository. you should never need a wrapper around an object just to handle that object. be careful not to mix responsibilities of services, entities (objects) and controllers. In my example code, I have merged services and controllers, because in simple cases, it's good enough.
I am developing an application using Symfony2 and doctrine 2. I would like to know how can I get the currently logged in user's Id.
Current Symfony versions (Symfony 4, Symfony >=3.2)
Since Symfony >=3.2 you can simply expect a UserInterface implementation to be injected to your controller action directly. You can then call getId() to retrieve user's identifier:
class DefaultController extends Controller
{
// when the user is mandatory (e.g. behind a firewall)
public function fooAction(UserInterface $user)
{
$userId = $user->getId();
}
// when the user is optional (e.g. can be anonymous)
public function barAction(UserInterface $user = null)
{
$userId = null !== $user ? $user->getId() : null;
}
}
You can still use the security token storage as in all Symfony versions since 2.6. For example, in your controller:
$user = $this->get('security.token_storage')->getToken()->getUser();
Note that the Controller::getUser() shortcut mentioned in the next part of this answer is no longer encouraged.
Legacy Symfony versions
The easiest way to access the user used to be to extend the base controller, and use the shortcut getUser() method:
$user = $this->getUser();
Since Symfony 2.6 you can retrieve a user from the security token storage:
$user = $this->get('security.token_storage')->getToken()->getUser();
Before Symfony 2.6, the token was accessible from the security context service instead:
$user = $this->get('security.context')->getToken()->getUser();
Note that the security context service is deprecated in Symfony 2 and was removed in Symfony 3.0.
In symfony2, we can get this simpler by this code:
$id = $this->getUser()->getId();
You can get the variable with the code below:
$userId = $this->get('security.context')->getToken()->getUser()->getId();
This can get dressed in the method:
/**
* Get user id
* #return integer $userId
*/
protected function getUserId()
{
$user = $this->get('security.context')->getToken()->getUser();
$userId = $user->getId();
return $userId;
}
And induction $this->getUserId()
public function example()
{
print_r($this->getUserId());
}
First of all sorry about my english, I'll try to do my best.
Im new to Laravel, im trying to implement custom auth throught a SOAP WS, I declare new class that implement UserProviderInterface. I success on implement retrieveByCredentials and validateCredentials methods but since i dont have access to database or global users information i cant implement retrieveByID method. Is there any way to make custom Auth not based on users id's ?
I need:
- Login and validate user throught SOAP WS
- Store User Info returned by WS.
- Remember me functionality
- Secure routes based on logged user and level of access
- Logout
Implemented class:
<?php
namespace Spt\Common\Providers;
use Illuminate\Auth\UserProviderInterface;
use Illuminate\Auth\GenericUser;
use Illuminate\Auth\UserInterface;
class AuthUserProvider implements UserProviderInterface{
private $user;
public function __construct(){
$this->user = null;
}
public function retrieveByID($identifier){
return $this->user;
}
public function retrieveByCredentials(array $credentials){
$client = new \SoapClient('webserviceurl');
$res = $client->Validar_Cliente($credentials);
$res = $res->Validar_ClienteResult;
if($res->infoError->bError === true){
return;
}
$res->id = $res->id_cliente;
$user = new GenericUser((array) $res);
return $user;
}
public function validateCredentials(UserInterface $user, array $credentials){
//Assumed that if WS returned a User is validated
return true;
}
}
I think that re-implement UserProviderInterface its not the solution but i googled and not found other way
Any Idea?
You're almost done, apart from the fact that private variable $user of AuthUserProvider doesn't survive the current http request. If you cannot "retrieve by id" from your web service, I guess the only way is to store the entire user in the session - Laravel itself stores the user's id in the session and the fact that it stores only the id (not the entire user) is one of the reasons why a retrieveByID method is needed.
The following is only to clarify and is untested.
class AuthUserProvider implements UserProviderInterface {
public function retrieveByCredentials(array $credentials) {
$client = new \SoapClient('webserviceurl');
$res = $client->Validar_Cliente($credentials);
$res = $res->Validar_ClienteResult;
if($res->infoError->bError === true) {
return;
}
$res->id = $res->id_cliente;
Session::put('entireuser', $res);
$user = new GenericUser((array) $res);
return $user;
}
public function retrieveByID($identifier) {
$res = Session::get('entireuser');
return new GenericUser((array) $res);
}
// ...
}
If you can't retrieve by id from your web service, I guess you cannot either retrieve by remember token, so it may be impossible for you to implement the "remember me" functionality, unless you store part of users data in a second database (which at that point could be used in place of the session above).
I can't figure out how it is best to get the Doctrine Entity Manager from my service layers, and template controller..
I thinking of making a singleton so i always can get the Entity manager, but is it the right way to do it?
Updated: I'll take an example
class Auth
{
const USER_ENTITY_NAME = 'Entities\User';
private $isVerified = FALSE;
public static function login($email, $password, $em, $rememberMe = false)
{
if(empty($email) OR empty($password))
{
// new login response
}
if($user = (self::getUser($email, $password, $em) !== null))
{
$sreg = SessionRegistry::instance();
$sreg->set("user_id", $user->getId());
}
return $user;
}
public static function getUser($email, $password, $em)
{
return $em->getRepository(
USER_ENTITY_NAME );
}
What i cant figure out is where i should get the user from? so i doesn't have to send the entity manager as an parameter.
Choose dependency injection over singleton.
I don't know which environment are you using Doctrine in, but I assume it being MVC - then any Controller should have access to the entity manager, either by passing it as a constructor argument, either by injecting it with a setter.
This way you can fetch stuff from the controller, and pass it to the Auth class eventually.
Anyway I think that authorization doesn't need an external class - I'd just write a loginAction method in a controller, get username and password from HTTP request and make the usual considerations [fetch the user / check if password is right], then store something in session in case of succesful login.