PHP Access a Nested Object Property Using Variable - php

I am using PHP7.
I have an Object I am trying to parse:
$RECORD = {
'name' => 'Stephen Brad Taylor',
'address' => '432 Cranberry Hills, Pittsburg',
'phone' => '708 865 456',
'Account' => (Object Vendor/Entity/User) {
'email' => 'INeedThisEmail#pleaseHelp.com' // I want to access this property.
'id' => 34,
'accessible' => ['email', 'id]
}
}
I have an array which I am using to select certain fields from RECORD:
$fieldnames = [
'name',
'address',
'phone',
'Account["email"]'
];
I am trying to parse the fieldnames from RECORD as follows:
$data[]
foreach($fieldnames as $k => $fieldname) {
$data[k] = $RECORD->$fieldname
}
The method above works for the first-level attributes: name, address, and phone. However, email returns null.
I have tried the following below and none have worked:
$data[k] = RECORD->${$fieldname}
$propertyName = '$RECORD->$fieldname'
$data[k] = ${$propertyName}
Does anyone know a way to access an object's properties using a string from an object reference?
Much gratitude <3

You can't use Account["email"] directly as a property accessor, it won't be split up to find the nested property. You need to parse it yourself.
foreach($fieldnames as $k => $fieldname) {
if (preg_match('/^(.*)\["(.*)"\]$/', $fieldname, $match) {
$data[$k] = $RECORD->{$match[1]}->{$match[2]};
} else {
$data[$k] = $RECORD->$fieldname;
}
}
Also, you need $ in $k.
This code only works for 1 level deep. If you need to deal with arbitrary levels, you'll have to write a recursive procedure. See How to access and manipulate multi-dimensional array by key names / path? for examples of how to code this.

I use anonymous functions for such cases. (PHP 7.4)
$index = fn($item) => $item->dotaItem->prop_def_index;
if (get_class($collection) === Collection::class) {
$index = fn($item) => $item->prop_def_index;
}

Related

how to make an array follow a strict form? [duplicate]

Is there a function out there to make sure that any given array conforms to a particular structure? What I mean is that is has particular key names, perhaps particular types for values, and whatever nested structure.
Right now I have a place where I want to make sure that the array getting past has certain keys, a couple holding a certain data type, and one sub-array with particular key names. I've done a lot of run-around because I was passing malformed arrays to it, and finally I'm at the point where I have a bunch of
if ( ! isset($arr['key1']) ) { .... }
if ( ! isset($arr['key2']) ) { .... }
if ( ! isset($arr['key3']) ) { .... }
I would have saved a lot of time and consternation if I could have checked that the array conformed to a particular structure beforehand. Ideally something like
$arrModel = array(
'key1' => NULL ,
'key2' => int ,
'key3' => array(
'key1' => NULL ,
'key2' => NULL ,
),
);
if ( ! validate_array( $arrModel, $arrCandidate ) ) { ... }
So, the question I'm asking is, does this already exists, or do I write this myself?
Convert array to JSON:
http://us.php.net/manual/en/function.json-encode.php
Then check against a JSON Schema:
http://json-schema.org/
http://jsonschemaphpv.sourceforge.net/
It doesn't exist built in.
Maybe try something like (untested):
array_diff(array_merge_recursive($arrCandidate, $arrModel), $arrModel)
You can use the Symfony Validation component
Installation:
composer require symfony/validator doctrine/annotations
Usage:
$myArray = [
'name' => ['first_name' => 'foo', 'last_name' => 'bar'],
'email' => 'foo#email.com'
];
$constraints = new Assert\Collection([
'name' => new Assert\Collection([
'first_name' => new Assert\Length(['min' => 101]),
'last_name' => new Assert\Length(['min' => 1]),
]),
'email' => new Assert\Email(),
]);
$validator = Validation::createValidator();
$violations = $validator->validate($myArray, $constraints);
For more details, see How to Validate Raw Values (Scalar Values and Arrays)
accepted answer make diff based on values, since it's about array structure you dont want to diff values. Insted you should use [array_diff_key()][1]
Function alone is not recursive. It will not work out of box on sample array from question.
Edit after years: Got into similar trouble whit recursive array functions, so there is mine array_diff_recursive, this does not solve original question because it compare values as well, but i believe it can be easily modified and someone can find it useful .
function array_diff_recursive(array $array1, array $array2){
return array_replace_recursive(
array_diff_recursive_one_way($array1, $array2),
array_diff_recursive_one_way($array2, $array1)
);
}//end array_diff_recursive()
function array_diff_recursive_one_way(array $array1, array $array2)
{
$ret = array();
foreach ($array1 as $key => $value) {
if (array_key_exists($key, $array2) === TRUE) {
if (is_array($value) === TRUE) {
$recurse = array_diff_recursive_one_way($value, $array2[$key]);
if (count($recurse) > 0) {
$ret[$key] = $recurse;
}
} elseif (in_array($value, $array2) === FALSE) {
$ret[$key] = $value;
}
} elseif (in_array($value, $array2) === FALSE) {
$ret[$key] = $value;
}
}
return $ret;
}//end array_diff_recursive_one_way()```
[1]: http://php.net/manual/en/function.array-diff-key.php
I know this is sort of an old post, sorry if my answer is not approppriate.
I'm in the process of writing a php package that does exactly what you are asking for, it's called Structure.
What you can do with the package is something like:
$arrayCheck = new \Structure\ArrayS();
$arrayCheck->setFormat(array("profile"=>"array"));
if ($arrayCheck->check($myArray)) {
//...
}
You can check it out here: http://github.com/3nr1c/structure
I came across a tool called Matchmaker on GitHub, which looks very comprehensive and has composer support and unit tests:
https://github.com/ptrofimov/matchmaker
You can include it into your project with
composer require ptrofimov/matchmaker.

Recursive array search - keep proper order and iterate down array

When accessing the script I am writing, you pass the category path to it when accessing the page. The script then compares the data to an array of actual categories, or a branch, that should be associated with that category.
I am setting the parents and all of its children into a tree and then going down the branch and comparing the data to ensure the customer is using a correct url. Here's a quick example of how the code works:
// Customer is accessing from site.com/store/some-cat/some-othercat
// We pass those variables with the htaccess to script.php?var=$1,$2
// We then explode that to make an array on $var[0] and $var[1]
$categoryMap = explode(",", $_GET['var']);
$categoryID = array();
$categoryInfoMap = array();
foreach ($categoryMap as $a) {
$categoryIDs[] = trim($a);
}
$getCategoryInfo = $db->fn->query("SELECT * FROM store_category");
....
// Inside while loop...
$categoryInfoMap[] = $db->result[]; // stored whole results as array
// End of the while loop
$masterKey = $mainClass->findKeyInDbArray($categoryInfoMap, 'c.path', $categoryMap[0]);
if ((isset($masterKey) && $masterKey === "0") || !empty($masterKey)) {
$thisId = $categoryInfoMap[$masterKey]['c.id'];
$thisPath = $categoryInfoMap[$masterKey]['c.path'];
$thisName = $categoryInfoMap[$masterKey]['c.name'];
$tree = $mainClass->buildTree($categoryInfoMap);
$children = $tree['children'][$thisId];
$childrenItems = "";
foreach ($categoryIDs as $cid) {
// One of the categories entered doesnt exist at all so we redirect,
// else we will go through them and make sure theyre apart of the branch
if (!$mainClass->recursive_array_search($cid, $tree)) {
... redirect them somewhere and die()
} else {
if (!$mainClass->recursive_array_search($cid, $children)) {
... redirect them somewhere and die()
} else {
!!!!!!!!!!!!============!!!!!!!!!!!!!!
THIS IS THE IMPORTANT PART HERE
!!!!!!!!!!!!============!!!!!!!!!!!!!!
}
}
}
}
... Rest of the script which works for now
Here is the functions used in the code above
public function findKeyInDbArray($products, $field, $value) {
foreach($products as $key => $product) {
if ($product[$field] === $value) {
return "$key";
}
}
return NULL;
}
public function buildTree($arr) {
$tree = array(
'children' => array()
);
$index = array(0=>&$tree);
foreach ($arr as $key => $val) {
$parent = &$index[$val['c.parentcatid']];
$node = $val;
$parent['children'][$val['c.id']] = $node;
$index[$val['c.id']] = &$parent['children'][$val['c.id']];
}
return $tree;
}
public function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value OR (is_array($value) && $this->recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
And here's an example of the tree array, from the parent node down. Shorted for visibility reasons
Array(
[c.id] => 1
[c.name] => Radios
[c.path] => radios
[c.parentcatid] => 0
[children] => (
[2] => (
[0] => 2
....
[children] => (
[3] => (
[c.id] => 3
....
[c.parentcatid] => 2
),
[4] => (
[c.id] => 4
....
[c.parentcatid] => 2
)
)
)
......
[10] => (
[0] => 10
....
[c.parentcatid] => 1
)
)
SO onto the good bits
Right now the code is working to prove that the branches have matching variables from their tree. If the item path, which is the variable we are using to compare to the url $var matches, then it will continue and work. so if in the branch the following values exist:
array(c.path => 'foo'),
array(c.path => 'bar')
And I visit the script as site.com/store/foo/bar then everything works great. If i visit the site as site.com/store/foo/notBar then it will fail, as the notBar variable is not a member of this branch. That's perfect right? Everything should work! Except it doesn't and for a good reason.
The issue here
If the item matches in the branch then it has passed the check and it's the end of the check. Not if the item is passed in the wrong order, such as site.com/store/bar/foo, then it still technically has good variables in it, but it should NOT pass since the structure is not in the order its coming down the parent array. Likewise, if another branch farther up the tree, lets say barwithNoChildren exists, i can swap foo or bar out with it and still pass, even though nothing should be there.
Hopefully you guy understand what I am asking, and can help suggest ways around this. I've been wracking my brain on this system for the last couple of days and since they want fancy urls for seo and other reasons, it's been a lot more difficult than I planned. Thanks for any suggestions!
A tree structure is not really helpful for this purpose. You should be thinking about how to create a data structure that makes it easy for you to match the input. Since your category input describes a branch of the tree, the best thing to do is build an array that you can use to match those branch descriptions to your categories efficiently.
Let's build an array where the keys are the paths for each category as described by their slugs, and the values are the category IDs. We can then immediately identify the matching category, or fail if the path is not in the array.
This breadcrumb-like structure is another pattern that is commonly used with categories. Along with the tree and flat id map, you can do pretty much anything you need. The key takeaway is to think about creating different structures with your data to accomplish different tasks. It's usually more efficient and less error prone to create a new structure that's easy to work with than it is to create complex logic to try and work with an existing structure that doesn't lend itself to the task at hand.
<?php
//Mock category records, would come from the DB in the real world
$categoryRecords = [
['id' => 1, 'title' => 'Radios', 'slug' => 'radios', 'parent_id' => 0],
['id' => 2, 'title' => 'Accessories', 'slug' => 'misc', 'parent_id' => 1],
['id' => 3, 'title' => 'Motorola', 'slug' => 'motorola', 'parent_id' => 1],
['id' => 4, 'title' => 'Handheld', 'slug' => 'handheld', 'parent_id' => 3],
['id' => 5, 'title' => 'Mobile', 'slug' => 'mobile', 'parent_id' => 3]
];
//Create an array that maps parent IDs to primary keys
$idMap = [];
foreach ($categoryRecords as $currRecord)
{
$idMap[$currRecord['id']] = $currRecord;
}
//Traverse the flat array and build the path lines
$paths = [];
$categoryIds = array_keys($idMap);
foreach ($categoryIds as $currLeafId)
{
$currCategoryId = $currLeafId;
$currLine = [];
do
{
$currLine[] = $idMap[$currCategoryId]['slug'];
$currCategoryId = $idMap[$currCategoryId]['parent_id'];
} while ($currCategoryId != 0);
$currLine = array_reverse($currLine);
$currPath = implode('/', $currLine);
$paths[$currPath] = $currLeafId;
}
//Join your input - $_GET['var'] in your example
$inputPath = implode('/', ['radios', 'motorola', 'handheld']);
//Now you can see if the incoming path matched a category
if(array_key_exists($inputPath, $paths))
{
$category = $categoryRecords[$paths[$inputPath]];
echo 'Matched category: '.$category['title'].PHP_EOL;
}
else
{
echo 'Invalid category path';
}

Check if value exists within an array of objects

I am working on an API which receives a PHP object of $POST data. I am trying to check wether the 'smsPhoneNumber' inside customFields exists but not sure how to do this.
I am currently able to check for 'email' using:
if ( property_exists( $data, 'email' ) ) {
return true;
}
Question: How to check if 'smsPhoneNumber' exists?
--
var_dump:
object(stdClass)[1515]
public 'email' => string 'email#email.com'
public 'customFields' =>
array (size=2)
0 =>
object(stdClass)[1512]
public 'name' => string 'Firstname'
public 'value' => string 'james'
1 =>
object(stdClass)[1514]
public 'name' => string 'smsPhoneNumber'
public 'value' => string '077'
You could use an array_filter to get the custom field you want.
$phoneFields = array_filter($data->customFields, function($field) {
return $field->name === 'smsPhoneNumber';
});
This will only return objects in the array that have a name property equal to smsPhoneNumber.
if (!count($phoneFields)) {
// Phone not found
}
// or
if ($phone = current($phoneFields)) {
echo "The first phone number found is " . $phone->value;
}
The drawback of using array_filter() to search for the subarray values is:
array_filter() will not stop once it finds a match; it will keep iterating even after a match is found until it reaches the end of the array.
You should use a technique that allows an early break/return.
I recommend a simple foreach() with a break.
$foundIndex = null;
foreach ($data->customFields as $index => $customFields) {
if ($customFields->name === 'smsPhoneNumber') {
$foundIndex = $index;
// or $wasFound = true;
// or $smsNumber = $customFields->value;
break;
}
}
This will prove to be very efficient and easy to read/maintain.

Optimization of an algorithm performed on a big array in PHP

I have a query that populates an array from the database. In some cases, this query returns a great amount of data, (let's say for purpose of an example, 100.000 records). Each row of the database has at least 6 or 7 columns.
$results = [
['id' => 1, 'name' => 'name', 'status' => true, 'date' => '10-01-2012'],
['id' => 2, 'name' => 'name 2', 'status' => false 'date' => '10-01-2013'],
...
]
I need to perform a substitution of some of the data inside the $results array, based on another one that give me some information about how i would change the values in the rows.
$model = [
'status' => ['function' => 'formatStatus', params => ['status']],
'date' => ['function' => 'formatDate', params => ['date']]
]
Now that i have all the data and what do i do with it i have the following routine.
foreach ($results as &$itemResult) {
$oldValues = $itemResult;
foreach ($itemResult as $attribute => &$value) {
if (isset($model[$attribute]['function'])) {
$function = $model[$attribute]['function'];
$params = $model[$attribute]['params'];
$paramsSize = count($params);
for ($i = 0; $i < $paramsSize; $i++) {
$newParams[] = $oldValues[$params[$i]];
}
$itemResult[$attribute] = call_user_func_array([$this, $function], $newParams);
$newParams = null;
}
}
}
So, for each attribute for each row of my data array, i run check for the existence of a function and params information. When the attribute in question needs to be replaced, i call the function via call_user_func_array and replace the value with the function return value.
Also notice that i am replacing the current array, not creating another, by passing the reference &$itemResult inside the loop, so in the end, i have the same array from the beginning but with all columns that needed to be replaced with its new values.
The thing is, for little arrays, this method is quite good. But for big ones, it becomes a pain.
Could you guys provide me some alternative to the problem?
Should i use another data structure instead of the PHP array?

How can I validate the structure of my PHP arrays?

Is there a function out there to make sure that any given array conforms to a particular structure? What I mean is that is has particular key names, perhaps particular types for values, and whatever nested structure.
Right now I have a place where I want to make sure that the array getting past has certain keys, a couple holding a certain data type, and one sub-array with particular key names. I've done a lot of run-around because I was passing malformed arrays to it, and finally I'm at the point where I have a bunch of
if ( ! isset($arr['key1']) ) { .... }
if ( ! isset($arr['key2']) ) { .... }
if ( ! isset($arr['key3']) ) { .... }
I would have saved a lot of time and consternation if I could have checked that the array conformed to a particular structure beforehand. Ideally something like
$arrModel = array(
'key1' => NULL ,
'key2' => int ,
'key3' => array(
'key1' => NULL ,
'key2' => NULL ,
),
);
if ( ! validate_array( $arrModel, $arrCandidate ) ) { ... }
So, the question I'm asking is, does this already exists, or do I write this myself?
Convert array to JSON:
http://us.php.net/manual/en/function.json-encode.php
Then check against a JSON Schema:
http://json-schema.org/
http://jsonschemaphpv.sourceforge.net/
It doesn't exist built in.
Maybe try something like (untested):
array_diff(array_merge_recursive($arrCandidate, $arrModel), $arrModel)
You can use the Symfony Validation component
Installation:
composer require symfony/validator doctrine/annotations
Usage:
$myArray = [
'name' => ['first_name' => 'foo', 'last_name' => 'bar'],
'email' => 'foo#email.com'
];
$constraints = new Assert\Collection([
'name' => new Assert\Collection([
'first_name' => new Assert\Length(['min' => 101]),
'last_name' => new Assert\Length(['min' => 1]),
]),
'email' => new Assert\Email(),
]);
$validator = Validation::createValidator();
$violations = $validator->validate($myArray, $constraints);
For more details, see How to Validate Raw Values (Scalar Values and Arrays)
accepted answer make diff based on values, since it's about array structure you dont want to diff values. Insted you should use [array_diff_key()][1]
Function alone is not recursive. It will not work out of box on sample array from question.
Edit after years: Got into similar trouble whit recursive array functions, so there is mine array_diff_recursive, this does not solve original question because it compare values as well, but i believe it can be easily modified and someone can find it useful .
function array_diff_recursive(array $array1, array $array2){
return array_replace_recursive(
array_diff_recursive_one_way($array1, $array2),
array_diff_recursive_one_way($array2, $array1)
);
}//end array_diff_recursive()
function array_diff_recursive_one_way(array $array1, array $array2)
{
$ret = array();
foreach ($array1 as $key => $value) {
if (array_key_exists($key, $array2) === TRUE) {
if (is_array($value) === TRUE) {
$recurse = array_diff_recursive_one_way($value, $array2[$key]);
if (count($recurse) > 0) {
$ret[$key] = $recurse;
}
} elseif (in_array($value, $array2) === FALSE) {
$ret[$key] = $value;
}
} elseif (in_array($value, $array2) === FALSE) {
$ret[$key] = $value;
}
}
return $ret;
}//end array_diff_recursive_one_way()```
[1]: http://php.net/manual/en/function.array-diff-key.php
I know this is sort of an old post, sorry if my answer is not approppriate.
I'm in the process of writing a php package that does exactly what you are asking for, it's called Structure.
What you can do with the package is something like:
$arrayCheck = new \Structure\ArrayS();
$arrayCheck->setFormat(array("profile"=>"array"));
if ($arrayCheck->check($myArray)) {
//...
}
You can check it out here: http://github.com/3nr1c/structure
I came across a tool called Matchmaker on GitHub, which looks very comprehensive and has composer support and unit tests:
https://github.com/ptrofimov/matchmaker
You can include it into your project with
composer require ptrofimov/matchmaker.

Categories