Update embedded subdocument in array with doctrine and mongodb - mongodb

I want to update (replace) a subdocument within an array of all the documents, that have this specific embedded subdocument.
My sample content object is:
{
"_id" : ObjectId("51f289e5345f9d10090022ef"),
"title" : "This is a content",
"descriptors" : [
{
"_id" : ObjectId("51f289e5345f9d10090022f4"),
"name" : "This is a descriptor",
"type" : "This is a property"
},
{
"_id" : ObjectId("51f289e5345f9d10090022f0"),
"name" : "This is another descriptor",
"type" : "This is another property"
}
]
}
I want to find the object with a specific descriptor._id.
I want to replace the one subdocument with another one (respectivley with an updated version of the old object, but not necessarily with the same properties).
While this works well in RoboMongo / shell...
db.Content.update(
{
'descriptors._id': ObjectId("51f289e5345f9d10090022f4")
},
{
$set: {
'descriptors.$': {
"_id" : ObjectId("51f289e5345f9d10090022f4"),
"name" : "This is the updated descriptor",
"category" : "This is a new property"
}
}
},
{
'multi': true
})
...and with the plain php methods...
$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';
$mongo = new \Mongo('mongodb://localhost:27017');
$database = $mongo->selectDB('MyDatabase');
$output = $database->selectCollection('Content')->update(
array('descriptors._id' => $descriptor['_id']),
array('$set' => array('descriptors.$' => $descriptor)),
array("multiple" => true)
);
...it doesn't work with Doctrine MongoDB ODM...
$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';
$query = $dm->createQueryBuilder('Content')
->update()->multiple(true)
->field('descriptors._id')->equals($descriptor['_id'])
->field('descriptors.$')->set($descriptor)
->getQuery()->execute();
...because it fails with the following error:
Notice: Undefined offset: 2 in C:\MyProject\vendor\doctrine\mongodb-odm\lib\Doctrine\ODM\MongoDB\Persisters\DocumentPersister.php line 998
So I assume, that Doctrine MongoDB ODM needs three parts in the dot-notation. The not so nice solution would be to iterate over the properties of the subdocument and set them manually.
$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';
$query = $dm->createQueryBuilder('Content')
->update()->multiple(true)
->field('descriptors._id')->equals($descriptor['_id']);
foreach ($descriptor as $key => $value) {
$query->field('descriptors.$.'.$key)->set($value);
}
$query->getQuery()->execute();
But this will only update existing and add new properties, but won't remove old/unnecessary properties from the subdocument.
Any ideas how to solve the problem:
with a simple query
while using Doctrine MongoDB ODM
without looping over the subdocument-array in php
I'm using:
Mongo-Server: 2.4.5
PHP: 5.4.16
PHP-Mongo-Driver: 1.4.1
Composer:
"php": ">=5.3.3",
"symfony/symfony": "2.3.*",
"doctrine/orm": ">=2.2.3,<2.4-dev",
"doctrine/doctrine-bundle": "1.2.*",
"doctrine/mongodb-odm": "1.0.*#dev",
"doctrine/mongodb-odm-bundle": "3.0.*#dev"

This was a bug in ODM and should be fixed in PR #661. Please take a look and ensure that the revised test satisfies your use case.
Until the next beta of ODM is tagged, this will only reside in the master branch; however, that should match your 1.0.*#dev version requirement.

You can do this thing but in a "not easy" way becuse when you use EmbedMany or ReferenceMany in Doctrine becames a Doctrine ArrayCollection objects that implemented the ArrayCollection contains() method with (as explained here) with the PHP in_array() method with the $strict parameter set to true... this way works with ReferenceMany but not with EmbedMany!
The concept is: ArrayCollection -> array -> ArrayCollection
In the definition of Document
...
/**
* #ODM\EmbedMany(targetDocument="EmbeddedElement")
*/
private $elements = array();
...
public function getElements() { return $this->elements; }
public function setElements($elements) { $this->elements = $elements; }
public function addElement(Element $element) { $this->elements[] = $element; }
public function setElement(Element $element, $index=0)
{
$elementsArray = $this->elements->toArray();
$elementsArray[$index] = $element;
$this->elements = new ArrayCollection($elementsArray);
}
public function removeElement(Element $element)
{
$index = array_search($element, $this->elements->toArray(), $strict=false);
if($index !== false)
$this->elements->remove($index);
}
public function removeElementByIndex($index) { $this->elements->remove($index); }

Related

Magento 2.4 Sidebar\RemoveItem - afterExecute() must be an instance of Magento\Framework\App\Response\Http

I have a plugin in my module that was working in Magento 2.3, but after upgrade to 2.4 i am getting the error
TypeError: Argument 2 passed to
..\Plugin\Controller\Checkout\Sidebar\RemoveItemPlugin::afterExecute()
must be an instance of Magento\Framework\App\Response\Http, instance
of Magento\Framework\Controller\Result\Json\Interceptor given
This is the code :
use Magento\Customer\CustomerData\SectionPoolInterface;
use Magento\Framework\Serialize\Serializer\Json;
class RemoveItemPlugin
{
public function __construct(
SectionPoolInterface $sectionPool,
Json $json
) {
$this->sectionPool = $sectionPool;
$this->json = $json;
}
public function afterExecute(
\Magento\Checkout\Controller\Sidebar\RemoveItem $subject,
\Magento\Framework\App\Response\Http $result
): \Magento\Framework\App\Response\Http {
/* Get Cart Items */
$sectionNames = "cart";
$sectionNames = $sectionNames ? array_unique(\explode(',', $sectionNames)) : null;
$forceNewSectionTimestamp = false;
$response = $this->sectionPool->getSectionsData($sectionNames, (bool)$forceNewSectionTimestamp);
/* Prepare Result */
$content = $this->json->unserialize($result->getContent());
$content['cartSection'] = $response;
$content = $this->json->serialize($content);
$result->setContent($content);
return $result;
}
}
I tried the answer in this question :
https://magento.stackexchange.com/questions/351344/magento-2-4-sidebar-removeitem-afterexecute-must-be-an-instance-of-magento-f
but once I change $result to be type resultInterface, I can no longer use $result->getContent() to return the cart json. Is there a way to do this with resultInterface?

MongoDB Symfony3 pagination From To limit results using QueryBuilder

I'm using mongoDB in my symfony3 application, and I need to use setMaxResults and setFirstResult in my repository to paginate my results in my listing action. But I'm more used to use MySql as a database and I don't find how to do it in my cas.
I tried to use it as this :
$qb = $this->createQueryBuilder('DemoBundle:Entity');
$qb ->select('u');
$qb ->setMaxResults($max)
->setFirstResult($first);
But I have an error as followed :
Attempted to call an undefined method named "setMaxResults" of class
"Doctrine\ODM\MongoDB\Query\Builder
The full function is as like this :
public function search($data, $page = 0, $max = NULL, $getResult = true)
{
$qb = $this->createQueryBuilder('AresAPITournamentBundle:Tournament');
$query = isset($data['query']) && $data['query']?$data['query']:null;
$qb ->select('u');
if ($query) {
$qb
->andWhere('u.name like :query')
->setParameter('query', "%".$query."%")
;
}
if ($max) {
$qb ->setMaxResults($max)
->setFirstResult($page * $max)
;
} else {
$preparedQuery = $qb->getQuery();
}
return $getResult?$preparedQuery->execute():$preparedQuery;
}
I found the ressource in this tutorial.
How can I achieve this ?
Response was :
$qb ->limit($to)
->skip($from);

Integration tests with mongodb

I'm using mongodb as my main database, so on my integration tests I try to test if my system handles the data as expected. The process is as follow:
Insert data on needed collections to prepare scenario
Perform call to the system
Validate data change or exist on database, depending of the scenario
I'm using behat as my BDD testing tool and I implement on my FeatureContext to insert and read data from collections, something like this:
Given I have the following data on collection Brand
[
{
"_id": "575df0b00419c905492d0461",
"name": "Adidas",
"image": "adidas.png"
}
]
Then I validate the following data exists on collection Brand
[
{
"_id": "575df0b00419c905492d0461",
"name": "Adidas",
"image": "adidas.png"
}
]
EDIT
/**
* #Given I have the following data on collection :collectionName
* #param string $collectionName
* #param PyStringNode $collectionData
* #throws Exception
*/
public function iHaveTheFollowingDataOnCollection($collectionName, PyStringNode $collectionData)
{
$data = json_decode($collectionData->getRaw(), true);
foreach ($data as $index => $element) {
foreach ($element as $key => $value) {
if ($key === '_id') {
$data[$index][$key] = new \MongoDB\BSON\ObjectID($value);
}
if (strpos($key, "#") !== false) {
$key = explode("#", $key)[1];
$data[$index][$key] = array(
"\$ref" => $value["\$ref"],
"\$id" => new \MongoDB\BSON\ObjectID($value["\$id"]),
"\$db" => $value["\$db"],
);
}
if ($value === "UNIX_TIME()") {
$data[$index][$key] = time();
}
}
}
$collection = $this->db->$collectionName;
foreach ($data as $document) {
$collection->insertOne($document);
}
}
END EDIT
The issue is that I have at least 1k different scenarios on different features and with that heavy usage test randomly fails becase it don't find expected data on collection.
My first thought is that exist some delay on insertion and availability of the data.
Any thoughts and how to fix it?

OR condation in symfony find Query query

Hi I am unable to use OR condation in my following Symfony findBy query.
$searchArrayTasks = array(
"name" => new \MongoRegex('/.*'.trim($_POST['keyword']).'.*/')
);
$documents = $dm->getRepository('WorkOrganisationBundle:Tasks')->findBy($searchArrayTasks)->sort($sortArray )->limit($limit)->skip($skip);
Can any one suggest please how to use OR condation in this query.Because i want to make a search basis on different parameters Like Name OR class OR Type.
Thanks Advance
This way (certainly in your Manager) is a bad practice.
Its purpose is for really dumb request.
2 things :
-Put your code in a Repository
-And code your query in sql or dql :
public function common($qb, $limit)
{
$qb->setMaxResults($limit)
->orderBy('task.id', 'DESC');
return $qb;
}
public function findByNameClassOrType($keyword, $limit)
{
$qb = $this->createQueryBuilder('task');
$qb->select('task')
->where('task.name LIKE ?', '%'.$keyword.'%')
->orWhere('task.class LIKE ?', '%'.$keyword.'%')
->orWhere('task.type LIKE ?', '%'.$keyword.'%');
$qb = $this->common($qb, $limit);
return $qb->getQuery()->getResult();
}
Use ? symbol to be sure that Doctrine escape your strings.
EDIT (mongodb) : with Mongo use addOr($expr)
$q = $doctrineOdm->createQueryBuilder('Work\OrganisationBundle\Document\Tasks');
$q->addOr($q->expr()->field('task.name')->equals($keyword));
$q->addOr($q->expr()->field('task.type')->equals($keyword));
$result = $q->getQuery()->execute();
For more informations see https://doctrine-mongodb-odm.readthedocs.org/en/latest/reference/query-builder-api.html

How to get returned data from mongoDB in Perl?

I read http://api.mongodb.org/perl/current/MongoDB/Examples.html and seems to be only documentation from mongoDB on Perl. How do I get my query result from mongoDB in perl. Let us say into Hash. I have successfully made connection to the db so far. I managed to do inserts into collections. Now how do I issue select query and get its returned data into hash or something similar?
Update:
Example of my data
{
"_id" : ObjectId("asdhgajsdghajgh"),
"country" : "USA"
"city" : "Boston"
}
{
"_id" : ObjectId("asdhgajsdghajgh"),
"country" : "USA"
"city" : "Seattle"
}
{
"_id" : ObjectId("asdhgajsdghajgh"),
"country" : "Canada"
"city" : "Calgary"
}
My code
my $cursor = $my_collection
->find({ country => 1 })
;
while (my $row = $cursor->next) {
print "$row\n";
}
This code is not resulting any output.
I want to basically iterate through the entire collection and read document by document.
Not sure what I am doing wrong. I used the code above. I changed $cur->next to $cursor->next I am guessing it was a typo. I appreciate all the answers so far.
That's not the official documentation. Head right to the CPAN:
MongoDB::Tutorial
MongoDB::Examples
Iterating results is pretty similar to the DBI way:
use Data::Printer;
use MongoDB;
# no query is performed on initialization!
my $cursor = $collection
->find({ active => 1, country => 'Canada' }) # filter "active" records from Canada
->sort({ stamp => -1 }) # order by "stamp" attribute, desc.
->limit(1000); # first 1000 records
# query & iterate
while (my $row = $cur->next) {
# it is 'p', from Data::Printer!
p $row;
}