How do I remove nested object from an object in CakePHP? - php

CakePHP API returns result like this:
{
"status": "OK",
"themes": [
{
"Theme": {
"id": "20",
"user_id": "50",
"name": "dwdwdw",
"language_code_from": "cz",
"language_code_to": "en",
"type": "CUSTOM",
"created": "2014-10-19 15:36:05",
"count_of_cards": 0
}
}
]
}
I would like to ask, how can in remove nested Theme object to get result like this?:
{
"status": "OK",
"themes": [
{
"id": "20",
"user_id": "50",
"name": "dwdwdw",
"language_code_from": "cz",
"language_code_to": "en",
"type": "CUSTOM",
"created": "2014-10-19 15:36:05",
"count_of_cards": 0
}
]
}
Here is my CakePHP code:
$this->Theme->recursive = -1;
// GET USER ID
$themeData['user_id'] = $isSessionValid;
// GET ALL THEMES RELATED TO USER
$foundThemes = $this->Theme->find('all', array(
'conditions' => array(
'Theme.user_id' => $themeData['user_id'])
)
);
$themes = array();
// FOREACH THEMES AND GET COUNT FOR CARDS FOR EACH THEME
foreach($foundThemes as $foundTheme) {
// GET COUNT OF QUESTIONS FOR ACTUAL THEME
$countOfCards = $this->Theme->Card->find('count', array(
'conditions' => array(
'Card.theme_id' => $foundTheme['Theme']['id'])
)
);
// APPEND TO ACTUAL ARRAY
$foundTheme['Theme']['count_of_cards'] = $countOfCards;
array_push($themes,$foundTheme);
}
// SET SUCCESS RESPOSNSE
$this->set(array(
'status' => 'OK',
'themes' => $themes,
'_serialize' => array(
'status',
'themes',
)
));
Many thanks for any advice.

You can manipulate CakePHP's array formats using its built in Hash utility: http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash
What I would do would be to flatten the results:
$results = Hash::flatten($results);
Your data array will end up as a single dimensional array looking like this:
$results = array(
'status' => 'OK'
'themes.0.Theme.id' => 20,
...
'themes.1.Theme.id' => 21,
...
);
You can then use string replace to remove "Theme" from your keys:
$keys = array_keys($results);
$keys = str_replace('Theme.', '', $keys);
Then you can use Hash::expand to get your original array, now formatted how you want:
$results = Hash::expand(array_combine($keys, array_values($results)));

I dont think CakePHP supports this. if you want to do this with an easy way check the Set Utility.
http://book.cakephp.org/2.0/en/core-utility-libraries/set.html

Related

How can I build an object/array?

I am rather new to PHP so I don't know how to work with these datasets. I make a MySQL select and get back an object like this:
{
"membername": "NAME",
"bookingdate": "2020-02-03",
"categoryid": 1,
"dailyworkhourssum": "7.70"
},
{
"membername": "NAME",
"bookingdate": "2020-02-03",
"categoryid": 3,
"dailyworkhourssum": "1.2"
},
{
"membername": "NAME",
"bookingdate": "2020-02-05",
"categoryid": 3,
"dailyworkhourssum": "7.70"
},
I want to iterate through this and in the end it should look like this:
{
"membername": "NAME",
"bookingdate": "2020-02-03",
"categoryid1": true,
"categorid3": true,
"dailyworkhourssum1": "7.70",
"dailyworkhourssum3": "1.2"
},
{
"membername": "NAME",
"bookingdate": "2020-02-05",
"categoryid": 3,
"dailyworkhourssum": "7.70"
},
What this does is that it merges tow fields together (if they have the same bookingdate )into one so that I can display it in a table without reoccurring dates.
My problem:
I don't know what this type of data is called.
I don't know how to create something like this.
I can add fields to this type of data with $data->newField = example so I think that this is an object.
In JS it's called an object, but in PHP you will use an associative array instead.
In your case, I think, you have an array of associative arrays. It looks like this:
$books = [
[
"membername" => "NAME",
"bookingdate" => "2020-02-03",
"categoryid" => 1,
"dailyworkhourssum" => "7.70"
],
[
"membername" => "NAME",
"bookingdate" => "2020-02-03",
"categoryid" => 3,
"dailyworkhourssum" => "1.2"
],
[
"membername" => "NAME",
"bookingdate" => "2020-02-05",
"categoryid" => 3,
"dailyworkhourssum" => "7.70"
]
];
If you wanna merge an arrays with the same "bookingdate" then I recommend you to loop through this array and add its elements to another associative array with bookingdates as keys, and check, in case if there is such key already, then merge the arrays, like this:
$merged = [];
foreach ($books as $book) {
$date = $book['bookingdate'];
if (isset($merged[$date])) {
$merged[$date] = $merged[$date] + $book;
} else {
$merged[$date] = $book;
}
}
I think that it is not a valid code (no time, sorry), but I hope, you cautch the idea.
If you want a 'list' instead of an associative array, than you can do this:
$mergedList = array_values($merged);
Thus you will rid of string keys.
If I understood correctly, you obtain a table with 4 columns an a variable number of rows and you want to transform it to a table with a variable number of columns. For that, using a data structure where every item is different from the previous one can make everything harder than it needs. I'd suggest you use a fixed structure:
// I'm assuming you have a PHP array as starting point
$input = [
[
'membername' => 'NAME',
'bookingdate' => '2020-02-03',
'categoryid' => 1,
'dailyworkhourssum' => '7.70',
],
[
'membername' => 'NAME',
'bookingdate' => '2020-02-03',
'categoryid' => 3,
'dailyworkhourssum' => '1.2',
],
[
'membername' => 'NAME',
'bookingdate' => '2020-02-05',
'categoryid' => 3,
'dailyworkhourssum' => '7.70',
],
];
$output = [];
foreach ($input as $data) {
// We'll group by booking date
if (!isset($output[$data['bookingdate']])) {
$output[$data['bookingdate']] = [
'membername' => $data['membername'],
'bookingdate' => $data['bookingdate'],
'categoryid' => $data['categoryid'],
'dailyworkhourssum' => [],
];
}
// A single date may have several daily work hours
$output[$data['bookingdate']]['dailyworkhourssum'][] = $data['dailyworkhourssum'];
}
// We discard array keys (we only needed them to group)
echo json_encode(array_values($output));
[{
"membername": "NAME",
"bookingdate": "2020-02-03",
"categoryid": 1,
"dailyworkhourssum": ["7.70", "1.2"]
}, {
"membername": "NAME",
"bookingdate": "2020-02-05",
"categoryid": 3,
"dailyworkhourssum": ["7.70"]
}]
Wherever you consume this JSON you just need to loop the dailyworkhourssum array. You may also want to loop the entire structure before printing the table and keep a counter in order to determine the maximum number of columns so you can draw empty cells where needed (tables are rectangular).

Mustache.php rendering multi-dimensional data

I'm utilizing Mustache to template some XML responses for an API. I was wondering how I could use the XML template below to render data from this array?
The data is not rendering at all when using this code:
$result = $m->render($template, $r);
echo $result;
Here is JSON converted data:
[
{
"UUID": "655482ab-38ee-433f-b310-1f6f227113b9",
"RefUUID": "92a8ecf6-8eb6-4d1e-b88d-59b50c3b0cc9",
"company":"UAR",
"itemname":"DOOR ",
"daysinstock":"41",
"condition":"A",
"stocknumber":"F0049356",
"ic":"120-00409AL",
"price":"750.00",
"quantity":"1",
"location":"U3020",
"comments": "comment for #0"
},
{
"UUID": "655482ab-38ee-433f-b310-1f6f227113b9",
"RefUUID": "92a8ecf6-8eb6-4d1e-b88d-59b50c3b0cc9",
"company":"UAR",
"itemname":"DOOR ",
"daysinstock":"68",
"condition":"C",
"stocknumber":"F0048586",
"ic":"120-00409AL",
"price":"750.00",
"quantity":"1",
"location":"KEEP"
"comments": "comment for #1"
},
{
"UUID": "655482ab-38ee-433f-b310-1f6f227113b9",
"RefUUID": "92a8ecf6-8eb6-4d1e-b88d-59b50c3b0cc9",
"company":"UAR",
"itemname":"DOOR ",
"daysinstock":"280",
"condition":"B",
"stocknumber":"171013",
"ic":"120-00409AL",
"price":"750.00",
"quantity":"1",
"location":"YCR4"
"comments": "comment for #2"
}
]
XML template trying to render
$template = '<SupplierResponse>
<QuotedPartList>
{{#parts}}
<QuotedPart>
<BMSObject>
<UUID>{{UUID}}</UUID>
<RefUUID>{{RefUUID}}</RefUUID>
</BMSObject>
<SupplierResponseCode>AsRequested</SupplierResponseCode>
<SupplierRefLineNum>{{SupplierRefLineNum}}</SupplierRefLineNum>
<PartNumInfo>
<PartNumType>Stock</PartNumType>
<PartNum>{{stocknumber}}</PartNum>
</PartNumInfo>
<PartNumInfo>
<PartNumType>IC</PartNumType>
<PartNum>{{ic}}</PartNum>
</PartNumInfo>
<PartType>PAL</PartType>
<PartDesc>{{itemname}}</PartDesc>
<PriceInfo>
<UnitListPrice>{{price}}</UnitListPrice>
<UnitNetPrice>{{price}}</UnitNetPrice>
</PriceInfo>
<RInfo>
<Grade>{{condition}}</Grade>
<DaysInStock>{{daysinstock}}</DaysInStock>
<PartLocation>{{location}}</PartLocation>
<PartStore>{{company}}</PartStore>
</RInfo>
<Availability>
<Quantity>{{quantity}}</Quantity>
<InventoryStatus>Available</InventoryStatus>
<AvailableShipDate>2018-05-10</AvailableShipDate>
</Availability>
<LineNoteInfo>
<LineNoteMemo>{{comments}}</LineNoteMemo>
</LineNoteInfo>
</QuotedPart>
{{/parts}}
</QuotedPartList>
</SupplierResponse>';
Edit: Based on new information that came to light after I posted this answer - your issue occurred because Mustache requires data to be stored in an associative array.
// Not correct
$data = [
[
'Foo' => 'Bar'
],
[
'Biz' => 'Buz'
],
]
// Correct
$data = [
'MyData' => [
[
'Foo' => 'Bar'
],
[
'Biz' => 'Buz'
]
]
]
You could try something like this:
<?php
$objectToPassIn = [
'parts' => [
// .. your data here
]
];
// Load template and initialize Mustache
$m = new Mustache_Engine(array(
'loader' => new Mustache_Loader_FilesystemLoader('path/to/where/template/is/stored', array('extension' => '.xml'))
));
$rendered = $m->render(
'template-name-without-file-extension',
$objectToPassIn
);
Finally got it fixed. The data was not formatted correctly:
Data:
$r = array("parts"=> array(
"UUID"=> "655482ab-38ee-433f-b310-1f6f227113b9",
"RefUUID"=> "92a8ecf6-8eb6-4d1e-b88d-59b50c3b0cc9",
"company"=>"UAR",
"itemname"=>"DOOR ",
"daysinstock"=>"41",
"condition"=>"A",
"stocknumber"=>"F0049356",
"ic"=>"120-00409AL",
"price"=>"750.00",
"quantity"=>"1",
"location"=>"U3020",
"comments"=> "comment for #0",
"SupplierRefNum"=> 1
),
array(
"UUID"=> "655482ab-38ee-433f-b310-1f6f227113b9",
"RefUUID"=> "92a8ecf6-8eb6-4d1e-b88d-59b50c3b0cc9",
"company"=>"UAR",
"itemname"=>"DOOR ",
"daysinstock"=>"68",
"condition"=>"C",
"stocknumber"=>"F0048586",
"ic"=>"120-00409AL",
"price"=>"750.00",
"quantity"=>"1",
"location"=>"KEEP",
"comments"=> "comment for #1",
"SupplierRefNum"=> 2
),
array(
"UUID"=> "655482ab-38ee-433f-b310-1f6f227113b9",
"RefUUID"=> "92a8ecf6-8eb6-4d1e-b88d-59b50c3b0cc9",
"company"=>"UAR",
"itemname"=>"DOOR ",
"daysinstock"=>"280",
"condition"=>"B",
"stocknumber"=>"171013",
"ic"=>"120-00409AL",
"price"=>"750.00",
"quantity"=>"1",
"location"=>"YCR4",
"comments"=> "comment for #2",
"SupplierRefNum"=> 3
}
}
);
Code:
$result = $m->render($template, $r); // Used same template as in my original post.

Return a subset of array where a given field is present

I would like to filter the Categories embedded Array to get only those which have a parent key.
{
"_id": ObjectId("5737283639533c000978ae71"),
"name": "Swiss",
"Categories": [
{
"name": "Management",
"_id": ObjectId("5738982e39533c00070f6a53")
},
{
"name": "Relations",
"_id": ObjectId("5738984a39533c000978ae72"),
"parent": ObjectId("5738982e39533c00070f6a53")
},
{
"name": "Ambiance",
"_id": ObjectId("57389bed39533c000b148164")
}
]
}
I've tried with the find but without success.
After some research it seems that it can be done via the aggregation command but I don't like the way it works, I would prefer to use only the find command.
Also, I'm asking myself if in term of performances it wouldn't be better to store each Categories in a new collection, would it be ?
Edit, I would like to get something like this as find output :
[
{
"name": "Relations",
"_id": ObjectId("5738984a39533c000978ae72"),
"parent": ObjectId("5738982e39533c00070f6a53")
}
]
The optimal way to do is in MongoDB 3.2 using the aggregation framework. All you need is project your documents and use the $filter operator to return a subset of the "Categories" array that match your criteria, but to do this you will need to use $ifNull operator give a "default" value to the "parent" field in all those sub-documents where that field is missing then use the $ne in your cond expression which determine where a give element should be included in the subset.
db.collection.aggregate([
{ "$project" : {
"_id": 0,
"Categories": {
"$filter": {
"input": "$Categories",
"as": "catg",
"cond": {
"$ne": [
{ "$ifNull": [ "$$catg.parent", false ] },
false
]
}
}
}
}}
])
From version 3.0 backwards, you need a different approach. Instead you need to use the $map operator to return a give element if it matches your criteria or false then use the $setDifference operator to filter out all those element in the returned array which are equal to false. Of course $setDifference is fine as long as the data being filtered is "unique".
db.collection.aggregate([
{ "$project" : {
"_id": 0,
"Categories": {
"$setDifference": [
{ "$map": {
"input": "$Categories",
"as": "catg",
"in": {
"$cond": [
{ "$ne": [
{ "$ifNull": [ "$$catg.parent", false ] },
false
]},
"$$catg",
false
]}
}
},
[ false ]
]
}
}}
])
Translation in PHP gives:
db.collection.aggregate(
array(
array("$project" => array(
"_id" => 0,
"Categories" => array(
"$filter" => array(
"input" => "$Categories",
"as" => "catg",
"cond" => array(
"$ne" => array(
array("$ifNull" => array("$$catg.parent", false),
false
)
)
)
)
))
)
)
And something this:
db.collection.aggregate(
array(
array("$project" => array(
"_id" => 0,
"Categories" => array(
"$setDifference" => array(
"$map" => array(
"input" => "$Categories",
"as" => "catg",
"in" => array(
"$cond" => array(
"$ne" => array(
array("$ifNull" => array( "$$catg.parent", false ) ),
false
),
"$$catg",
false
)
),
array(false)
)
)
))
)
)
As a solution according to above mentioned description please try executing following query
db.mycoll.find({Categories:{$elemMatch:{parent:{$exists:true}}}},
{Categories:{$elemMatch:{parent:{$exists:true}}}})
The above example uses $elemMatch operator to filter elements in an embedded document.

PHP Mongo aggregate match regex

Performing a query that simulates a 'like/mysql' searching for teams on the name of the team
Team document structure
{
"_id": 9,
"name": "azerty",
"tag": "dsfds",
"desc": "ggdfgsdfgdfgdf",
"captain": 8,
"coach": 8,
"members": [{
"date_joined": "2016-03-31 15:22:09",
"user_id": 8
}, {
"date_joined": "2016-03-31 19:22:35",
"user_id": 9
}],
"current_invites": [{
"invite_id": 21,
"username": "Nikki",
"user_id": "9",
"status": 1,
"date_invited": "2016-03-31 18:32:40"
}, {
"invite_id": 22,
"username": "Nikki",
"user_id": "9",
"status": 2,
"date_invited": "2016-03-31 18:33:16"
}]
}
PHP Code =
$q = '/.*'.$q.'*./';
$result = $this->coll->aggregate(
array('$match' => array('name' => $q)),
array('$project' => array('name' => 1,'members' => array('$size' => '$members'))));
Feels like I'm going mad not knowing how to fix this.
Have used regex before after migrating to mongo but not with the combination of agg-match.
in my case i am finding the aggregated result in which you can not set the where clause so use the aggregated functions like $sort $unwind $orderby and so on i am using the all of the above mention and have the problem with the like stuff to match the string like %str% here my code in which i implement the like using $match with MongoRegex
public function getRecords($table,$where = array(),$like_key = false,$like_value = false,$offset = 1,$limit = 10,$order_column = false,$order_type = false, $isAggregate=false,$pipeline=array()){
$temp = $this->getMongoDb()->where($where);
if($like_key && $like_value){
$temp = $temp->like($like_key,$like_value);
// this like filter is for aggregated result work both on normal get record or by aggregated result
$pipeline[]=array(
'$match' => array( $like_key => new MongoRegex( "/$like_value/i" ) )
);
}
if($order_column && $order_type){
$order_by = array();
$order_by[$order_column] = $order_type;
$temp = $temp->order_by($order_by);
$pipeline[]=array(
'$sort'=>array($order_column => ($order_type =="desc")? -1 : 1)
);
}
I got the solution when I read the following aggregation framework go the aggregation framework
I hope you will get your solution to resolve your issue.

getting relational results from three tables into one nested array

i have googled for solution to my problem but nun helped me.
here i have three tables items, feeds and images. each item has one feed and one or more images.
i have 3 functions. one is to return records from items table the second one receives feeds_id (foreign key in items table) then return records from feeds table. the third function is to return all images related to items_id.
those functions are :
* To get all items in database:
function get_items(){
return $query = Database::getInstance('db')
->table('items')
->columns(
'id',
'items.rowid',
'items.feed_id as feed_id',
'title' )
->findAll();
}
* To get feed data from feeds table :
function get_feeds($id){
return $query = Database::getInstance('db')
->table('feeds')
->eq('id',$id)
->findAll();
}
* To get image data from images table :
function get_images($id){
return $query = Database::getInstance('db')
->table('images')
->columns('items_id','src as image_url',
'title as image_title',
'alt')
->eq('items_id',$id)
->findAll();
}
Then i have the following code to call those function and display the result in jsonformat:
$response['items'] = array();
$response['feeds'] = array();
$response['images'] = array();
foreach ($items = get_items() as $item) {
$response['items'][] = array(
'id' => (int)$item['rowid'],
'feed_id' => (int)$item['feed_id'],
'title' => $item['title'],
);
foreach ($feeds = get_feeds((int)$item['feed_id']) as $feed) {
$response['feeds'][] = array(
'title' => $feed['title'],
'logo_url' => $feed['logo_url'],
'site_url' => $feed['site_url'],
);
}
foreach ($images = get_images($item['id']) as $image) {
$response['images'][] = array(
'id' => $image['items_id'],
'url' => $image['image_url'],
'thumb' => $_SERVER['SERVER_NAME'] . /myServer/images/thumbs/'. 'thumb_'.basename($image['image_url']),
'title' => $image['image_title'],
'alt' => $image['alt']
);
}
}
echo json_encode($response, JSON_PRETTY_PRINT);
so, my expectation is to get json output like:
"items": [
{
"id": ,
"feed_id":
"title":
"feeds": [
{
"title": ,
"logo_url": ,
"site_url": "
}
]
"images": [
{
"id": ,
"url": ",
"thumb":
"title": "",
"alt": ""
},
{
....
}
]
}]
i mean each item array should include nested arrays of its related data coming from get_feeds and get_images functions.
instead of that, i get response like :
//here i select two items from my db
"items": [
{ //first_item
"id": ,
"feed_id":
"title":
},
{ //second_item
"id": ,
"feed_id":
"title":
}
],
"feeds": [
{ // feed data for first item
"title": ,
"logo_url": ,
"site_url": "
},
{ // feed data for second item
"title": ,
"logo_url": ,
"site_url": "
}
],
"images": [
{ // image data for first item
"id": ,
"url": ",
"thumb":
"title": "",
"alt": ""
},
{ // other images data
....
}
]
}]
as you see i am getting output without keeping relation between items, feeds and images, all of them are shown independently.
my queries are fine but i am suspecting error in my foreach statements.
i could fix this issue by joining those tree tables in one query, but i don't want to do that because i need to do validation and other operations to output comes from each table.
i appreciate your help
i found the solution. it is very easy :)
it is just like:
$response['items'][] = array(
'id' => (int)$item['rowid'],
'feed_id' => (int)$item['feed_id'],
'title' => $item['title'],
'feeds' => array(
)
'images' => array(
)
);

Categories