How do I work with an array object in PHP? - php

I have a Laravel site I am modifying, but there are some parts of the PHP code I don't quite understand, which are "array objects" or "object arrays". You see, I don't even know what to call them and so can't find a tutorial or basic data on it. Below is the code that I am dealing with:
private function parseMetric($result, $view)
{
$data = collect([]);
$result->each(function($item) use ($data, $view) {
if (isset($item->metric->{$view})) {
$data->push((object)[
'label' => $item->metric->{$view},
'value' => $item->metric->count
]);
}
});
...
From what I can tell, this creates an object out of $result. If I json_encode this and echo it out I get this:
[{"label":"1k-25k","value":14229},
{"label":"1mm+","value":1281},
{"label":"25k-50k","value":398},
{"label":"50k-75k","value":493},
{"label":"75k-100k","value":3848},
{"label":"100k-150k","value":9921},
{"label":"150k-200k","value":4949},
{"label":"200k-250k","value":3883},
{"label":"250k-300k","value":2685},
{"label":"300k-350k","value":2744},
{"label":"350k-500k","value":4526},
{"label":"500k-1mm","value":8690}]
Now this is obviously an array of arrays... or is it? Is it an array of objects? Or is it an object containing arrays? But the most important question is, how do I access and move or change the individual objects/arrays in this object? For example, I want to take the second object/array, which is:
{"label":"1mm+","value":1281}
and move it to the end. How do I do that? How do I find it? I used the following piece of code to find it which is pretty clunky:
$pos = strpos(json_encode($result), '1mm+');
if($pos){
Log::debug('Enrich 73, I found it!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
And once I find it, how do I move that array/object to the end of the whole object?
And finally, where can I find some kind of tutorial, or documentation, that describes this construct and how to work with it?

There is no need to json_encode the data. Since the data is an instance of Laravel Collection, you can manipulate it like so
$item = $data->firstWhere('label', '1mm+'); // get the item
$data = $data->filter(fn($value, $key) => $value->label !== '1mm+') // remove $item from $data
->push($item); // move $item to the end of data

Acording to Laravel documnentation for Collections, you can try something like this :
To find index of element with name = "1mm+" :
$index = $datas->search(function ($item, $key) {
return $item['name'] == "1mm+";
});
to get an element at a given index :
$element = $datas->get($index);
to Move element at index 3 to the end :
$index = 3
$elementToMove = $data->splice($index, 1);
$datas->push($elementToMove);
Here is a link to the document used : https://laravel.com/docs/8.x/collections

Related

Laravel ->put() issue - Mixed content (JSON vs. not JSON)

I'm having an issue with using Laravels put() function, as I want put JSON content in this one single scenario.
$datatable->GroupsCollection = $datatable->GroupsCollection->put($job, '{"grade":'.$grade.'}' );
But when trying to create 'fake' JSON, the inserted value will be:
{\"grade\":'VALUE_OF_$GRADE'}
I've tried using str_replace() and stripslashes() to cut out the backwardslashes, but no bueno.
I've Googled around, and reading something about a cast was needed in the Model.
So I put in this:
protected $casts = [
'dvalue' => 'array',
];
This result in breaking existing functionality of the code.
public function getGroupsCollectionAttribute()
{
return collect($this->dvalue ? $this->dvalue['groups'] : null);
}
public function setGroupsCollectionAttribute($value)
{
$currentValue = $this->dvalue ?? new Collection();
$this->dvalue['groups'] = $currentValue->$value;
}
I 'fixed' the get, but I'm not sure how I should format the 'set' function with this new cast and setting it to an array.
Worth to notice is that we have mixed content in the DB-rows, so it's not always JSON.
Any easier way to go around this?
Ending up fixing it by simply creating an array like this:
$grade_json = array("grade" => $grade);
$datatable->GroupsCollection = $datatable->GroupsCollection->put($job, $grade_json);

Extract particular array from multidimensional array

I have a JSON array of data that I am trying to extract particular value/keys(?) from, and would like to add them into a new array.
The array looks like this:
{ "total':2000,
"achievements":[
{
"id":6,
"achievement":{},
"criteria":{
"id":2050,
"is_completed":false
},
"completed_timestamp":1224053510000
},
{
"id":8,
"achievement":{},
"criteria":{
"id":1289,
"is_completed":true
},
"completed_timestamp":0000000
}
]
}
I want to search for true in the is_completed, and then add the id from that array into a new array.
Basically, find the id's of all the key/array (sorry unsure of terminology) where is_completed is true.
I've tried something simple like finding trying to find the key of an ID, but struggling to get that to work. And also seen some of the multi-level for loop examples but can't get them to work for my data.
Example:
$key = array_search('1289', array_column($array, 'id'));
As pointed out in the comments, you could combine array_filter (to filter completed events) and array_column (to extract their IDs).
$completedAchievements = array_filter(
$array->achievements,
static function (\stdClass $achievement): bool {
return $achievement->criteria->is_completed === true;
}
);
$completedAchievementsIds = array_column($completedAchievements, 'id');
print_r($completedAchievementsIds); // Array([0] => 8)
Note: the code above supposes your JSON was decoded as an object. If it was decoded as an array, just replace -> syntax with the corresponding array index access.
Demo

Array_map through a array of objects and grab properties

So I have a var_dump($instagram->get_images()); that gives me the following output:
I want to use array_map to map through all the properties and use them inside a foreach loop later on.. but I'm running into some issues:
Here is the attempt that I have:
$mediaUrls = array_map(function($entry) {
return [
'media_url' => $entry['media_url'],
];
}, $instagram->get_images());
I'm getting back the following error:
Could someone assist me on properly array_mapping through the objects and then later be able to use foreach ($MediaUrls as $media) etc...
The error is correct. You're using array map on an object. But the object does have a ->data property that is an array. But the items in the array are objects, so you'll need to refer to their properties rather than using array syntax.
$images = $instagram->get_images();
$mediaUrls = array_map(function($entry) {
return [
'media_url' => $entry->media_url,
];
}, $images->data);
Couple of suggestions. You said, "I want to use array_map to map through all the properties and use them inside a foreach loop later on."
You can reiterate $images->data later on, so I don't really see the value of making another array just for that purpose
foreach ($images->data as $imageData) {
// do something with $imageData->media_url
}
This would be almost exactly the same as iterating the array you're making with array_map.
foreach ($images->data as $imageData) {
// do something with $imageData['media_url']
}
If you want to get an array of just the urls, you can do it more simply with array_column.
$images = $instagram->get_images();
$mediaUrls = array_column($images->data, 'media_url');
(This won't give you the same result. It will be an array of strings rather than an array of arrays.)

Parsing changing JSON from API - what design pattern to use - PHP

I am getting article/articles from an API. The JSON object varies, some articles have properties that some other do not have.
I need to iterate through the items and manipulate the properties, if they are set.
What is the best way to tackle this?
Actually for now I do something that I find very ugly...
foreach ($items as $key => $item) {
if(isset($item->title)){
$parsed[$key]['title'] = $this->formatTitle($item->title);
}
if(isset($item->salutation)){$parsed[$key]['salutation'] = $item->salutation;}
if(isset($item->eventDate) && isset($item->enventEndDate)){
$parsed[$key]['eventDates'] = $this->ersDate($item->eventDate, $item->eventEndDate);
$parsed[$key]['startDateTimestamp'] = $this->toTimestamp($item->eventDate);
} elseif(isset($item->eventDate) && !isset($item->enventEndDate)){
$parsed[$key]['eventDates'] = $this->ersDate($item->eventDate);
$parsed[$key]['startDateTimestamp'] = $this->toTimestamp($item->eventDate);
}
//... code continues ...
Since your source has unpredictable shape, I don't think there is any way around parsing the data.
You can abstract the ugliness in a separate function, so that your main script just does:
$parsed = parseAPI($items);
If you use $items = json_decode($apiResponse,true), you get an array instead of an object. You can then use the + operators on arrays along with a default array to cast all API responses to the same shape.
$defaultItem = [
'salutation' => null,
'eventDate' => null,
'eventEndDate' => null,
...
];
Now when you get items from the API, you can do:
$items = json_decode($apiResponse,true);
foreach($items as &$item) $item += $defaultItem;
Now each member of $items has all the keys you expect. If any key was missing, $defaultItem's matching key and value was inserted.

Soap xml is passing reference in php

I am calling a webservice using soap in php but I am getting the error in xml as response from the server.
The problem is that when creating the xml for the request Php introduces the id in the xml and then wherever it finds the same node it just passes the id as the reference.
Eg:-
<ns1:ChargeBU id=\"ref1\">
<ns1:ChargeBreakUp>
<ns1:PriceId>0</ns1:PriceId>
<ns1:ChargeType>TboMarkup</ns1:ChargeType>
<ns1:Amount>35</ns1:Amount>
</ns1:ChargeBreakUp><ns1:ChargeBreakUp>
<ns1:PriceId>0</ns1:PriceId>
<ns1:ChargeType>OtherCharges</ns1:ChargeType>
<ns1:Amount>0.00</ns1:Amount>
</ns1:ChargeBreakUp>
</ns1:ChargeBU>
and then when it finds the same node it does this
<ns1:ChargeBU href=\"#ref1\"/>
So how can i prevent this so that it includes the full node again instead of just passing the reference ??
I had the same issue but couldn't figure out anything to do differently within SoapClient to fix it. i ended up overriding __doRequest() to modify the xml before sending to remove the reference id's from the elements and replace the reference elements with the elements they reference. if you do this, be sure to fix __getLastRequest() as shown here.
Edit: Using unserialize(serialize($input)) before sending seems to have fixed this for me.
you can create a new copy (instance) of that array to prevent php to use refs for the same values.
for example, we have:
$item = array(
"id" => 1,
"name" => "test value"
);
and our request/response:
$response = array(
"item1" => $item,
"item2" => $item
);
by default, php will replace item2 value with reference to item1 (both items point to the same array)
in order to prevent such behaviour, we need to create two different items with the same structure, something like:
function copyArray($source){
$result = array();
foreach($source as $key => $item){
$result[$key] = (is_array($item) ? copyArray($item) : $item);
}
return $result;
}
and request/response:
$response = array(
"item1" => copyArray($item),
"item2" => copyArray($item)
);
the same by structure items are in fact different arrays in memory and php will not generate any refs in this case
I did some research, and SOAP extension, nuSOAP, WSO2 are not supported since 2010. They are full of unfixed bugs, I don't recommend to use them.
Use Zend 2 SOAP instead which does not use any unsupported extension, or if you are a Symfony fan, then try out the BeSimple SOAP boundle, which tries to fix the bugs of the SOAP extension. Don't reinvent the wheel!
I have changed the function a little bit because. if the $source is not an array ,we have a small problem in the foreach
function copyArray(Array $source){
$result = array();
if($source) { // not for empty arrays
foreach($source as $key => $item){
$result[$key] = (is_array($item) ? copyArray($item) : $item);
}
}
return $result;
}

Categories