I need to iterate over objects in PHP and to apply a certain function on each and every single value in this object.
The objects are absolutely arbitrary. They can include vars, another objects, arrays, arrays of objects and so on...
Is there a generic method to do so? If yes, how?
Usage example:
RESTful API which receives requests in JSON format.
json_decode() is executed on request body and creates an arbitrary object.
Now, it is good, for example, to execute mysqli_real_escape_string() on every value in this object before further validations.
OBJECT EXAMPLE:
{
"_id": "551a78c500eed4fa853870fc",
"index": 0,
"guid": "f35a0b22-05b3-4f07-a3b5-1a319a663200",
"isActive": false,
"balance": "$3,312.76",
"age": 33,
"name": "Wolf Oconnor",
"gender": "male",
"company": "CHORIZON",
"email": "wolfoconnor#chorizon.com",
"phone": "+1 (958) 479-2837",
"address": "696 Moore Street, Coaldale, Kansas, 9597",
"registered": "2015-01-20T03:39:28 -02:00",
"latitude": 15.764928,
"longitude": -125.084813,
"tags": [
"id",
"nulla",
"tempor",
"do",
"nulla",
"laboris",
"consequat"
],
"friends": [
{
"id": 0,
"name": "Casey Dominguez"
},
{
"id": 1,
"name": "Morton Rich"
},
{
"id": 2,
"name": "Marla Parsons"
}
],
"greeting": "Hello, Wolf Oconnor! You have 3 unread messages."
}
If you just need to walk over the data and won't need to re-encode it, json_decode()'s second parameter, $assoc will cause it to return an associative array. From there, array_walk_recursive() should work well for what you're after.
$data = json_decode($source_object);
$success = array_walk_recursive($data, "my_validate");
function my_validate($value, $key){
//Do validation.
}
function RecursiveStuff($value, $callable)
{
if (is_array($value) || is_object($value))
{
foreach (&$prop in $value) {
$prop = RecursiveStuff($prop);
}
}
else {
$value = call_user_func($callable, $value);
}
return $value;
}
And use it like:
$decodedObject = RecursiveStuff($decodedObject, function($value)
{
return escapesomething($value); // do something with value here
});
You can just pass function name like:
$decodedObject = RecursiveStuff($decodedObject, 'mysqli_real_escape_string');
Related
I've got the following data structure:
Array -> Object -> Array -> Object -> Object
[
{
"id":6834,
"contract_id":13,
"schedule_column_values":[
{
"id":34001,
"field_value":{
"id":324241,
"value":10,
"field":{
"id":1,
"signature":"ios"
}
}
},
{
"id":34001,
"field_value":{
"id":324241,
"value":10,
"field":{
"id":1,
"signature":"android"
}
}
}
]
}
]
What I'm trying to achieve is that if a field has the signature of "android", remove its grandparent object from schedule_column_values. Basically, if a signature is "android", the final data would look like this:
[
{
"id": 6834,
"contract_id": 13,
"schedule_column_values": [
{
"id": 34001,
"field_value": {
"id": 324241,
"value": 10,
"field": {
"id": 1,
"signature": "ios"
}
}
}
]
}
]
This is just an example but the structure is always the same and we always know what signature we're looking for. It could be anything other than android but we know the string we're looking for.
I've tried a nested foreach loop and tried unset but it doesn't seem to work. The other way is I've set a NULL to object value of schedule_column_values when the signature of field is matched, but I cannot have NULL in the object.
What would be a good way to filter out this structure?
This is a perfect use case for array_filter:
$filtered_array = [];
foreach($array as $grandparent){
$filtered_schedules = array_filter(
$grandparent->schedule_column_values,
function($item){
return $item->field_value->field->signature !== 'android';
}
);
$altered_grandparent = clone $grandparent;
$altered_grandparent->schedule_column_values = $filtered_schedules;
$filtered_array[] = $altered_grandparent;
}
I have the following which I would like to order alphabetically by the Key i.e first for each array group would be "bname", followed by "created_at".
{
"leads": [
{
"lead_id": 1,
"zoho_lead": null,
"bname": "ABC Limited",
"tname": "ABC",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-09-06 15:54:21",
"updated_at": "2017-09-06 15:54:21",
"user": "Sean McCabe",
"source": "Unknown",
"industry": "None"
},
{
"lead_id": 2,
"zoho_lead": 51186111981,
"bname": "Business Name Limited",
"tname": "Trading Name",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-06-01 12:34:56",
"updated_at": null,
"user": "John Doe",
"source": "Unknown",
"industry": "None"
}
]
}
I'm trying to use ksort like so in the foreach loop:
class LeadController extends Controller
{
use Helpers;
public function index(Lead $leads)
{
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
$lead->ksort();
}
return $leads;
}
But I get the following error:
Call to undefined method Illuminate\\Database\\Query\\Builder::ksort()
How do I use this function, or is there a Laravel way of doing this, or a better way altogether?
Thanks.
Managed to get it to return with the Keys in alphabetical order, so below is the solution in-case someone else should require it:
public function index(Lead $leads)
{
$leadOut = Array();
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
//Convert to Array
$leadOrder = $lead->toArray();
//Sort as desired
ksort($leadOrder);
//Add to array
$leadOut[] = $leadOrder;
}
return $leadOut;
}
There is likely a cleaner way to do this, but it works for my instance, and perhaps additional answers may be posted that are better.
You could do something like:
return Lead::with('user', 'source', 'industry')->get()->map(function ($lead) {
$item = $lead->toArray();
$item['user'] = $lead->user->name;
$item['source'] = $lead->source->name;
$item['industry'] = $lead->industry->name;
ksort($item);
return $item;
});
This should be much more efficient as it will eager load the relationships rather than make 3 extra queries for each iteration.
I want to remove Extra array from this JSON "data".
how to do this in PHP. Is it any function in PHP that solve it.?
{
"data": [
[
{
"user_id": "654120",
"user_name": "Jhon_Thomsona",
"user_image": null
}
],
[
{
"user_id": "1065040766943114",
"user_name": "Er Ayush_Gemini",
"user_image": "KP8LSHQFwk.png"
}
]
]
}
I want my final array to look like this:
{
"data": [
{
"user_id": "654120",
"user_name": "Jhon_Thomsona",
"user_image": null
},
{
"user_id": "1065040766943114",
"user_name": "Er Ayush_Gemini",
"user_image": "KP8LSHQFwk.png"
}
]
}
You can remove the extra array layer around each user object by mapping reset over the elements of data, then reencoding as JSON.
$data = json_decode($json);
$data->data = array_map('reset', $data->data);
$json = json_encode($data);
Of course, if you are creating this JSON yourself, you should avoid creating this structure to begin with rather than altering it after the fact.
<?php
$foo = json_decode($yourjson);
$data = [];
foreach($foo->data as $array) $data = array_merge($data, $array);
$foo->data = $data;
$yourjson = json_encode($foo);
EDIT Use of array_merge + Oriented object
I would like to flatten an object. This is what I've got so far:
{
"1": {
"id": 1,
"name": "parent",
"children": {
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
}
}
},
"2":{
"id": 2,
"name": "parent2"
}
}
And this is what I would like to accomplish. So keep the same order but flatten the object:
{
"1": {
"id": 1,
"name": "parent",
},
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
},
"2": {
"id": 2,
"name": "parent2"
}
}
So far I haven't found a solution to this. I've tried a function without much success:
protected function _flattenObject($array)
{
static $flattened = [];
if(is_object($array) && count($array) > 0)
{
foreach ($array as $key => $member) {
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
$this->_flattenObject($member);
}
}
}
return $flattened;
}
The tough part for me is to keep the same order (children below its parent). And the function mentioned above also removes all objects and almost only keeps the keys with its value, so it wasn't a great success at all.
Hopefully somebody over here knows a good solution for this.
By the way, the reason I want such flatten structure is because the system I have to work with, has trouble handling multidimensional arrays and objects. And I still want to display an hierarchy, which is possible with the flatten structure I described, because the objects actually contain a "level" key as well so I can give them some padding based on the "level" while still showing up below their parent.
EDIT:
The JSON didn't seem to be valid, so I modified it a bit.
The main problem seems to be that you are not doing anything with the returned results of your recursive function. Unless using static inside a method does some magic that I don't know of...
So this section:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// What happens with the returned value?
$this->_flattenObject($member);
}
Should probably be more like this:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// Add the returned array to the array you already have
$flattened += $this->_flattenObject($member);
}
Here is code that works. It adds a field "level" to your objects, to represent how many levels deep in the original hierarchy they were.
<?php
$obj = json_decode('[{
"id": 1,
"name": "parent",
"children": [{
"id": 4,
"name": "child1",
"parent": 1
}, {
"id": 5,
"name": "child2",
"parent": 1
}]
}, {
"id": 2,
"name": "parent2"
}]');
function _flattenRecursive($array, &$flattened, &$level)
{
foreach ($array as $key => $member) {
$insert = $member;
$children = null;
if (is_array($insert->children)) {
$children = $insert->children;
$insert->children = array();
}
$insert->level = $level;
$flattened[] = $insert;
if ($children !== null) {
$level++;
_flattenRecursive($children, $flattened, $level);
$level--;
}
}
}
function flattenObject($array)
{
$flattened = [];
$level = 0;
_flattenRecursive($array, $flattened, $level);
return $flattened;
}
$flat = flattenObject($obj);
var_dump($flat);
?>
Hi guys I was wonder how you can return the index based on ID with the following.
$friends = '{
"data": [
{
"name": "Paul",
"id": "12000"
},
{
"name": "Bonnie",
"id": "120310"
},
{
"name": "Melissa",
"id": "120944"
},
{
"name": "Simon",
"id": "125930"
},
{
"name": "Anthony",
"id": "120605"
},
{
"name": "David",
"id": "120733"
}
]
}';
$obj = json_decode($friends);
for example, I want to get print the name based on ID. I've had a go at using array_search but it didn't like the structure of the array. I will be using it inside a sql loop and passing the ID from the query to return the name string from the array.
print $obj->{'data'}[0]->{'name'};
//where 0 is returned index based on a defined ID.
thanks a lot
The json_decode makes your variables into objects. To do a search, you could use:
// The extra true at the end makes it decode to associative arrays
$json = json_decode($friends, true);
findByName($json, 1234);
function findNameById($obj, $id) {
foreach($obj as $o) {
if($o['id'] == $id) return $o['name'];
}
return false;
}
You will need PHP 5.3 in order to utilize the following closure and enhanced ternary operator (prints name if record is found; otherwise, prints 'No record found.'):
$findById = function($id) use ($friends) {
foreach (json_decode($friends)->data as $friend) {
if ($friend->id === $id) return $friend->name;
}
return;
};
echo $findById('125930') ?: 'No record found.';
Nice thing about this is that you can pass this callable around and it will remember the scope it was defined with. This means that you can pass any ID but the $friends array will always be available to the closure.