I have the following class diagram:
When I try to persist the $blockImage collection on a new ImageContentBlock the first ImageContentBlockImage object (always the one with $order = 1) is dropped from the collection and not persisted. I can trace its existing back until the point I'm actually persisting (and flushing) the object.
The ImageContentBlock => ImageContentBlockImage relation is defined as OneToMany, where the ImageContentBlockImage object as a ManyToOne relation with a JoinColumn. The ImageContentBlockImage => Image relation is a ManyToOne relation. All of these relations use the following cascade options: cascade={"merge", "persist", "detach"}
The objects are temporarily added, managed and edited in serialised string through Redis before they are persisted. The objects are retrieved from Redis correctly and stored back into it again.
I have the following code when I get the objects out of Redis and intent to persist them in the entity manager. The objects need to be merged (attached) after detaching them.
if ($block instanceof ImageContentBlock) {
/**
* #var ImageContentBlock $block
* #var ImageContentBlockImage $blockImage
* #var Image $mergedImage
* #var ArrayCollection $images ;
*/
$blockImages = $block->getBlockImages();
$block->setBlockImages(new ArrayCollection());
foreach ($blockImages as $blockImageUuid => $blockImage) {
// merge the image?
$image = $blockImage->getImage();
$blockImage->setImage(new Image());
$image = $entityManager->merge($image);
$blockImage->setImage($image);
// set the image block
$blockImage->setBlock($block);
if (!is_null($blockImage->id)) {
$blockImage = $entityManager->merge($blockImage);
}
if (is_null($blockImage->id)) {
$entityManager->persist($blockImage);
}
// put it back!
$block->putImageContentBlockImage($blockImage, $blockImageUuid);
}
}
I have read about the limitations of Doctrine and its bad practices. I guess this is one of them? But I do need this setup to work. How can I achieve this?
Edit 1:
I've been digging into this bug even further. I found this issue here on StackOverflow. This dude did debug the EntityManager and the UnitOfWork. When I add 3 ImageContentBlockImage objects to the ImageContentBlock object they are added to the UnitOfWork and marked for persistence. But one, the first one, is never written to the db. Their solution is to persist the temporary data to the database and not to keep in session (Redis in my case). I use a lot of requests that manage these objects, it would slow down the user experience. So how can I enforce this to work?
Edit 2:
When trying to create a new ImageContentBlock object with 3 ImageContentBlockImage objects. The output of the UnitOfWork is;
array (size=3)
'0000000001fdf8f000000000117b20f8' =>
object(stdClass)[1214]
public '__CLASS__' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlockImage' (length=63)
public 'block' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlock' (length=58)
public 'image' => string 'PagewizeProxy\__CG__\Pagewize\Domain\File\Image' (length=47)
[..]
'0000000001fdf8f200000000117b20f8' =>
object(stdClass)[1255]
public '__CLASS__' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlockImage' (length=63)
public 'block' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlock' (length=58)
public 'image' => string 'PagewizeProxy\__CG__\Pagewize\Domain\File\Image' (length=47)
[..]
'0000000001fdf8fb00000000117b20f8' =>
object(stdClass)[1257]
public '__CLASS__' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlock' (length=58)
public 'blockImages' => string 'Array(3)' (length=8)
I can see there is one ImageContentBlockImage object missing from the UnitOfWork. This is before i call the flush operation, after doing some merge operations and such. I guess i'll have to look into the flow.
Still if someone can point me into the right direction..
Edit 3:
Alright, I have been testing with the JMS Serializer to rule out the php-serialiser problem that Doctrine has. When I debug the application I can see the new ImageContentBlockImage is added with its (unique) Image object. All the ImageContentBlockImage objects have their own Image. After submitting the page that holds the Block objects, and the array is deserialised from Redis I can see that 2 ImageContentBlockImage objects have a reference to the same the Image object. This reference is unique while I'm on the page and I add an image.
Related
I have a problem with get Collection from entity "User" (UserInterface, PasswordAuthenticatedUserInterface). In entity exists Collection (relation ManyToMany) to "Property" entity, it's large and have many collections.
When trying to get properties in Request Subscriber (on any other method at request level) i've got error "SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'sess_data' at row 1".
Thans for help.
Collection in User Entity:
#[ORM\ManyToMany(targetEntity: Property::class, inversedBy: 'users')]
private Collection $properties;
Method get properties
//use Symfony\Bundle\SecurityBundle\Security;
// Security $security;
/** #var User $user */
$user = $this->security->getUser();
if($user === null ) {
return null;
}
$properties = $user?->getProperties();
When get method run in controller it works fine. I storage sessions in database, and error is with size data to save (any collections from Property objects). Can I somehow bypass or disable this?
When I try to fetch by session id user from UserRepository and run method getProperties ends with the same error.
I'm trying to persist a TradeEntity. A TradeEntity has a OneToOne relationship with a CurrencyEntity.
/**
* #ORM\OneToOne(targetEntity="Repositories\Currency\CurrencyEntity")
* #ORM\JoinColumn(name="currency", referencedColumnName="id")
*
* #var CurrencyEntity
*/
protected $currency;
I have recieved a CurrencyEntity from another object which I'm trying to insert in this new TradeEntity and persist it to the database but get an exception:
Type: Doctrine\ORM\ORMInvalidArgumentException
Message: Expected value of type "Repositories\Currency\CurrencyEntity"
for association field "Repositories\Trade\TradeEntity#$currency", got "integer" instead.
Is there no other way of me persisting TradeEntity without fetching the CurrencyEntity from the database and setting it that way?
In light of my recent discovery, i felt the need to update this answer.
Reading about Doctrine's advanced configuration, i came across Reference Proxies.
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database.
This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this:
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Old answer:
What you can do is (it defeats the purpose of an ORM, but it is possible):
$conn = $entityManager->getConnection();
$conn->insert(
'table_name',
array(
'column_name' => 'column_value',
// define all the columns here
)
);
See Doctrine's Data Retrieval And Manipulation
I have a Symfony Standard Distribution app and use Doctrine's query builder and result cache to speed up database queries. I also assign unique ids to all my caches such as
....
->getQuery()
->useResultCache(true, 2592000, 'single_product_query_id_'.$id)
->getOneOrNullResult();
....
When a product field changes I can delete this specific cache using
....
$em = $this->getDoctrine()->getManager();
$cacheDriver = $em->getConfiguration()->getResultCacheImpl();
$cache = $cacheDriver->fetch('single_product_query_id_'.$id);
if($cache){
$cacheDriver->delete('single_product_query_id_'.$id);
}
....
Deleting the cache obviously make the changes visible instantly but I found this method to be a waste because in my controller I already have the up-to-date data to be persisted in my db using doctrine so I resolved to updating my cache instead of deleting it. This is where my challenge lies because the documentation is not clear on how to do this apart from the code to save the cache which is
....
$cacheDriver->save('single_product_query_id_'.$id, $new_cache, $new_lifetime);
....
So I dug deeper by passing my cache as variable $cache to a twig template and viewing via var dump. It looked like this
....
array (size=2)
'SELECT d0_.id AS id_0, d0_.title AS title_1, d0_.imagepath AS imagepath_2, d0_.description AS description_3,.......................
array (size=1)
0 =>
array (size=57)
'id_0' => string '1' (length=1)
'title_1' => string ''Tasty Thursday'!' (length=17)
'imagepath_2' => string 'main.jpeg' (length=9)
'description_3' => string 'this and every Thursday, buy a 2kg forest cake and get a 1kg fruity forest cake FREE!!!' (length=87)
...........................
I do not know how doctrine generates this multidimensional array, I would like to get this function(s) so that I can properly populate my cache to update.
Currently, I successfully update my cache by var dumping my cache to find out which row I need to update then iterating through the array. But every time I get a new idea and add a new row to an entity I have to repeat this process especially if this entity was joined to product table.
So my question is, how can I manually build a doctrine cache array from query result or better entity?
The examples shown here (https://parse.com/docs/relations_guide#onetomany-pointers) is in iOS and Android.
Is it possible to create a Pointer data store with PHP SDK?
I just ran into the same issue trying to make a many-to-many relation and after some trial and error I got it working by doing:
$obj = new ParseObject("ObjectClassName");
$obj->setAssociativeArray("relationFieldName", array('__type' => 'Pointer', 'className' => 'relationClassName', 'objectId' => 'relationObjId'));
For example, if you want to relate a Book with a User via the Book's authors key, ObjectClassName would be Book, relationFieldName would be authors and relationClassName would be _User (remember Parse's special classes are prefixed with an underscore).
Let me know if this works for you too! It's a bit frustrating that I couldn't find anything about this on Parse's PHP documentation on relations. I had the same issue in JS before but at least this question helped me right away.
I had the same issue. There is a function in the SDK specifically for this that is more concise.
In \Parse\ParseOject:
$pointer = ParseObject::create('className', $id, true);
I found it in ParseObject.php :
/**
* Static method which returns a new Parse Object for a given class
* Optionally creates a pointer object if the objectId is provided.
*
* #param string $className Class Name for data on Parse.
* #param string $objectId Unique identifier for existing object.
* #param bool $isPointer If the object is a pointer.
*
* #return ParseObject
*/
public static function create($className, $objectId = null, $isPointer = false)
It sounds like you are trying to create a 1-many relationship. You can do this using ParseObject::getRelation, which will return an existing relation, or, create a new one (you can check out the definition here).
$obj = new ParseObject("ObjectClassName");
// get a relation (new or existing)
$relation = $obj->getRelation('myObjects');
// add objects to this relation
$relation->add([$myObject1, $myObject2, $myObject3]);
// save my object & my relation, with the new objects in it
$obj->save();
If you're trying to do many-many relations you'll want to create a Join table (technically a join 'class', like MyParseJoinClass) instead. If I misunderstood, and you're trying to perform a 1-1, you can always set an object as a property to automatically create a pointer
$obj->set('myPointer', $myPointedObject);
Internally you can use $obj->_toPointer to get an array composing a pointer referencing an object, but I would recommend you utilize the supported relation & pointer types in ParseObject instead, much easier to manage.
I'm working in a project that use Doctrine 2 in Symfony 2 and I use MEMCACHE to store doctrine's results.
I have a problem with objects that are retrieved from MEMCACHE.
I found this post similar, but this approach not resolves my problem: Doctrine detaching, caching, and merging
This is the scenario
/**
* This is in entity ContestRegistry
* #var contest
*
* #ORM\ManyToOne(targetEntity="Contest", inversedBy="usersRegistered")
* #ORM\JoinColumn(name="contest_id", referencedColumnName="id", onDelete="CASCADE"))
*
*/
protected $contest;
and in other entity
/**
* #var usersRegistered
*
* #ORM\OneToMany(targetEntity="ContestRegistry", mappedBy="contest")
*
*/
protected $usersRegistered;
Now imagine that Contest is in cache and I want to save a ContestRegistry entry.
So I retrieve the object contest in cache as follows:
$contest = $cacheDriver->fetch($key);
$contest = $this->getEntityManager()->merge($contest);
return $contest;
And as last operation I do:
$contestRegistry = new ContestRegistry();
$contestRegistry->setContest($contest);
$this->entityManager->persist($contestRegistry);
$this->entityManager->flush();
My problem is that doctrine saves the new entity correctly, but also it makes an update on the entity Contest and it updates the column updated. The real problem is that it makes an update query for every entry, I just want to add a reference to the entity.
How I can make it possible?
Any help would be appreciated.
Why
When an entity is merged back into the EntityManager, it will be marked as dirty. This means that when a flush is performed, the entity will be updated in the database. This seems reasonable to me, because when you make an entity managed, you actually want the EntityManager to manage it ;)
In your case you only need the entity for an association with another entity, so you don't really need it to be managed. I therefor suggest a different approach.
Use a reference
So don't merge $contest back into the EntityManager, but grab a reference to it:
$contest = $cacheDriver->fetch($key);
$contestRef = $em->getReference('Contest', $contest->getId());
$contestRegistry = new ContestRegistry();
$contestRegistry->setContest($contestRef);
$em->persist($contestRegistry);
$em->flush();
That reference will be a Proxy (unless it's already managed), and won't be loaded from the db at all (not even when flushing the EntityManager).
Result Cache
In stead of using you own caching mechanisms, you could use Doctrine's result cache. It caches the query results in order to prevent a trip to the database, but (if I'm not mistaken) still hydrates those results. This prevents a lot of issues that you can get with caching entities themselves.
What you want to achieve is called partial update.
You should use something like this instead
/**
* Partially updates an entity
*
* #param Object $entity The entity to update
* #param Request $request
*/
protected function partialUpdate($entity, $request)
{
$parameters = $request->request->all();
$accessor = PropertyAccess::createPropertyAccessor();
foreach ($parameters as $key => $parameter) {
$accessor->setValue($entity, $key, $parameter);
}
}
Merge requires the whole entity to be 100% fullfilled with data.
I haven't checked the behavior with children (many to one, one to one, and so on) relations yet.
Partial update is usually used on PATCH (or PUT) on a Rest API.