Trying to add virtual property to query result in Symfony2 with JMS - symfony

I have a simple task ahead of me, yet I find myself pretty much incapable of completing it.
My model is pretty complex, so I'll try to simplify for the sake of being specific.
I have two entities, Call and Caller, with entity repositories that I access via custom services. I am using JMS Serializer Bundle. All entities are mapped correctly, the the whole thing is working pretty fine. But I have this idea that I just can't make happen. To the point...
These are my entities described:
--Call--
#call_id
location
date_created
Caller
--Caller--
#caller_id
phone_num
The idea is to have a list of all calls with their fields for example:
New York, 2015.12.12. 20:07:06, Novak Djokovic, 3816976548 [YY]
London, 2015.12.13. 20:07:06, Jelena Jankovic, 3811116333 [XX]
Fields YY and XX represent the number of calls already in a database with that specific number.
I have a query that returns the list without YY and XX values, and I also have a separate query that returns number of calls from a specific number. The thing gets complicated when I try to join them. Not sure how to do that.
I read about VirtualProperty annotation for JMS, but failed to actually see how to use it this time (since it's not a good practice to access your repository or service from an Entity).
These are my methods:
1 - get list of all calls with callers
public function findAllCalls()
{
$callerAlias = "c";
//getAlias method returns the alias of the current entity (call)
$qb = $this->createQueryBuilder($this->getAlias());
$qb->leftJoin($this->getAlias() . '.caller', $callerAlias);
return $qb->getQuery()->getResult();
}
2 - get number of calls from a specific number based on a call as a parameter
public function getNumberOfCalls(Call $call) {
$callerAlias = "c";
$qb = $this->createQueryBuilder($this->getAlias());
$qb->leftJoin($this->getAlias() . '.caller', $callerAlias);
$qb->select("COUNT(" . $this->getAlias() . ".call_id)");
$qb->where($callerAlias.".phonenbr = ".$call->getPhoneNumber());
return $qb->getQuery()->getScalarResult();
}
Hoping to hear your opinions on this, 'cause I really struggled to find the sollution.

Related

Doctrine find() and querybuilder() return different result in PHPUnit test

With Doctrine and Symfony in my PHPUnit test method :
// Change username for user #1 (Sheriff Woody to Chuck Norris)
$form = $crawler->selectButton('Update')->form([
'user[username]' => 'Chuck Norris',
]);
$client->submit($form);
// Find user #1
$user = $em->getRepository(User::class)->find(1);
dump($user); // Username = "Sheriff Woody"
$user = $em->createQueryBuilder()
->from(User::class, 'user')
->andWhere('user.id = :userId')
->setParameter('userId', 1)
->select('
user
')
->getQuery()
->getOneOrNullResult()
;
dump($user); // Username = "Chuck Norris"
Why my two methods to fetch the user #1 return different results ?
diagnosis / explanation
I assume* you already created the User object you're editing via crawler before in that function and checked that it is there. This leads to it being a managed entity.
It is in the nature of data, to not sync itself magically with the database, but some automatism must be in place or some method executed to sync it.
The find() method will always try to use the cache (unless explicitly turned off, also see side note). The query builder won't, if you explicitly call getResult() (or one of its varieties), since you explicitly want a query to be executed. Executing a different query might lead to the cache not being hit, producing the current result. (it should update the first user object though ...) [updated, due to comment from Arno Hilke]
((( side note: Keeping objects in sync is hard. It's mainly about having consistency in the database, but all of ACID is wanted. Any process talking to the database should assume, that it only is working with the state at the moment of its first query, and is the only user of the database. Unless additional constraints must be met and inconsistent reads can occur, in which case isolation levels should be raised (See also: transactions or more precisely: isolation). So, automatically syncing is usually not wanted. Doctrine uses certain assumptions for performance gains (mainly: isolation / locking is optimistic). However, in your particular case, all of those things are of no actual concern... since you actually want a non-repeatable read. )))
(* otherwise, the behavior you're seeing would be really unexpected)
solution
One easy solution would be, to actively and explicitly sync the data from the database by either calling $em->refresh($user), or - before fetching the user again - to call $em->clear(), which will detach all entities (clearing the cache, which might have a noticable performance impact) and allowing you to call find again with the proper results being returned.
Please note, that detaching entities means, that any object previously returned from the entity manager should be discarded and fetched again (not via refresh).
alternate solution 1 - everything is requests
instead of checking the database, you could instead do a different request to a page that displays the user's name and checks that it has changed.
alternate solution 2 - using only one entity manager
using only one entity manager (that is: sharing the entity manager / database in the unit test with the server on the request) may be a reasonable solution, but it comes with its own set of problems. mainly omitted commits and flushes may avoid detection.
alternate solution 3 - using multiple entity managers
using one entity manager to set up the test, since the server is using a new entity manager to perform its work, you should theoretically - to do this actually properly - create yet another entity manager to check the server's behavior.
comment: the alternate solutions 1,2 and 3 would work with the highest isolation level, the initial solution probably wouldn't.

TYPO3 does not persist consistently

I'm creating Events and want to bundle them into consolidated objects matched by title so I created an EventBundle repository which holds these objects and I register single events against it matching them by title into the Bundles.
Since I have a lot of troubles saving them I already went so far as to cache them locally which does help somewhat but still it's pretty bad.
public function registerEvent($event) {
//We are matching with the title of the event so we get that first
$title = $event->getEvTitle();
if(!isset($this->aBundles[$title]))
//Then we look up the event bundle for this title, if it does not exist this will return null
$this->aBundles[$title] = $this->findEventBundleByTitle($title);
if($this->aBundles[$title] != NULL) {
$this->aBundles[$title]->copyDetails($event);
$this->aBundles[$title]->setEvTitle($title);
$this->update($this->aBundles[$title]);
print_r("Update: $title\n");
}
else {
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
$this->aBundles[$title] = $objectManager->get('Ext\MyEvents\Domain\Model\EventBundle');
$this->aBundles[$title]->copyDetails($event);
$this->aBundles[$title]->setEvTitle($title);
$this->add($this->aBundles[$title]);
print_r("Add: $title\n");
}
}
public function findEventBundleByTitle($title){
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(FALSE);
$query->matching(
$query->equals('ev_title', $title)
);
$res = $query->execute();
$bundle = ($res->count()==0?NULL:$res->getFirst());
return $bundle;
}
Now running this I would expect to see one add for each title and then updates - which is true for the first run.
But on subsequent runs there are again some adds, it does not match some of the events to the title. With each subsequent run there are less and less adds until there are only updates. But when looking into the Database it shows multiple records with the same title now. A unique index will cause errors on the second run too as the lookup of the Object fails, sometimes without any pattern.
Any idea why this might happen? I can check to see the entries in the database between the runs so it's most likely that the lookup fails for some reason. But I'm totally out of ideas why that might be the case as it does work eventually, but there are a lot more than just 1-2 entries in the database for some of the events then...
Also confusing is the fact that after 5 runs all events do match consistently with some events being in the database 5 times at this point of time. But all matches are to the FIRST of those entries so it's not like it is not matched by the query, it's just being ignored until there are enough of them?!, all entries created due to the database lookup not returning anything are ignored after this point. Deleting them from the database by hand restarts the adding of spurious content again.
To answer it myself... I just found that within the copy function I copied over some properties of the Model that I probably should not copy which confuses TYPO3 and does break the saving to the DB.
So if someone stumbles across this, make sure you only copy valid data and not all properties of the Model as some of the properties might break functionality.

Error: forceDelete method doesnt exist

Can you please explain me the following behavior?
public function kill($id)
{
$post = Post::withTrashed()->where('id',$id)->get();
$post->forceDelete();
return redirect()->back()->with('success','Post Deleted Succesfully');
}
The code results in this error:
forceDelete method doesnt exist
But the following code does not.
public function kill($id)
{
$post = Post::withTrashed()->where('id',$id)->first();
$post->forceDelete();
return redirect()->back()->with('success','Post Deleted Succesfully');
}
Could someone explain?
My Laravel knowledge is rusty and I am a Rails guy. I just landed on your post to review it if it needs any improvements.
Any ways... if I am correct...
You have 2 functions, let them be F1 and F2 respectively.
In F1, you are searching for a post with a specific ID. What you get is a collection proxy and not a Post. Thus it doesn't respond to forceDelete().
In F2, you are asking for the first object in the collection thus you are getting a Post object that responds to forceDelete().
To delete a collection you will be able to use the delete() function instead in some thing like
Post::withTrashed()->where('id',$id)->delete();
Actually it is pretty simple. As you indicated in the post,
$post = Post::withTrashed()->where('id',$id)->get();
$post->forceDelete();
gives error
while
$post = Post::withTrashed()->where('id',$id)->first();
$post->forceDelete();
doesnot. The reason as for my understanding is, $id is always unique. And it returns a single data (or single model) related to that particular id from the post table. In the first case where you are using get() method, what you are trying to return is a multiple row data, hence it will return a single data but in multi-dimensional array format. You can check that using dd() helper function.
In second case, you are using first() method, it always returns single row of data related to that particular $id, so forceDelete() method exists for that case(In other sense you can say that forceDelete exists only the single row data model, but not multiple data row model which you are tying to retrieve using get(). Remember get() always tries to return multiple data, and multiple data can only be held on array, so it gives array as a result although the result is only one.)
Hope this helps.
This is because when you user ->get() what you really have is a Laravel collection and there is no forceDelete() method on collection.
So you have to use ->first() or ->find() and eloquent will return the model and then you can use forceDelete on it.
For example:
$users->each(function ($user) {
$user->forcedelete();
});
Is is efficient way of doing this is another question. But it works.

Where should I put a logic for querying extra data in CQRS command flow

I'm trying to implement simple DDD/CQRS architecture without event-sourcing for now.
Currently I need to write some code for adding a notification to a document entity (document can have multiple notifications).
I've already created a command NotificationAddCommand, ICommandService and IRepository.
Before inserting new notification through IRepository I have to query current user_id from db using NotificationAddCommand.User_name property.
I'm not sure how to do it right, because I can
Use IQuery from read-flow.
Pass user_name to domain entity and resolve user_id in the repository.
Code:
public class DocumentsCommandService : ICommandService<NotificationAddCommand>
{
private readonly IRepository<Notification, long> _notificationsRepository;
public DocumentsCommandService(
IRepository<Notification, long> notifsRepo)
{
_notificationsRepository = notifsRepo;
}
public void Handle(NotificationAddCommand command)
{
// command.user_id = Resolve(command.user_name) ??
// command.source_secret_id = Resolve(command.source_id, command.source_type) ??
foreach (var receiverId in command.Receivers)
{
var notificationEntity = _notificationsRepository.Get(0);
notificationEntity.TargetId = receiverId;
notificationEntity.Body = command.Text;
_notificationsRepository.Add(notificationEntity);
}
}
}
What if I need more complex logic before inserting? Is it ok to use IQuery or should I create additional services?
The idea of reusing your IQuery somewhat defeats the purpose of CQRS in the sense that your read-side is supposed to be optimized for pulling data for display/query purposes - meaning that it can be denormalized, distributed etc. in any way you deem necessary without being restricted by - or having implications for - the command side (a key example being that it might not be immediately consistent, while your command side obviously needs to be for integrity/validity purposes).
With that in mind, you should look to implement a contract for your write side that will resolve the necessary information for you. Driving from the consumer, that might look like this:
public DocumentsCommandService(IRepository<Notification, long> notifsRepo,
IUserIdResolver userIdResolver)
public interface IUserIdResolver
{
string ByName(string username);
}
With IUserIdResolver implemented as appropriate.
Of course, if both this and the query-side use the same low-level data access implementation (e.g. an immediately-consistent repository) that's fine - what's important is that your architecture is such that if you need to swap out where your read side gets its data for the purposes of, e.g. facilitating a slow offline process, your read and write sides are sufficiently separated that you can swap out where you're reading from without having to untangle reads from the writes.
Ultimately the most important thing is to know why you are making the architectural decisions you're making in your scenario - then you will find it much easier to make these sorts of decisions one way or another.
In a project i'm working i have similar issues. I see 3 options to solve this problem
1) What i did do is make a UserCommandRepository that has a query option. Then you would inject that repository into your service.
Since the few queries i did need were so simplistic (just returning single values) it seemed like a fine tradeoff in my case.
2) Another way of handling it is by forcing the user to just raise a command with the user_id. Then you can let him do the querying.
3) A third option is ask yourself why you need a user_id. If it's to make some relations when querying the data you could also have this handles when querying the data (or when propagating your writeDB to your readDB)

Symfony2 $em->createQuery() also selects removed objects

Foreword: My script is a bit complicated so I try to reduce the complexity down to a simple example.
Imagine we have an entity "company". We have also a repository-function that looks like this:
public function delete(){
// get company-objects to delete
[...]
// delete some companies
$this->getEntityManager()->remove($company1);
$this->getEntityManager()->remove($company2);
// do other things
[...]
// get companies via createQuery (I really need to do it via
// createQuery because of different reasons that would be too
// complicated to explain for this problem)
$query = $this->getEntityManager()
->createQuery('
SELECT company
FROM MyOwnBundle:Company company
WHERE [...irrelevant...]
');
$companies = $query->getResult();
dump($companies);
exit;
}
The problem: The createQuery also selects the removed companies.
My thoughts:
I thought that the entityManager knows that the companies are removed and therefore doesn't select them in a query. But this thought was wrong... (But why is this? Isn't the entityManager a global singleton-object?)
I know that when I would persist the data with flush() before using createQuery the results would be fine. But I can't persist the data here because then the whole process wouldn't be in a transaction.
I also can't manipulate the where-part of createQuery to exclude the already deleted companies because the whole process is separated into many functions and it would be hard to transport an array with the removed companies through the whole process.
The Question: How can I get the companies via createQuery without the removed ones?
You could wrap your code like below to make your whole process be in a transaction. This way you could flush right after remove the data.
$this->getEntityManager()->transactional(function($em) {
// put your code here
// $em is instanceof EntityManager
});

Resources