I am attempting to update multiple array elements using a PHP version of the query described in how to update sequence number in mongodb safely
Which describes the query:
db.so.update(
{ _id: new ObjectId("4f55e7ba362e2f2a734c92f8")},
{ $set : { 'subs.1.order' : 3, 'subs.2.order' : 2 } }
);
I'm building a query to add an 'order' field to my document
{
"_id": {
"$oid": "5209acfd0de2316335000001"
},
"bookListId": "116ad5af-7cc6-4652-9bb3-aea852e584e8",
"favoriteBook": [
{
"title": "One Favorite Book",
},
{
"title": "Another Favorite Book",
},
{
"title": "A Third Favorite Book",
}
]
}
..and am doing it in PHP with
$criteria = array('bookListId' => $bookListId);
$favoriteBookOrder = array();
for($i=0;$i<sizeof($order);$i++) {
$key = 'favoriteBook.'.($i+1).'.order';
$val = $order[$i];
$favoriteBookOrder[] = array($key=>intval($val));
}
$setFavoriteBookOrder = array('$set' => $favoriteBookOrder);
$collection->update($criteria, $setFavoriteBookOrder);
but this doesn't produce any result because the query doesn't have the correct organization of arrays..
error_log(json_encode($setupdated));
outputs
{"$set":[{"favoriteBook.1.order":2},{"favoriteBook.2.order":1},{"favoriteBook.3.order":3}]}
which if you notice has too many quotes, brackets around the entire array, and curly braces around each item. I believe this is what is causing the error:
"Invalid modifier specified: $set"
What is the correct syntax for building the array in PHP? Thanks!
EDIT
this is the fix
$favoriteBookOrder[] = array($key=>intval($val));
should be
$favoriteBookOrder[$key] = intval($val);
also i removed the +1 from $i because Mongo indexes are 0 based
correct query now
{"$set":{"favoriteBook.0.order":1,"favoriteBook.1.order":2,"favoriteBook.2.order":3}}
To make you query work right you have to change one line:
$favoriteBookOrder[] = array($key=>intval($val));
to
$favoriteBookOrder[$key] = intval($val);
But I think you wont be happy with result, because it will something like this:
"favoriteBook" : {
"0" : { "order" : NumberLong(123) },
"1" : { "order" : NumberLong(321) },
"2" : { "order" : NumberLong(456) }
}
I suggest you rewrite code in this way:
for($i=0;$i<sizeof($order);$i++)
$favoriteBookOrder[$i]['order'] = $order[$i];
$setupdated = array('$set' => ['favoriteBookOrder' => $favoriteBookOrder]);
and you'll get:
"favoriteBookOrder" : [
{ "order" : NumberLong(123) },
{ "order" : NumberLong(321) },
{ "order" : NumberLong(456) }
]
P.S. you can play with $pushAll modificator instead of $set, it could be more suitable for you.
Related
I have in the process of moving some code from the front-end (in JavaScript) to the server-side (which is PHP) where it will be filtered and sent out in an API call, and I can't seem to get the filter working properly on the back-end. The code takes an array of objects and filters it for the objects where a certain nested field (which is also an array of objects) contains certain values. The basic shape of the API:
{
"id": 1217,
"name": "Best product ever",
"tags": [
{
"id": 125,
"name": "Important Value",
"slug": "important-value"
},
{
"id": 157,
"name": "Value",
"slug": "value"
},
{
"id": 180,
"name": "Value",
"slug": "value"
},
{
"id": 126,
"name": "Value",
"slug": "value"
},
{
"id": 206,
"name": "Other Important Value",
"slug": "other-important-value"
}
}
The working JS code:
let productAttributes = ['important-value', 'value', 'value', 'value', 'other-important-value'];
filterResults(results) {
let filteredResults = results.filter(product => {
return product.tags.find(tag => {
return tag.slug === this.productAttributes[0];
});
});
if (this.productAttributes[0] !== 'certain important value') {
filteredResults = filteredResults.filter(product => {
return product.tags.find(tag => {
return tag.slug === this.productAttributes[4];
});
});
}
return filteredResults;
}
And the (not yet working) PHP code:
function get_awesome_products() {
$baseRequest = 'https://myawesomeapi/wp-json/wc/v3/products/?
consumer_key=xxxx&consumer_secret=xxxx&per_page=100&page=';
for ($count = 1; $count <= 9; $count++ ) {
$request = wp_remote_get( $baseRequest . (string)$count);
$body = wp_remote_retrieve_body( $request );
$data = array_values( json_decode( $body, true ));
if ($count < 2) {
$completeProductList = $data;
} else {
$completeProductList = array_merge($completeProductList, $data);
}
}
// The code above this comment is doing what I expect, the code below is not.
$filteredProducts = null;
foreach ($completeProductList as &$product) {
$tagArray = $product['tags'];
if (in_array($reg_test_array[0], $tagArray, true) &&
in_array($reg_test_array[4], $tagArray, true))
{
array_push($filteredProducts, $product);
}
unset($product);
return new WP_REST_Response($filteredProducts, 200);
The impression I get is that I need to write a custom function to take the place of Array.prototype.find(), but I'm not strong in PHP and am having trouble wrapping my head around it.
EDIT: Edited to add example of object being filtered and additional PHP code
You could also use the PHP equivalent function array_filter (among a few other array-specific functions) for this task.
Example:
// Your 0 and 4 index values from $reg_test_array
$importantTags = [ "important-value", "other-important-value" ];
$filteredProducts = array_filter($completeProductList, function($product) use ($importantTags) {
return (bool)array_intersect($importantTags, array_column($product['tags'], 'slug'));
});
return new WP_REST_Response($filteredProducts , 200);
Sandbox
This should be equivalent to the JavaScript code you posted, but done without looping through the filtered results twice.
Without knowing the context of important-value and other-important-value, and how they come to be ordered in the $attributes array, it's a little difficult to improve upon the conditional checks used. What I've written thus far however feels like a code smell to me, because it's reliant hard coded values.
function filterResults(array $results, array $attributes)
{
return array_reduce($results, function ($filteredResults, $result) use ($attributes) {
// Extract tag slugs from result
$tagSlugs = array_column($result['tags'], 'slug');
// Append result to filtered results where first attribute exists in tag slugs;
// Or first attribute is not *other-important-value* and fourth attribute exists in tag slugs
if (in_array($attribute[0], $tagSlugs) && ($attribute[0] === 'other-important-value' || in_array($attribute[4], $tagSlugs))) {
$filteredResults[] = $result;
}
return $filteredResults;
}, []);
}
So I have a web JSON API and I want to display certain objects from it on my site. Here's my JSON;
{
"descriptions": [
{
"name" = "Jim",
"class" = "3'
},
{
"name" = "luke",
"class" = "2"
},
{
"name" = "Mat",
"class" = "3"
}
I would like to make a list of the students in class 3. Currently, this is what I have to gather and pass the data although I'm not sure how to run the check to see if they're in the class and if so return there name.
$api = "https://students.com";
$json = json_decode(file_get_contents($api ), true);
$students= $json ["descriptions"][0]["class"];
Although that just returns 3 the first class value.
Note
I apologize if there's a similar post but I was unable to find one that answers my simple request. Possibly I'm just not looking in the right place. Thanks in advance
You can use array_filter() also.
$filteredStudent = array_filter($json["description"], function($value) {
return $value["class"] == 3;
});
http://php.net/manual/en/function.array-filter.php
foreach($json["descriptions"] as $value)
{
if($value['class'] == '3')
$names[] = $value['name'];
}
return $names;
I am using PHP to connect with MongoDB. My code is as follows.
// connect
$m = new MongoClient($con_string); // connect to a remote host at a given port
$db = $m->main;
$customers = $db->customer->find();
i want to return $customers collection as json document to my HTML. How can i do this?
You can do this two ways:
echo json_encode(iterator_to_array($customers));
or you can manually scroll through it:
foreach($customers as $k => $row){
echo json_encode($row);
}
Each of MongoDBs objects should have their __toString() methods correctly implemented to bring back the representation of the value.
This also will work. And you can customize your json as well.
$arr = array();
foreach($customers as $c)
{
$temp = array("name" => $c["name"], "phone" => $c["phone"],
"address" => $c["address"]);
array_push($arr, $temp);
}
echo json_encode($arr);
Other answers work, but it is good to know that the generated JSON will have the following form (in this example I use an hypothetical "name" field for your customers):
{
"5587d2c3cd8348455b26feab": {
"_id": {
"$id": "5587d2c3cd8348455b26feab"
},
"name": "Robert"
},
"5587d2c3cd8348455b26feac": {
"_id": {
"$id": "5587d2c3cd8348455b26feac"
},
"name": "John"
}
}
So in case you don't want the Object _id to be the key of each of your result objects you can add a false parameter to iterator_to_array.
Your code would be:
echo json_encode(iterator_to_array($customers, false), true);
This creates the same result as
$result = Array();
foreach ($customers as $entry) {
array_push($result, $entry);
}
echo json_encode($result, true);
which is an array of JSON objects
[
{
"_id": {
"$id": "5587d2c3cd8348455b26feab"
},
"name": "Robert"
},
{
"_id": {
"$id": "5587d2c3cd8348455b26feac"
},
"name": "John"
}
]
Here I have created a collection with a single document
db.getCollection('example').insert({"example":1});
I have tried to use Projection, and I get back the _id.
db.getCollection('example').find({"example":1},{"_id":1});
{
"_id" : ObjectId("562a6300bbc948a4315f3abc")
}
However, I need the below output as shown below.
id and not _id
ObjectId("562a6300bbc948a4315f3abc") vs "562a6300bbc948a4315f3abc"
{
"id" : "562a6300bbc948a4315f3abc"
}
Although I can process #1 and #2 on my app server(PHP based) to get the desired ouput, I am looking if there is a way to get the expected result on querying from mongo itself
MongoDB 4.0 adds the $convert aggregation operator and the $toString alias which allows you to do exactly that:
db.getCollection('example').aggregate([
{ "$match": { "example":1 } },
{ "$project": { "_id": { "$toString": "$_id" } } }
])
A main usage would most likely be though to use the _id value as a "key" in a document.
db.getCollection('example').insertOne({ "a": 1, "b": 2 })
db.getCollection('example').aggregate([
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": [
[{
"k": { "$toString": "$_id" },
"v": {
"$arrayToObject": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$ne": ["$$this.k", "_id"] }
}
}
}
}]
]
}
}}
])
Which would return:
{
"5b06973e7f859c325db150fd" : { "a" : 1, "b" : 2 }
}
Which clearly shows the string, as does the other example.
Generally though there is usually a way to do "transforms" on the cursor as documents are returned from the server. This is usually a good thing since an ObjectId is a 12-byte binary representation as opposed to a 24 character hex "string" which takes a lot more space.
The shell has a .map() method
db.getCollection('example').find().map(d => Object.assign(d, { _id: d._id.valueOf() }) )
And NodeJS has a Cursor.map() which can do much the same thing:
let cursor = db.collection('example').find()
.map(( _id, ...d }) => ({ _id: _id.toString(), ...d }));
while ( await cursor.hasNext() ) {
let doc = cursor.next();
// do something
})
And the same method exists in other drivers as well ( just not PHP ), or you can just iterate the cursor and transform the content as is more likely the best thing to do.
In fact, whole cursor results can be reduced into a single object with great ease by simply adding to any cursor returning statement, when working in the shell
.toArray().reduce((o,e) => {
var _id = e._id;
delete e._id;
return Object.assign(o, { [_id]: e })
},{ })
Or for full ES6 JavaScript supporting environments like nodejs:
.toArray().reduce((o,({ _id, ...e })) => ({ ...o, [_id]: e }),{ })
Really simple stuff without the complexity of what needs to process in the aggregation framework. And very possible in any language by much the same means.
You need to use the .aggregate() method.
db.getCollection('example').aggregate([ { "$project": { "_id": 0, "id": "$_id" } } ]);
Which yields:
{ "id" : ObjectId("562a67745488a8d831ce2e35") }
or using the .str property.
db.getCollection('example').find({"example":1},{"_id":1}).map(function(doc) {
return {'id': doc._id.str }
})
Which returns:
[ { "id" : "562a67745488a8d831ce2e35" } ]
Well if you are using the PHP driver you can do something like this:
$connection = new MongoClient();
$db = $connection->test;
$col = $db->example;
$cursor = $col->find([], ["_id" => 1]);
foreach($cursor as $doc) { print_r(array("id" => $doc["_id"])); }
Which yields:
Array
(
[id] => MongoId Object
(
[$id] => 562a6c60f850734c0c8b4567
)
)
Or using again the MongoCollection::aggregate method.
$result = $col->aggregate(array(["$project" => ["id" => "$_id", "_id" => 0]]))
Then using the foreach loop:
Array
(
[_id] => MongoId Object
(
[$id] => 562a6c60f850734c0c8b4567
)
)
One simple solution for traversing MongoCursor on PHP side is to use Generators as well as foreach or array_map($function, iterator_to_array($cursor)).
Example:
function map_traversable(callable $mapper, \Traversable $iterator) {
foreach($iterator as $val) {
yield $mapper($val);
}
}
You can meet more at PHP documentation about generators syntax.
So, now you can use/reuse it (or similar implementation) for any propose of "projecting" your data on PHP side with any amount of mapping (just like pipeline does in aggregate) but with fewer iterations amount. And this solution is pretty convenient for OOP in a case of reusing your map functions.
UPD:
Just for your case example below:
$cursor = $db->getCollection('example')->find(["example":1],["_id":1]);
$mapper = function($record) {
return array('id' => (string) $record['_id']); //see \MongoId::__toString()
}
$traversableWithIdAsStringApplied = map_traversable($mapper, $cursor);
//...
now you can proceed with more mappings applied to $traversableWithIdAsStringApplied or use just iterator_to_array for simple array retrieving.
I have created a small dataset (currently only 8 items) in an Elastic Search index.
The entries are structured as follows
{
"value_a": "Foo",
"value_b": "Bar",
"value_c": "Baz"
}
In Elastic Search this then looks as follows (taken from querying my Elastic Search endpoint directly) :
{
"_index": "foo_bar_bazindex",
"_type": "foo_bar_baz",
"_id": "4",
"_score": 1,
"_source": {
"value_a": "Foo",
"value_b": "Bar",
"value_c": "Baz"
}
}
The combinations of value a, b and c are unique.
I want to find the values of "value_c" by performing a bool filtered search with the values a and b.
In my code I have been trying this as follows:
$filter = new \Elastica\Filter\Bool();
$query = new \Elastica\Query();
$aFilter = new \Elastica\Filter\Term(['value_a' => "Foo"]);
$bFilter = new \Elastica\Filter\Term(['value_b' => "Bar"]);
$filter->addMust($aFilter);
$filter->addMust($bFilter);
$query->setFilter($filter);
$results = $this->bookingPeriodIndex->search($query)->getResults();
var_dump($results);
However this returns no results (the var dump outputs an empty array) - also manually trying this query by posting this query directly to the server:
{
"query": {
"filtered": {
"filter": {
"bool": {
"must": [
{
"term": {
"value_a": "Foo"
}
},
{
"term": {
"value_b": "Bar"
}
}
]
}
}
}
}
}
This also yields no results - I would however expect it to return one result as I have the following entry:
{
"value_a": "Foo",
"value_b": "Bar",
"value_c": "Baz"
}
Am I missing something? Could it be due to the small dataset? We use the same Bool Filter Elastica queries elsewhere in our codebase and these work as expected, however I cannot seem to get any data to return from this index.
Any help would be appreciated.
In the end I got the results I was looking for by using a bool query with two match statements:
The raw query:
{
"query": {
"bool": {
"must": [
{
"match": {
"value_a": "Foo"
}
},
{
"match": {
"value_b": "Bar"
}
}
]
}
}
}
The query using Elastica:
$query = new Query;
$booleanQuery = new Bool();
$fooMatch = new Match();
$fooMatch->setField('value_a', 'Foo');
$booleanQuery->addMust($fooMatch);
$barMatch = new Match();
$barMatch->setField('value_b', 'Bar');
$booleanQuery->addMust($barMatch);
$query->setQuery($booleanQuery);
$results = $this->index->search($query)->getResults();