I am having a bit of trouble resolving things in a PHP while loop. Basically I have an array in this form (easier to represent in JSON for brevity)
{
"node2": {
"rowid": "2",
"label": "Eco-Lights - Compact Fluorescent Lamps (CFL)",
"slug": "eco-lights-compact-fluorescent-lamps-cfl",
"prefix": "/categories",
"parent": "55",
"path": null,
"weight": "100",
"featured": "0",
"active": "0"
},
"node3": {
"rowid": "3",
"label": "Light Movers, Hangers and Accessories",
"slug": "light-movers-hangers-and-accessories",
"prefix": "/categories",
"parent": "59",
"path": null,
"weight": "100",
"featured": "0",
"active": "0"
}
}
This array is about 150 elements give or take a few. The key of the node is simply "node"+rowid for ease of lookup (in the next part)
What I am trying to do is take any node and go right up until its parent is zero (i/e has no parents) and on each iteration grab the label and slug of the parent element.
So far I have done this using a while loop as below. $this->categories is the array as above. The trouble is that the loop is incorrect and it's hitting 4gb of memory and doing around 5,000 loops for an array of 150 elements with roughly one parent each so it should be something less than 500 iterations.
public function ResolveCategoryUrl($id) {
$element=$element=$this->categories['node'.$id];
$parent=$element['parent'];
while(1) {
$prev=$element;
$element=$this->categories['node'.$element['parent']];
$parts[]=$element['slug'];
$breadcrumbs[]=['label'=>$element['label'],'url'=>$element['prefix'].$element['path'].$element['data']['url_postfix']];
$parent=$element['parent'];
if($parent<=1) {
break;
}
}
return [
'path'=>array_reverse($parts),
'breadcrumbs'=>$breadcrumbs
];
}
I cannot figure out how to tell PHP to take an element and work backward up to its parent until parent = 0 then return what I want from the function.
Sorry if the explanation is difficult, I have no other way to explain it!
This sounds like a basic reverse tree search (starting at a leaf node and working back up to the root). This is accomplished easily when using recursion. Something along these lines is probably what you're looking for:
function visitNode($nodeNum, $output = [])
{
$output[] = $nodeNum;
$element = $this->categories['node' . $nodeNum];
$parent = $element['parent'];
// Recursive case: keep searching until you're at the parent
if($parent != 0) {
return visitNode($parent, $output);
}
// Base Case
return $output;
}
The output of this function will be an array of your ids representing the chain of nodes visited on the search from your leaf node to its parent. The output of calling this on a parent node will be an array with a single entry.
You may need to put in another case to ensure that the category node + $nodeNum actually exists, but it may make more sense for that to be elsewhere.
Update
To use this function to solve your particular problem and build the breadcrumbs (getting the specific output you require):
$path = [];
$breadcrumbs = [];
$myStartNode = '3';
$pathIds = array_reverse(visitNode($myStartNode));
foreach($pathIds as $pathId) {
$element = $this->categories[$pathId];
$path[] = $element->slug;
$breadcrumbs[] = [
'label' => $element['label'],
'url' => $element['prefix'].$element['path'].$element['data']['url_postfix']
];
}
return [ 'path' => $path, 'breadcrumbs' => $breadcrumbs ];
The visitNode function above was mentioned solely to try to help solve the issue I believe that you're having difficulty with. Also, it's probably best to keep that logic outside what you're trying to do because you may be required to perform that same sort of tree traversal in another context that requires different output.
You said there is no [node0].
If you are certain that all nodes are in order (no missing numbers), then you can do something like:
$i = count($arrayData) //will give you number of nodes
And you can loop through your array with something like:
for ($a=$i, $a>=1; $a--)
{
$b = $arrayData[$a];
$label = $b['label'];
}
Related
I have a use defined json string inside a database.
The JSON string has lots of levels. I know my user will define a kay called "basevalue" and place it somewhere in the json.
The problem is, I don't know ahead of time where in the JSON it will be placed, and every use is likely to place is in different places in the array, perhaps at different levels.
This is an example of the JSON data being saved by the user:
{
"name": "",
"type": "layout",
"children": [
{
"name": "",
"type": "section",
"children": [
{
"name": "",
"type": "row",
"children": [
{
"name": "",
"type": "column",
"props": {
},
"children": []
},
{
"type": "column",
"children": [
{
"type": "itemdata",
"props": {
**"basevalue": "100",**
},
"children": []
}
]
}
]
}
]
}
I'm converting this data to an array using json_decode:
$json = json_decode($json, true);
No I need to search through the array, and find the key of 'basevalue' and then get whatever value the user has input, in the case above that would be '100'.
So to the issue is, I have no idea what 'node' the 'basevalue' key will be. It could be 40 deep, it could be in the first 'children' node.
This is up to the user.
So how do I take any version of the JSON string about and return the '100'?
Many thanks.
You can recursively iterate over the data and get the value of the key basevalue. To make this search faster, we can adopt an early exit approach similar to breadth first search. By this, we call add all values who are arrays in a queue and continue our search for the key basevalue and later on deal with pending queue. This would be faster than basic recursion because a lot of times it's possible that key was on the same level but we searched all the way down on all other trees which proved out to be trivial.
Snippet:
function getBaseValue($arr,$search_key){
$pending_calls = [];
foreach($arr as $key => $value){
if(is_array($value)){
$pending_calls[] = $value; // queue them for later judgement
}else if($search_key === $key){
return $value;
}
}
foreach($pending_calls as $call){
$returned_val = getBaseValue($call,$search_key);
if($returned_val !== false) return $returned_val;
}
return false;
}
echo getBaseValue($arr,'basevalue');
Demo: https://3v4l.org/gdLK1
Hi i'm really mongodb newbie.
I have a document like this:
{
"_id": ObjectId("53182e32e4b0feedb1dea751"),
"solutions": [
[
{
"solution": "Double Room Economy (Without Breakfast)",
"board": "Room Only",
"id": "HK-15501871",
"price": 5000,
"available": "1",
"CXL": "[]",
"unique": 0
},
{
"solution": "Double Room Economy (With Breakfast)",
"board": "Room Only",
"id": "HK-15501871",
"price": 4600,
"available": "1",
"CXL": "[]",
"unique": 1
},
{
"solution": "Double Room Economy (Room Only)",
"board": "Room Only",
"id": "HK-15501871",
"price": 5500,
"available": "1",
"CXL": "[]",
"unique": 2
}
]
]
}
And i need to update the field CXL inside the second array of solutions.
so solutions.1.CXL
This is how i take document:
$collection = $this->getCollection();
$query = array("_id"=>new MongoId($id));
$document = $collection->findOne($query);
now i need to update that field without touch the other.
How can i do?
Thanks!
SOLVED THANKS TO #Sammaye
i solved in this way:
$collection->update(
array('_id' => new MongoId('..')),
array('$set' => array('solutions.0.1.CXL' => 'something'))
);
Edit
To actually update by the first index then you can do:
$db->collection->update(
['_id' => new \MongoId($id)],
['$set' => ['solutions.0.1.CLX' => 'whatever']]
);
I misread the question in posting the information below:
So what you wanna update all CXL fields in the document (since you are only searching by top level document _id)?
That isn't possible without manually pulling this document out and iterating the subdocuments in the solutions field and then resaving it.
This is becausde there is currently no way of saying, "Update all that match"
This, however, is most likely the JIRA you would want to look for: https://jira.mongodb.org/browse/SERVER-1243
As long as you know you are going to update the second element then use the index of the array to do so. But that problem next. First you need the $set operator in order not to blow away your document and just set the field value:
db.collection.update(
{ _id: ObjectId("53182e32e4b0feedb1dea751") },
{ $set: { "solutions.0.1.CXL": [ 1, 2, 3 ] } }
)
If you just want to add to the array rather than replace the whole thing, then just use $push instead:
db.collection.update(
{ _id: ObjectId("53182e32e4b0feedb1dea751") },
{ $push: { "solutions.0.1.CXL": 4 } }
)
If you are paying attention to the notation, then you will notice that the array index values are present in the field to be updated. There is a very good reason for this, which can be read on the documentation for the positional $ operator.
The issue is that you have a nested array, which as the documentation refers to, causes a problem if you try to match items within that nested array. That problem is, if you try to use the "positional" operator to find the matched index of something you look for in a query, then it will contain the value of the first array index match that it finds.
In this case that would be your "top level" array and the "found" index is 0 and not 1 as you may expect.
Please be aware of this issue if you intend to use nested arrays.
You can update like this:
update({
_id: ObjectId("53182e32e4b0feedb1dea751"),
solutions.id: HK-15501871,
solutions.CLX: "Whatever!",")
},{
$set: {"comments.$.type": abc}
}, false, true
);
You may want to go through this once
http://docs.mongodb.org/manual/reference/method/db.collection.update/
I have a key in my document whose structure is as follow:
"tag": [
{
"schemeName": "http:\/\/somesite.com\/categoryscheme2",
"name": "Test Tag2",
"value": 1,
"slug": "test_tag2"
},
{
"schemaName": "http:\/\/somesite.com\/categoryscheme3",
"name": "Test Tag3",
"value": 1,
"slug": "test_tag3"
}
]
Now, I get inputs as tag=test_tag2ANDtest_tag3. How can I write a query for this?
I tried to iterate through the loop but I didnt got any results.
Correct me if I am wrong but you don't need an $and or $elemMatch, instead:
$mongodb->collection->find(array('tags.slug'=>array(
'$in' => array('test_tag2','test_tag3'))))
Should work, however, if your English suggests what a second read does, then you can also use $all in place of $in. This will ensure that all root documents must have those slugs in them.
use $elemMatch operator to match elements inside the array.
I have a JSON file that looks similar to this:
{
"Pages":{
"/":{
"Name": "Home",
"Page": "index.php"
},
"/_admin":{
"Name": "Admin",
"Page": "_admin/index.php",
"Template": "admin",
"MobileTemplate": "admin-mobile",
"Pages":{
"/settings":{
"Name": "Settings",
"Page": "_admin/settings/index.php",
"Config": "_admin/settings/config.php",
"Pages":{
"/user":{
"Name": "Users",
"Page": "_admin/settings/user.php",
"Config": "_admin/settings/config.php",
"CatchAll": true
}
}
}
}
},
"/tasdf":{
"Name": "fs",
"Page": "index.php"
}
}
}
I am trying to loop through this array (I have used JSON decode to turn it into PHP), and for every block of "Pages" I want to add extra data.
For example, the working should look like this:
Array Loop Starts
Finds "Pages"
-Goes through "/"
-No "Pages" - continue
- Goees through "/_admin"
-Finds "Pages"
-Goes through "/settings"
-Finds "Pages"
-Goes Through "/user"
-No Pages Continue
- Goes through "/tasdf"
- No "Pages" - continue
End Loop
Everytime it goes through a part, I want it to merge with another array.
I am struggling writing code to see it keep looping everytime it finds the word "Pages" as the key. I have attempted many times but keep scrapping my code.
Any help with this would be great!
You're looking for a recursive function that scans your array to a depth of n. Something like this could work:
function findPagesInArray($myArray) {
foreach($myArray as $index => $element) {
// If this is an array, search deeper
if(gettype($element) == 'array') {
findPagesInArray($element);
}
// Reached the Pages..
if($index == 'Pages') {
// Do your task here
}
}
}
And you would now use it by calling findPagesInArray($json_object)
I have this JSON and you can see under products i have barcodes for each product what i want to do is only get the information that matches the product barcode
{
"company": "village",
"logo": "http:\/\/www.incard.com.au\/newsite\/template\/images\/movieticket\/4cinemas\/villagetop.png",
"products": [
{
"barcode": "236690091",
"name": "Weekday",
"logo-URL": "http: \/\/www.incard.com.au\/newsite\/template\/images\/movieticket\/4cinemas\/ticketpic1.png",
"price": "12.50",
"discount": "1.50"
},
{
"barcode": "236690092",
"name": "Weekend",
"logo-URL": "http: \/\/www.incard.com.au\/newsite\/template\/images\/movieticket\/4cinemas\/ticketpic1.png",
"price": "13.50",
"discount": "1.60"
},
{
"barcode": "236690093",
"name": "Gold Class",
"logo-URL": "http: \/\/www.incard.com.au\/newsite\/template\/images\/movieticket\/4cinemas\/ticketpic1.png",
"price": "13.50",
"discount": "1.60"
}
],
"store_name": "movies"
}
for example If i hit 236690091 I only what the database (MongoDB) to return
"barcode": "236690091",
"name": "Weekday",
"logo-URL": "http: \/\/www.incard.com.au\/newsite\/template\/images\/movieticket\/4cinemas\/ticketpic1.png",
"price": "12.50",
"discount": "1.50"
not every product.
This is what I have tried
public function getbarcode($barcode)
{
// select a collection (analogous to a relational database's table)
$collection = $this->db->movies->products;
// find everything in the collection
$cursor = $collection->find(array("barcode" =>"{$barcode}"));
$test = array();
// iterate through the results
while( $cursor->hasNext() ) {
$test[] = ($cursor->getNext());
}
//Print Results
print json_encode($test);
}
You can't do this. MongoDB will always return the full document and will not allow you to return only a nested part that you want to search against. I would suggest to split out the products into its own collection, and then add the company info to each product. This will also circumvent the 16MB document limit in case you have lots of products for each company.
Without changing your schema, the following code should work:
public function getbarcode($barcode)
{
$products = array();
$collection = $this->db->movies->products;
foreach( $collection->find( array( 'products.barcode' => $barcode ) ) as $item )
{
foreach( $item->products as $product )
{
if ( $product['barcode'] == $barcode )
{
$products[] = $item;
}
}
}
return $products;
}
You can't do this the way you want. MongoDB will return only whole documents or some fields from the documents (if you specify them in query). You can't return only values that are matched by your query.
You can create a separate collection that will only hold the products objects (with a reference to a collection that holds the company data) where you can directly query for the product data you want.
If you can't / won't create another collection you can find all documents that have the product with specified barcode and filter them out using PHP.
For this second approach your query should be:
$collection->find(array("products.barcode" =>"{$barcode}"),
array('products' => 1));
With this query you're reaching into objects and returning only documents that have the barcode you are looking for.
Also, in this query you will only return the products property from your document and not the whole document. The products property will contain all the child objects, not just the one you are trying to find.
In your while loop you should check the values and filter them out properly.