Repository in PRE_WRITE event returns query data, not saved data - symfony

During a PUT call to an item I need to get the current saved values in order to compare them to request params.
Say the PUT call contains a name parameter that is different from the currently saved one.
I thought getting the entity with $repository->findOneBy would return the saved value but it's not, I'm getting the PUT param value instead.
The setup is taken from https://api-platform.com/docs/core/events :
const ALLOWED_METHOD = Request::METHOD_PUT;
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
['preWriteWorkflow', EventPriorities::PRE_WRITE],
],
];
}
public function preWriteWorkflow(GetResponseForControllerResultEvent $event)
{
$entity = $event->getControllerResult();
if (!($entity instanceof MyEntity)) {
return;
}
$route = "/{$entity->getId()}";
$result = $this->checkRequestFromControllerResult($event, $route);
if (!$result) {
return;
}
// Getting entity from repository in order to get the currently saved value
$savedEntity = $this->MyEntityRepository->findOneBy(['id' => $entity->getId()]);
// Both will return the Name value of the PUT call
// Shouldn't $savedEntity return the currently saved name ?
$entity->getName();
$savedEntity->getName();
}
What is the reason behind this behavior? Is there a way to get eventArgs injected in this method so that I can use getEntityChangeSet or hasChangedField?

What is the reason behind this behavior?
This is doctrine behaviour. Once you've fetched an entity, the instance is stored and always returned. Given that, you have one and only one instance of your entity during request's lifecycle.
$event->getControllerResult() === $repository->findBy($id); //true !
Roughly, Api-platform calls Doctrine and fetch your entity while executing the ReadListener. Because this is an object, doctrine's find*() methods always returns a pointer/reference to the entity, even if it is updated.
Yes, during a PUT request, the updated instance is the fetched one, in order to trigger doctrine update actions at the end of the request.
An easy way to keep an instance of the so called previous object is to clone it before the Deserialization event.
Note that this strategy is used by api-platform with the security_post_denormalize and previous_object security attributes.
EDIT
Working on a similar use case, i've found that the ReadListener stores the current object within the Request under the "data" key, whereas the previous object is stored within the "previous_data" key.
$entity = $request->get('data');
$previousEntity = $request->get('previous_data'); // This is a clone.

Related

Riverpod Flutter: Looking for best way to combine FutureProvider with StateNotifierProvider

I am working on a basic Support Ticket System. I get the Tickets from Firebase (Either as a Stream or Future).
I want to allow some Filtering Options (e.g. sort by Status and Category).
For this, I thought about using A Future Provider to get the List and a StateNotiferProvider to update the List depending on which filter is being used.
This is the code I have so far:
final ticketListStreamProvider =
RP.FutureProvider((_) => FirestoreService.getTicketList());
class TicketListNotifier extends RP.StateNotifier<List<Ticket>> {
TicketListNotifier() : super([]);
void addTicket(Ticket ticket) {
state = List.from(state)..add(ticket);
}
void removeTicket(Ticket ticket) {
state = List.from(state)..remove(ticket);
}
}
final ticketsController =
RP.StateNotifierProvider<TicketListNotifier, List<Ticket>>(
(ref) => TicketListNotifier(),
);
There are multiple issues I have with that. Firstly it doesn't work.
The StateNotifier accepts a List and not a Future<List>. I need to convert it somehow or rewrite the StateNotifier to accept the Future.
I was trying to stay close to one of the official examples.
(https://github.com/rrousselGit/riverpod/tree/master/examples/todos)
Unfortunately, they don't use data from an outside source like firebase to do it.
What's the best approach to get resolve this issue with which combination of providers?
Thanks
You can fetch your ticketlist in your TicketListNotifier and set its state with your ticket list.
class TicketListNotifier extends RP.StateNotifier<List<Ticket>> {
TicketListNotifier() : super([]);
Future<void> fetchTicketList() async {
FirestoreService.getTicketList().when(
//success
// state = fetched_data_from_firestore
// error
// error handle
)
}
}
final ticketsController =
RP.StateNotifierProvider<TicketListNotifier, List<Ticket>>(
(ref) => TicketListNotifier(),
);
Call this method where you want to fetch it /*maybe in your widget's initState method
ref.read(ticketsController.notifier).fetchTicketList();
Now ref.read(ticketsController); will return your ticket list
Since you have the ticket list in your TicketListNotifier's state you can use your add/remove method like this:
ref.read(ticketsController.notifier).addTicket(someTicket);

Store filters in session in LexikFormFilterBundle

Currently I store filters in session like this:
// Filter action
if ('filter' == $request->get('filter_action')) {
// Bind values from the request
$filterForm->handleRequest($request);
if ($filterForm->isValid()) {
// Build the query from the given form object
$filterUpdater->addFilterConditions($filterForm, $queryBuilder);
// Save filter to session
$filterData = $filterForm->getData();
$session->set(sprintf('%sControllerFilter', $this->filterName), $filterData);
$session->set(sprintf('%sControllerFilterPage', $this->filterName), 1);
}
} else {
// Get filter from session
if ($session->has(sprintf('%sControllerFilter', $this->filterName))) {
$filterData = $session->get(sprintf('%sControllerFilter', $this->filterName));
foreach ($filterData as $key => $filter) {
if (\is_object($filter)) {
$filterData[$key] = $em->merge($filter);
}
}
$filterForm = $this->createFilterForm($filterData, $this->getSiteFromSession($request));
$filterUpdater->addFilterConditions($filterForm, $queryBuilder);
}
}
But due to EntityManager::merge() deprecations I need to change this solution. Any ideas how to do it? The solution is to skip using EntityFilterType and use ChoiceFilterType but I don't want to, cause EntityFilterType is a much more comfortable solution.
If I remove lines that are responsible for merging entities from session, then I get error:
Entity of type "App\Entity\Category" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?
merge is used to re-attach a detached entity. persist tells Doctrine that the entity is to be saved, so it achieves the re-attach. This way you achieve that the entity will be managed, as the error message suggests.
You can read more here: https://symfony.com/doc/current/doctrine.html#persisting-objects-to-the-database

Meteor: Get count of collection by name. Accessing global scope on server

I'd like to create a method that returns the count of a generic collection.
Calling the method would look something like this:
Meteor.call('getCollectionCount', 'COLLECTION_NAME');
And the result would be the collection count.
The server method code would look something like this:
getCollectionCount: function (collectionName) {
return window[collectionName].find().count();
}
This won't work because window isn't defined on the server, but is something similar possible?
Use global instead of window.
Note that this uses the variable name assigned to the collection object, not the name given to the collection. For this to work with Meteor.users you need to assign another variable name.
if (Meteor.isServer) {
users = Meteor.users;
}
if (Meteor.isClient) {
Meteor.call('count', 'users', function (err, res) {
// do something with number of users
});
}
Also probably a good idea to check that global[collectionName] is actually a collection.
I came up with this code which makes the following assumptions :
collections are declared in the global scope as top level objects.
collections are searched by collection name, not the collection variable identifier.
So client code should declare their collections like this :
MyCollection=new Meteor.Collection("my-collection");
And use the function like this :
var clientResult=Meteor.call("getCollectionCount","my-collection",function(error,result){
if(error){
console.log(error);
return;
}
console.log("actual server-side count is : ",result);
});
console.log("published subset count is : ",clientResult);
The method supports execution on the client (this is known as method stub or method simulation) but will only yield the count of the collection subset replicated client-side, to get the real count wait for server-side response using a callback.
/packages/my-package/lib/my-package.js
getCollection=function(collectionName){
if(collectionName=="users"){
return Meteor.users;
}
var globalScope=Meteor.isClient?window:global;
for(var property in globalScope){
var object=globalScope[property];
if(object instanceof Meteor.Collection && object._name==collectionName){
return object;
}
}
throw Meteor.Error(500,"No collection named "+collectionName);
};
Meteor.methods({
getCollectionCount:function(collectionName){
return getCollection(collectionName).find().count();
}
});
As Meteor.users is not declared as a top level variable you have to account for the special case (yes, this is ugly).
Digging into Meteor's collection handling code could provide a better alternative (getting access to a collection handle by collection name).
Final words on this : using a method call to count a collection documents is unfortunately non-reactive, so given the Meteor paradigm this might be of little use.
Most of the time you will want to fetch the number of documents in a collection for pagination purpose (something like a "Load more" button in a posts list for example), and as the rest of the Meteor architecture you'll want this to be reactive.
To count documents in a collection reactively you'll have to setup a slightly more complicated publication as showcased in the "counts-by-room" example in the docs.
http://docs.meteor.com/#meteor_publish
This is something you definitely want to read and understand.
This smart package is actually doing it right :
http://atmospherejs.com/package/publish-counts
It provides a helper function that is publishing the counts of any cursor.
Keep track of the collections on some other property that the server has access too. You could even call it window if you really wanted to.
var wow = new Meteor.Collection("wow");
collections["wow"] = wow;
getCollectionCount: function (collectionName) {
return collections[collectionName].find().count();
}
If you don't want the package users to change how they work with collections in the app then I think you should use MongoInternals to get collections by name from the db. Not tested but here is an example:
//on server
Meteor.methods({
count: function( name ){
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var collection = db.collection( name );
return collection && collection.count({});
}
});
Another example of MongoInternals use is here. Documentation of the count() function available from the mongo driver is here.

Datanucleus Query: accessing transient collection after close

I have a specific query method in all my managers that must return a transient collection, but i want to CLOSE the query immediately after executing it.
tx.begin();
Query query=pm.newQuery(...);
Collection col=(Collection)query.execute();
pm.makeTransientAll(col,true);
query.close();
tx.commit();
The problem: The collection CANNOT be accessed after the query is closed (DN knows the identity?) otherwise it throws a "Query has been closed" error!
The solution: Create a COPY of the original collection!
Collection col=new ArrayList((Collection)query.execute());
But i want to avoid that... Even if it's a local copy and it's not a deep clone, it still allocates the space needed for the entire array of elements (so, at some point there is going to be 2x allocated memory) and i would like to avoid that.
I'm i missing something? Is there a way to avoid the creation of a clone?
Well, i found the reason of this behavior:
The query Object returned (collection) if an instance of: org.datanucleus.store.rdbms.query.ForwardQueryResult
that extends: AbstractRDBMSQueryResult
that extends: AbstractQueryResult
that extends: AbstractList
so, i get an object that is a LIST implementation, and the query result is bound to that implementation.
/** The Result Objects. */
protected List resultObjs = new ArrayList();
/**
* Method to return the results as an array.
* #return The array.
*/
public synchronized Object[] toArray()
{
assertIsOpen();
advanceToEndOfResultSet();
return resultObjs.toArray();
}
So, i cannot avoid the creation of a NEW array...

Different RavenDB collections with documents of same type

In RavenDB I can store objects of type Products and Categories and they will automatically be located in different collections. This is fine.
But what if I have 2 logically completely different types of products but they use the same class? Or instead of 2 I could have a generic number of different types of products. Would it then be possible to tell Raven to split the product documents up in collections, lets say based on a string property available on the Product class?
Thankyou in advance.
EDIT:
I Have created and registered the following StoreListener that changes the collection for the documents to be stored on runtime. This results in the documents correctly being stored in different collections and thus making a nice, logically grouping of the documents.
public class DynamicCollectionDefinerStoreListener : IDocumentStoreListener
{
public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
var entity = entityInstance as EntityData;
if(entity == null)
throw new Exception("Cannot handle object of type " + EntityInstance.GetType());
metadata["Raven-Entity-Name"] = RavenJToken.FromObject(entity.TypeId);
return true;
}
public void AfterStore(string key, object entityInstance, RavenJObject metadata)
{
}
}
However, it seems I have to adjust my queries too in order to be able to get the objects back. My typical query of mine used to look like this:
session => session.Query<EntityData>().Where(e => e.TypeId == typeId)
With the 'typeId' being the name of the new raven collections (and the name of the entity type saved as a seperate field on the EntityData-object too).
How would I go about quering back my objects? I can't find the spot where I can define my collection at runtime prioring to executing my query.
Do I have to execute some raw lucene queries? Or can I maybe implement a query listener?
EDIT:
I found a way of storing, querying and deleting objects using dynamically defined collections, but I'm not sure this is the right way to do it:
Document store listener:
(I use the class defined above)
Method resolving index names:
private string GetIndexName(string typeId)
{
return "dynamic/" + typeId;
}
Store/Query/Delete:
// Storing
session.Store(entity);
// Query
var someResults = session.Query<EntityData>(GetIndexName(entity.TypeId)).Where(e => e.EntityId == entity.EntityId)
var someMoreResults = session.Advanced.LuceneQuery<EntityData>(GetIndexName(entityTypeId)).Where("TypeId:Colors AND Range.Basic.ColorCode:Yellow)
// Deleting
var loadedEntity = session.Query<EntityData>(GetIndexName(entity.TypeId)).Where(e =>
e.EntityId == entity.EntityId).SingleOrDefault();
if (loadedEntity != null)
{
session.Delete<EntityData>(loadedEntity);
}
I have the feeling its getting a little dirty, but is this the way to store/query/delete when specifying the collection names runtime? Or do I trap myself this way?
Stephan,
You can provide the logic for deciding on the collection name using:
store.Conventions.FindTypeTagName
This is handled statically, using the generic type.
If you want to make that decision at runtime, you can provide it using a DocumentStoreListner

Resources