Symfony. Using transactions with 2+ repositories - symfony

In a certain controller I need to do several inserts/updates in the database. I'm using 4 repositories of 4 entities for that, but I need this to be done in a unit of work.
I know that I can do these inserts/updates directly on the controller and use $em->getConnection()->beginTransaction(), $em->getConnection()->rollBack() and $em->getConnection()->commit() to ensure that all or none are done. But this goes against symfony best practices, so I'd like to use repositories.
I've seen this tutorial for Doctrine 1, but it seems like a very complex solution to something that is supposed to be simpler.
Any idea?
Thank you in advance.
edit, I'm using Symfony 3.3.5.
edit2, I add an example of what I want to do. The whole process is to add a message written by a worker to the rest of his department mates, all within a company. The message may or may not have attachments (let's say it does). These files should appear as department and company files, so that even if the department is deleted, the files continue to appear as belonging to the company.
I haven't included error handling in the code to make it simpler.
// New Files
$file1 = new File(); // AppBundle\Entity\File
$file1->setName("name1");
$file2 = new File();
$file2->setName("name2");
$em->getRepository('AppBundle:File')->insert($file1);
$em->getRepository('AppBundle:File')->insert($file2);
// New Post
$post = new Post(); // AppBundle\Entity\Post
$post->setContent($some_text)
->setAuthor($some_user) // AppBundle\Entity\User
->addFile($file1)
->addFile($file2);
$em->getRepository('AppBundle:Post')->insert($post);
// Getting company and department
$company = $em->getRepository('AppBundle:Company')->findOneByCode($some_company_code);
$department = $em->getRepository('AppBundle:Department')->findOneByCode($some_dept_code);
$company->addFile($file1)->addFile($file2);
$department->addFile($file1)->addFile($file2);
$em->getRepository('AppBundle:Company')->update($company);
$em->getRepository('AppBundle:Department')->update($department);
// Notifications
$notif = new Notification(); // AppBundle\Entity\Notification
$notif->setText("Text")
->setPost($post)
->addAddressee($some_dept_member)
->addAddressee($other_dept_member);
$notif = $em->getRepository('AppBundle:Notification')->insert($notif);

In repositories you should only fetch entities or execute queries with DQL that retrieve some data.
Every time you need to create a new row in your db, you have to inform the entity manager that you want to persist the entity, calling $em->persist($myNewEntity). This inform the entity manager that that entity should be persisted to dB.
Once you have created (and persisted) all your entities and/or modified your fetched entities, you can call $em->persist() just once: the entity manager will do all the needed insert/update/delete in a single transaction to your dB.
If you also need to retrieve/fetch data in the same transaction you should consider including all your code in a callback function and pass it to the entity manager, like this
$myCallback=function($em) {
//Do all here: access repositories, create new entities, edit entities
//...
//no need to call $em->flush(), it will be called by Doctrine after the execution of the callback
};
$em->transactional($myCallback) ;

Related

Can I read/update only a part of an entity using entity core?

I have the User entity that contains the Email/Name/.../HashedPassword/Salt.
Now, every time, after the user logs in the entire User entity goes to the client so that the user can modify some of the properties.
However I would prefer not to send the last two properties; but if I set them to null before send them to client, then when the entity comes back, I would need to get the original entity from the database, set the two properties to the just arrived entity then save it.
Is there a better solution, like saving only a part of the entity?
Or maybe I am security paranoid and this is not a problem.
For avoiding reading, you can just select a new object without the properties you do not want to expose:
return user.Select(x => new User
{
Id = x.Id,
Email = x.Email,
Name = x.Name,
});
Your update procedure is the correct way to handle this.

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?

Symfony2 - Doctrine2 store changeset for later (or alternative solution to approve changes)

I have several entities, each with its form type. I want to be able, instead of saving the entity straight away on save, to save a copy of the changes we want to perform and store it in DB.
We'd send a message to the user who can approve the change, who will review the original and the changed field(s) and will approve or not. If approved the entity would be properly flushed.
To solve the issue I was thinking about:
1) doing a persist
2) getting the changesets (both the one related to "normal" fields, and the one relative to collections)
3) storing it in DB
4) Performing $em->refresh() to discard changes.
Later what I need is to get the changset(s) back, ask the (other) user to approve it and flush it.
Is this doable? What I'm especially concerned about is that the entity manager that generated the first changeset is not the same we are going to use to perform the flush, I basically need to "load" a changeset.
Any idea on how to solve the issue (this way, or another way ;) )
Another solution (working only for "normal" fields, not reference ones that come from other entities to the current one, like a many to many) would be to clone the current entity, store it, and then once approved copy the field(s) from the cloned to the original one. But it does not work for all fields (if the previous solution does not work we'd limit the feature just to "normal" fields).
Thank you!
SN
Well, you could just treat the modifications as entities themselves, so that every change is stored in the database, and then all the changes that were approved are executed against the entity.
So, for example, if you have some Books stored in the database, and you want to make sure that all the modifications made to these are approved, just add a model that would contain the changeset that has to be processed, and a handler that would apply these changes:
<?php
class UpdateBookCommand
{
// If you'll store these commands in a database, perhaps this field would be a relation,
// or you could just store the ID
public $bookId;
public $newTitle;
public $newAuthor;
// Perhaps this field should be somehow protected from unauthorized changes
public $isApproved;
}
class UpdateBookHandler
{
private $bookRepository;
private $em;
public function handle(UpdateBookCommand $command)
{
if (!$command->isApproved) {
throw new NotAuthorizedException();
}
$book = $this->bookRepository->find($command->bookId);
$book->setTitle($command->newTitle);
$book->setAuthor($command->newAuthor);
$this->em->persist($book);
$this->em->flush();
}
}
Next, in your controller you would just have to make sure that the commands are somehow stored (in a database or maybe even in a message queue), and the handler gets called when the changesets could possibly get applied.
P.S. Perhaps I could have explained this a bit better, but mostly the inspiration for this solution comes from the CQRS pattern that's explained quite well by Martin Fowler. However, I guess in your case a full-blown CQRS implementation is unnecessary and a simpler solution should work.

Symfony2 best way of removing business logic from controller and correct usage of model

I'm in searching of the best way of removing business logic from controller and correct usage of model(and maybe services).
Some details below.
Actually, my project is more complicated, but as example I will use Simple Blog application.
I have created my application (Simple Blog) in next steps:
created bundle
generated entities(Topic, Post, Comment)
generated controller for each entity, using doctrine:generate:crud
installed FOSUserBundle and generated User entity
So, I have all needed methods and forms in my controllers. But now I have some troubles:
Admin need to be able see all topics and posts, when simple User can only see
topic and posts where he is owner.
Currently there are indexAction, that return findAll common for any user. As solution, I can check in action, if ROLE_USER or ADMIN and return find result for each condition. But this variant keep some logic at action.
I also can generate action for each role, but what happened if roles amount will increase?
What is the best way to solve this problem with result for each role?
I need to edit some parameters before saving.
For example, I have some scheduler, where I create date in some steps, using features of DateTime.
Before saving I need to do some calculations with date.
I can do it in controller using service or simple $request->params edit.
What is the best way to edit some $request parameters before saving?
My questions I have marked with bold.
Thanks a lot for any help!
What I would do is to create a query which fetches the topics. Afterwards I would have a method argument which specifies if the query should select only the topics for a certain user or all topics. Something like this should do the work in your TopicRepository:
public function findTopics($userId = false)
{
$query = $this->createQueryBuilder('topic');
if($userId) {
$query->join('topic.user', 'user')
->where('user.id = :user_id')
->setParameter(':user_id', $userId)
;
}
return $query->getQuery()->getResult();
}
So, whenever you need to get the topics only by a user, you would pass a $userId to the method and it would return the results only for that user. In your controller you'd have something similar to this code (Symfony 2.6+):
$authorizationChecker = $this->get('security.authorization_checker');
if($authorizationChecker->isGranted('ROLE_ADMIN')){
$results = $this->get('doctrine.orm.entity_manager')->getRepository('TopicRepository')->findTopics();
} else {
$results = $this->get('doctrine.orm.entity_manager')->getRepository('TopicRepository')->findTopics($this->getUser()->getId());
}
You can try using Doctrine Events and create a PreUpdate depending on your case. See the documentation for more information. If you have a TopicFormType, you could also try the form events.
You are not supposed to "edit" a $request, which is why you can't directly do that. You can, however, retrieve a value, save it as a $variable and then do whatever you want with it. You can always create a new Request if you really need it. Could you be more specific what you want to do here and why is this necessary?

Application independent of database schema

I have an asp.net application that uses the entity framework to interact with the database.
In the new version that I am working on now, I need to separate the application from the database so that it can be used with a variety of databases that have similar data but different schemas. (Able to be used for new clients)
So far this is my approach, but it feels wrong
I have objects from a data entity model generated by the clients database
I custom wrote objects that my system will use
I wrote an interface that outlines all of the data operations that return my custom objects
I wrote an implementation of the interface that takes my client's objects from the entity framework connected to their database, and loads the fields I want into my custom objects.
This feels wrong as now I have 2 sets of similar objects.
Example, here I get salesOrders from the clients database, and dump the data into my custom Job object:
public List<Job> getJobs()
{
List<Job> jobs = new List<Job>();
using (var context = new TBDIEntities.TBDIEntities())
{
//get all future events that are not cancelled
List<SalesOrder> salesOrders = context.SalesOrders
.Where(c => c.EVENTCONFIRMATION != "CANCELLED" && c.FUNCTIONDATE >= DateTime.Now)
.ToList<SalesOrder>();
jobs.AddRange(from order in salesOrders
let dateTime = order.FUNCTIONSTARTTIME
where dateTime != null
select new Job
{
Description = order.FUNCTIONTYPE,
StartTime = (DateTime)dateTime,
Id = order.SALESORDERREF_TXNID.ToString(),
ShiftGroups = new List<ShiftGroup>(),
Status = order.EVENTCONFIRMATION,
ShiftCount = (int)context.BSS_ShiftListView
.Count(c => c.SALESORDERREF_TXNID == order.SALESORDERREF_TXNID),
ConfirmedShifts = (int)context.BSS_ShiftListView
.Count(c => c.SALESORDERREF_TXNID == order.SALESORDERREF_TXNID && c.Confirmed != null),
Client = new Client { Name = order.CustomerRef_FullName }
});
}
return jobs;
}
So I am creating a new context, getting a collection of salesOrders (the table name in the clients database), then taking the the data from the salesOrders and creating new Job objects (the ones I wrote that my application will interact with) and returning the Job objects.
This feels wrong as now I have 2 lists of similar objects (SalesOrders and Jobs), and I have to write CRUD operations for each object rather than just using the entity framework.
Example, I have a page where you can add new shifts. But the Shifts table will be different from client to client, and changes I make need to update the clients table. So how do I write code that can use shifts, but can have the entity framework swapped out with schemas from new clients? I need things like shifts to be in a collection that I can use to databind an asp:ListView.
What is the smartest way of doing this? How do I use the entity framework but be independent of customer schema so my project can be reused for many databases?
Your two similar objects are really performing two different roles for your architectural layers and are not redundant. What you're working with are Domain Models (SalesOrders), Data Transfer Objects, and View Models (Jobs). So you really might end up with 3 sets of objects.
A tool like AutoMapper takes out much of the pain of the tedious object-to-object mapping.

Resources