I need to do something basic, I have two entities: User and Action. Each User has X tokens assigned by the Admin and then he can perform Y actions based on the amount of tokens. So lets say one User only has enough tokens to perform one Action, I identified that if I perform multiple simultaneously requests at the same exact time (like 5 or more requests at the same time). Instead of just one Action, the User executes two or more Actions (and only in the explained scenario, in the rest everything works fine)
The related code to my explanation:
public function useractions(Requests $request){
$user = $this->getUser();
$post = Request::createFromGlobals();
if($post->request->has('new_action') && $this->isCsrfTokenValid("mycsrf", $post->request->get('csrf_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$tokens = $user->getTokens();
if($tokens<1){
$error = "Not enough tokens";
}
if(empty($error)){
$user->setTokens($tokens-1);
$entityManager->flush();
$action = new Action();
$action->setUser($user);
$entityManager->persist($transaction);
$entityManager->flush();
}
}
}
And I am using mariadb 10.5.12 with InnoDB as the engine
Obviously I am making a big mistake in my code or missing something in the Symfony or Doctrine configuration. Someone could tell me the mistake? Thanks
You probably have a race condition between the user's SELECT (the user refresh, called by your authentication mechanism) and your UPDATE (the first $entityManager->flush();).
Doctrine has a built-in method to manage concurrent requests, the EntityManager::transactional() method (doc). You may just need to wrap your code into it:
$entityManager = $this->getDoctrine()->getManager();
$error = $entityManager->transactional(function($entityManager) use ($user) {
// Required to perform a blocking SELECT for UPDATE
$entityManager->refresh($user);
$tokens = $user->getTokens();
if($tokens<1){
return "Not enough tokens";
}
$user->setTokens($tokens-1);
$entityManager->persist($user);
});
if (empty($error)) {
$action = new Action();
$action->setUser($user);
$entityManager->persist($action);
$entityManager->flush();
}
Note: make sure that your transaction is fast enough, as it has blocking effects on the mariadb side, affecting the involved rows.
For the execution of one update command, you should use flush once, and not in each iterate.
also, you can use transactions to do all of them as one unit.
but if you want to prevent concurrency updating you should use versioning! by this, when doctrine wants to update something check versioning and if it changes throw an error and prevent to persist data to the database ( you can handle this error by a listener ) and by this way just first request will be a success.
for example, I flag updatedAt filed as a version filed and it will be checked in each persistence.
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Version
* #ORM\Column(type="datetime",options={"default": "CURRENT_TIMESTAMP"})
*/
private $UpdatedAt;
Related
I have an DailyReport Entity in my Domain Layer. There are some fields in this object:
reportId
userId
date
tasks - Collection of things that user did in given day;
mood - how does the user felt during the whole day;
Also, there are some methods in my Application Service:
DailyReportService::addTaskToDailyReport
DailyReportService::setUserMoodInDailyReport
The thing is that both of these methods require DailyReport to be created earlier or created during function execution. How to deal with this situation?
I have found 2 solutions:
1 Create new DailyReport object before method dispatching, and after that pass reportId to them:
//PHP, simplified
public function __invoke() {
$taskData = getTaskData();
/** #var $dailyReport DailyReport|null **/
$dailyReport = $dailyReportRepository->getOneByDateAndUser('1234-12-12', $user);
//there were no report created today, create new one
if($dailyReport === null) {
$dailyReport = new DailyReport('1234-12-12', $user);
$dailyReportRepository->store($dailyReport);
}
$result = $dailyReportService->addTaskToDailyReport($taskData, $dailyReport->reportId);
//[...]
}
This one requires to put a more business logic to my Controller which i want to avoid.
2: Verify in method that DailyReport exists, and create new one if needed:
//my controller method
public function __invoke() {
$taskData = getTaskData();
$result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);
//[...]
}
//in my service:
public function addTaskToDailyReport($taskData, $date, $user) {
//Ensure that daily report for given day and user exists:
/** #var $dailyReport DailyReport|null **/
$dailyReport = $dailyReportRepository->getOneByDateAndUser();
//there were no report created today, create new one
if($dailyReport === null) {
$dailyReport = new DailyReport($date, $user);
$dailyReportRepository->store($dailyReport);
}
//perform rest of domain logic here
}
This one reduces complexity of my UI layer and does not expose business logic above the Application Layer.
Maybe these example is more CRUD-ish than DDD, but i wanted to expose one of my use-case in simpler way.
Which solution should be used when in these case? Is there any better way to handle get-or-create logic in DDD?
EDIT 2020-03-05 16:21:
a 3 example, this is what i am talking about in my first comment to Savvas Answer:
//a method that listens to new requests
public function onKernelRequest() {
//assume that user is logged in
$dailyReportService->ensureThereIsAUserReportForGivenDay(
$userObject,
$currentDateObject
);
}
// in my dailyReportService:
public function ensureThereIsAUserReportForGivenDay($user, $date) {
$report = getReportFromDB();
if($report === null) {
$report = createNewReport();
storeNewReport();
}
return $report;
}
//in my controllers
public function __invoke() {
$taskData = getTaskData();
//addTaskToDailyReport() only adds the data to summary, does not creates a new one
$result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);
//[...]
}
This will be executed only when user will log in for the first time/user were logged in yesterday but this is his first request during the new day.
There will be less complexity in my business logic, i do not need to constantly checking in services/controllers if there is a report created because this has been executed
previously in the day.
I'm not sure if this is the answer you want to hear, but basically I think you're dealing with accidental complexity, and you're trying to solve the wrong problem.
Before continuing I'd strongly suggest you consider the following questions:
What happens if someone submits the same report twice
What happens if someone submits a report two different times, but in the second one, it's slightly different?
What is the impact of actually storing the same report from the same person twice?
The answers to the above questions should guide your decision.
IMPORTANT: Also, please note that both of your methods above have a small window where two concurrent requests to store the rerport would succeed.
From personal experience I would suggest:
If having duplicates isn't that big a problem (for example you may have a script that you run manually or automatically every so often that clears duplicates), then follow your option 1. It's not that bad, and for human scale errors should work OK.
If duplicates are somewhat of a problem, have a process that runs asynchronously after reports are submited, and tries to find duplicates. Then deal with them according to how your domain experts want (for example maybe duplicates are deleted, if one is newer either the old is deleted or flagged for human decision)
If this is part of an invariant-level constraint in the business (although I highly doubt it given that we're speaking about reports), and at no point in time should there ever be two reports, then there should be an aggregate in place to enforce this. Maybe this is UserMonthlyReport or whatever, and you can enforce this during runtime. Of course this is more complicated and potentially a lot more work, but if there is a business case for an invariant, then this is what you should do. (again, I doubt it's needed for reports, but I write it here in the care reports were used as an example, or for future readers).
I have two types of user: client, manager. are stored in separated tables.
Each of then have unique nickname.
So, I need to open profile by nickname.
For Client is:
$client = Client::where("nickname", $nickname)
For Manager is:
$manager = Manager::where("nickname", $nickname)
So, I try to make universal function that dewtect where is client and manager and execute the appropriate query.
How can I improve this code and detect type of user only by nickename?
You should use one model for this, I guess it would be best way to handle clients and managers.
If for some reason you want to use two models, you can create method and put it in Client model since most of queries will be for clients:
public function getClientOrManagerByNickname($nickname) {
$client = $this->where('nickname', $nickname)->first();
return is_null($client) ? (new Manager)->where('nickname', $nickname)->first() : $client;
}
This code will create one query if client is found and will return this client. Or it will create two queires and will return manager. If there is no client and manager with this nickname, it will return null.
As I said I would suggest something a bit more complex but that to me would be less prone to make mistakes because of a confusion on the Model returned.
I would suggest something along this:
$modelname = getModelName(); $values = $modelName::where(1);
Where the function getModelName() would just return the ModelName.
I'm aware that this implies going through the DB another time and it increases the cost of the operation but I would rather loss a bit of performance and have more coherence in the code.
That's my personal opinion though.
I suspect this is a straightforward problem about entities not updating in memory.
Here are my repository functions:
public function insertTask($information)
{
...
$entityManager = $this->getEntityManager();
$entityManager->persist($taskObj);
$entityManager->flush();
// Update the task defects for this task
$this->updateTaskDefectTaskIds($taskObj->getId(), $task['defects']);
return $taskObj;
}
private function updateTaskDefectTaskIds($task, $defects)
{
$taskDefectIds = array();
foreach ($defects as $defect)
{
$taskDefectIds[] = intval($defect['taskDefectId']);
}
// Update the task IDs on the task defects
$this->getEntityManager()
->createQuery('UPDATE Model:TaskDefect td SET td.task = :task WHERE td.id IN (:taskDefectIds)')
->setParameter('task', $task)
->setParameter('taskDefectIds', $taskDefectIds)
->execute();
$this->getEntityManager()->flush();
}
This appears to be working. Basically I have:
- Task table
- TaskDefect table
TaskDefects by virtue of the way the app is written, get inserted before the Task does. But this means I need to, once the Task is eventually added, update the TaskDefect records to point to the new TaskId.
The problem I face though, is somewhere in memory (from the actual database side of things it is updating correctly) it's not picking up my changes. For example, I update some TaskDefects to point to the new TaskId - but then I access the Task object and it says there are no defects.
If I go to another page, and try and access the same task - then it says there are defects.
So I feel I'm missing a flush() or a persist() or something which is stopping the entities in memory from updating. Obviously reloading a page forces the refresh and it works fine then.
Here's what I have in my controller:
$task = $repository->insertTask($content); // i figured maybe at this point it's too much to expect the task obj to magically update
$updatedTask = $repository->findOneById($task->getId()); // so I grab it again... but no luck
var_dump($updatedTask->getDefects()); // ... because this returns no defects
Any ideas welcome. Thanks.
This line in insertTask:
return $taskObj;
Has no connection to what is happening in updateTaskDefectTaskIds because you're not modifying $taskObj at all, you're just passing an id value, then updating defect objects via DQL.
If you'd like $taskObj to reflect your defect additions from insertTask you would do something like this:
public function insertTask($information)
{
...
$entityManager = $this->getEntityManager();
$entityManager->persist($taskObj);
$entityManager->flush();
// Update the task defects for this task
$this->updateTaskDefectTaskIds($taskObj, $task['defects']);
return $taskObj;
}
private function updateTaskDefectTaskIds($taskObj, $defects)
{
foreach ($defects as $defect)
{
$defect = $this->getEntityManager()->getRepository('YourBundle:Defect')->find(intval($defect['taskDefectId']));
if ($defect instanceof Defect) {
$defect->setTaskObj($taskObj);
$this->getEntityManager()->persist($defect);
$taskObj->addDefect($defect);
}
}
$this->getEntityManager()->persist($taskObj);
$this->getEntityManager()->flush();
}
Or, if you don't mind an extra db call just refresh $taskObj in insertTask like this:
$this->getEntityManager()->refresh($taskObj);
return $taskObj;
Also, doctrine loves to cache what you have in memory, so if it doesn't have any reason to check the db (in your code example it can't know about your change) then it will just happily serve you up the stale object when you fetch the entity by id.
I'm using FOSUserBundle. What is the difference between these two?
$this->get('fos_user.user_manager');
...and...
$this->getUser();
I've found I've used both of the above at different times and everything works fine.
I'm guessing the first one is from FOS and the second one is the default one, but I'm guessing I should always use the same one.
This is one piece of code I've used:
$user = $this->getUser();
if($user) {
$email = $user->getEmail();
} else {
$email = "no email";
}
..and another...
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->findUserBy(array('memberID' => '123'));
...so should I have used the same method for both?
With $this->getUser() is only a shortcut to
$this->get('security.context')->getToken()->getUser()
So this means you get the user object according to the current security token. It's perfect and easy, when you want to retrieve the actual logged in user.
But if you want to get other users, fos_user.user_manager is the choice, as it has methods to find users easy and hiding the implementation behind. And it provides also methods for creating new users and updating them. And also if you retrieve the current logged in user with $this->getUser() and made modifcations to them, you should use the fos user manager to update them. Take a look in the docs for more!
They return different objects. $this->get('fos_user.user_manager') returns a FOS\UserBudle\Doctrine\UserManager object and $this->getUser() returns a FOS\UserBundle\Model\User object. The former handles users and the latter is a user. So no, you are using it right.
Where the two differ is in saving a user or creating a new user. If using the FOSUserBundle, you should always use the $this->get('fos_user.user_manager') method. This gives you access to the updateUser() function that works with the FOSUserBundle to make sure it updates all the user attributes that you don't need to explicitly declare in your User model, like date_created and roles.
That function is different than using Doctrine to persist() and then flush() the model.
I'm using Doctrine to save user data and I want to have a last modification field.
Here is the pseudo-code for how I would like to save the form once the user presses Save:
start transaction
do a lot of things, possibly querying the database, possibly not
if anything will be changed by this transaction
modify a last updated field
commit transaction
The problematic part is if anything will be changed by this transaction. Can Doctrine give me such information?
How can I tell if entities have changed in the current transaction?
edit
Just to clear things up, I'm trying to modify a field called lastUpdated in an entity called User if any entity (including but not limited to User) will be changed once the currect transaction is commited. In other words, if I start a transaction and modify the field called nbCars of an entity called Garage, I wish to update the lastUpdated field of the User entity even though that entity hasn't been modified.
This is a necessary reply that aims at correcting what #ColinMorelli posted (since flushing within an lifecycle event listener is disallowed - yes, there's one location in the docs that says otherwise, but we'll get rid of that, so please don't do it!).
You can simply listen to onFlush with a listener like following:
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
class UpdateUserEventSubscriber implements EventSubscriber
{
protected $user;
public function __construct(User $user)
{
// assuming the user is managed here
$this->user = $user;
}
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
// before you ask, `(bool) array()` with empty array is `false`
if (
$uow->getScheduledEntityInsertions()
|| $uow->getScheduledEntityUpdates()
|| $uow->getScheduledEntityDeletions()
|| $uow->getScheduledCollectionUpdates()
|| $uow->getScheduledCollectionDeletions()
) {
// update the user here
$this->user->setLastModifiedDate(new DateTime());
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata(get_class($this->user)),
$this->user
);
}
}
public function getSubscribedEvents()
{
return array(Events::onFlush);
}
}
This will apply the change to the configured User object only if the UnitOfWork contains changes to be committed to the DB (an unit of work is actually what you could probably define as an application level state transaction).
You can register this subscriber with the ORM at any time by calling
$user = $entityManager->find('User', 123);
$eventManager = $entityManager->getEventManager();
$subscriber = new UpdateUserEventSubscriber($user);
$eventManager->addEventSubscriber($subscriber);
Sorry for giving you the wrong answer at first, this should guide you in the right direction (note that it's not perfect).
You'll need to implement two events. One which listens to the OnFlush event, and acts like this:
// This should listen to OnFlush events
public function updateLastModifiedTime(OnFlushEventArgs $event) {
$entity = $event->getEntity();
$entityManager = $event->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
if (count($unitOfWork->getScheduledEntityInsertions()) > 0 || count($unitOfWork->getScheduledEntityUpdates()) > 0) {
// update the user here
$this->user->setLastModifiedDate(new \DateTime());
}
}
We need to wait for the OnFlush event, because this is the only opportunity for us to get access to all of the work that is going to be done. Note, I didn't include it above, but there is also $unitOfWork->getScheduledEntityDeletions() as well, if you want to track that.
Next, you need another final event listener which listens to the PostFlush event, and looks like this:
// This should listen to PostFlush events
public function writeLastUserUpdate(PostFlushEventArgs $event) {
$entityManager = $event->getEntityManager();
$entityManager->persist($this->user);
$entityManager->flush($this->user);
}
Once the transaction has been started, it's too late, unfortunately, to get doctrine to save another entity. Because of that, we can make the update to the field of the User object in the OnFlush handler, but we can't actually save it there. (You can probably find a way to do this, but it's not supported by Doctrine and would have to use some protected APIs of the UnitOfWork).
Once the transaction completes, however, you can immediately execute another quick transaction to update the datetime on the user. Yes, this does have the unfortunate side-effect of not executing in a single transaction.
#PreUpdate event won't be invoked if there's no change on the entity.
My guess would have been, similarly to the other 2 answers, is to say whatever you want to do when there will or will not be changes, use event listeners.
But if you only want to know before the transaction starts, you can use Doctrine_Record::getModified() (link).