Elegant way to validate multidimensional $_POST - php

I have an user input like this: $_POST['multi']['dim']['form'] = 1.213.
I want to validate this and at the moment I use this function:
public function checkFloat($number, $min = null, $max = null)
{
$num = filter_var($number, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND);
if($num === false || ($min !== null && $num < $min) || ($max !== null && $num > $max))
return false;
return $num;
}
But befor calling this function I have to check first that $_POST['multi']['dim']['form'] is set.
So every call looks like
if(isset($_POST['multi']['dim']['form']) && ($num = checkFloat($_POST['multi']['dim']['form']) !== false))
{
// do something here
}
If I do not check first, if the variable is set, PHP will throw a notice.
I noticed the PHP function filter_input, but it seems that this is not working with multidimensional $_POST.
I thought about a wrapper like
function checkFloat($names_of_the_fields,$min,$max)
{
// check if $_POST[$name][$of][$the][...] is set
// make the validation
}
But I'm not sure how to pass the $name_of_the_fields.
Here I thought of an array $arr['key1']['key2'][...]. but since I not know how deep this is, i have to run a lot is_array checks. Or I pass an array like $arr = ['key1','key2',...].
Is there a nice and clean way to do this? Should I ignore the notice?
Or should I go on with if(isset.. && checkFloat...)?
Changing the form and using eval() is not an option.
Thanks in advance
EDIT 1:
is_array($var) is not that slow, if $var is setted. So it would be ok, if I use a function that checks structure. But the question is still, if this is a good way, or if there is a better (maybe faster) way to do this.

How about this? You'll like it :D
function filter_input_simple($type, $name, $filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
static $vars;
if (!$vars) {
$vars = array(
INPUT_GET => filter_input(INPUT_SERVER, 'QUERY_STRING'),
INPUT_POST => file_get_contents('php://input'),
INPUT_COOKIE => filter_input(INPUT_SERVER, 'HTTP_COOKIE'),
);
$s = array('&', '&', ';[; ]++');
foreach ($vars as $t => $var) {
$tmp = array();
foreach (preg_split("#{$s[$t]}#", $var, -1, PREG_SPLIT_NO_EMPTY) as $i) {
list($k, $v) = explode('=', $i, 2) + array(1 => '');
$tmp[urldecode($k)] = urldecode($v);
}
unset($tmp['']);
$vars[$t] = $tmp;
}
$vars += array(INPUT_REQUEST =>
$vars[INPUT_COOKIE] + $vars[INPUT_POST] + $vars[INPUT_GET]
);
}
$type = (int)$type;
$name = filter_var($name);
if (!isset($vars[$type][$name])) {
return null;
}
return filter_var($vars[$type][$name], $filter, $options);
}
Usage:
// This function does not use extracted values as $_GET,
// so put QueryString on your browser for testing.
//
// ?foo[bar][validated_float]=1,234.213
$options = array(
'options' => array(
'min_range' => 1234,
'max_range' => 1235,
),
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
);
var_dump(filter_input_simple(INPUT_GET, 'foo[bar][validated_float]', FILTER_VALIDATE_FLOAT, $options));

After some thinking and testing I came up with this:
I splitted each validation function up into two functions and added a function that checks if an array key exists.
validate[Type]Var($val, $options): checks if $val contains a valid value. (no changes here)
validate[Type]Input(&$inputArr,$keys,$options): calls getArrayValue(&array,$keys) and if the key exists, it calls validate[Type]Val() to validate the value.
In detail:
public function getArrayValue(&array,$keys)
{
$key = array_shift($keys);
if(!isset($array[$key]))
return null;
if(empty($keys))
return $array[$key];
return $this->getArrayValue($array[$key],$keys);
}
public function validateTypeInput(&$inputArr, $keys, $options = [])
{
$value = $this->getArrayValue($inputArr, $keys);
if(isset($value))
return $this->validateTypeVal($value,$options);
else
return null; // or return anything else to show that the value was invalid
}
The function can be called by
ValidationClass->validateTypeInput($_POST,['key1','key2','key3'],$options);
to validate $_POST['key1']['key2']['key3'].
Notice: I wrote an validateTypeInput for each type, because some of them are different. Eg. if you validate checkbox input, you dont want a not setted input to be considered invalid.

It won't validate the structure of the data passed to it, but it will ensure that each item passes the checkFloat function:
function validate_info($info) {
if (is_array($info)) {
// $info is an array, validate each of its children
foreach ($info as $item) {
// If item is invalid, stop processing
if (validate_info($item) === false) { return false; }
}
// If each $item was valid, then this $info is valid.
return true;
} else {
// $info is a single item
return checkFloat($info, [YOUR_MIN_VAL], [YOUR_MAX_VAL]);
}
}
The function will return true if every item in $info returns true for checkFloat. If any value in $info returns false, then validate_info will return false. This function uses recursion to check the whole array, regardless of structure.
edit: This function does use the is_array function, but you seem to be mistakenly thinking that the is_array function is slow. The is_array function runs in constant time, which can be verified by checking the PHP source code.

Class Definition:
/**
* Object for filter_input_array_recursive().
*/
class FilterObject {
private $filter;
private $options;
/**
* Constructor.
*
* #param int $filter same as ones for filter_input().
* #param mixed $options same as ones for filter_input().
*/
public function __construct($filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
$this->filter = $filter;
$this->options = $options;
}
public function getFilter() {
return $this->filter;
}
public function getOptions() {
return $this->options;
}
}
Function Definition:
/**
* Apply filter_input() recursively.
*
* #param int $type INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_REQUEST are supported.
* #param array $filters Multi-demensional array, which contains FilterObject for each leaf.
* #return array
*/
function filter_input_array_recursive($type, array $filters) {
static $recursive_static;
static $flag_match;
static $is_not_array;
static $filter_array;
static $types;
if (!$flag_match) {
/* initialize static variables */
$types = array(
INPUT_GET => $_GET,
INPUT_POST => $_POST,
INPUT_COOKIE => $_COOKIE,
INPUT_REQUEST => $_REQUEST,
);
$flag_match = function ($v, $f) {
return (int)(isset($v['flags']) ? $v['flags'] : $v) & $f;
};
$is_not_array = function ($v) {
return !is_array($v);
};
$filter_array = function ($v) {
return !is_array($v) ? $v : false;
};
}
$recursive = $recursive_static;
if (!$recursive) {
/* only for first loop */
$type = (int)$type;
if (!isset($types[$type])) {
throw new \InvalidArgumentException('unknown super global var type');
}
$var = $types[$type];
$recursive_static = true;
} else {
/* after first loop */
$var = $type;
}
$ret = array();
foreach ($filters as $key => $value) {
$isset = isset($var[$key]);
if (is_array($value)) {
// apply child filters
$ret[$key] = filter_input_array_recursive($isset ? $var[$key] : array(), $value);
} else {
if (!($value instanceof FilterObject)) {
// create default FilterObject for invalid leaf
$value = new FilterObject;
}
$filter = $value->getFilter();
$options = $value->getOptions();
// check if key exists...
// true -> apply filter_var() with supplied filter and options
// false -> regard as null
try {
$ret[$key] = $isset ? filter_var($var[$key], $filter, $options) : null;
} catch (Exception $e) {
$recursive_static = false;
throw $e;
}
if ($flag_match($options, FILTER_FORCE_ARRAY | FILTER_REQUIRE_ARRAY)) {
// differently from filter_input(),
// this function prevent unexpected non-array value
if (!is_array($ret[$key])) {
$ret[$key] = array();
}
// differently from filter_input(),
// this function prevent unexpected multi-demensional array
if ($flag_match($options, FILTER_FORCE_ARRAY)) {
// eliminate arrays
$ret[$key] = array_filter($ret[$key], $is_not_array);
} else {
// change arrays into false
$ret[$key] = array_map($filter_array, $ret[$key]);
}
}
}
}
if (!$recursive) {
/* only for first loop */
$recursive_static = false;
}
return $ret;
}
Usage:
$_POST['foo']['bar']['validated_float'] = '1,234.213';
$_POST['foo']['forced_1d_array'] = array('a', array('b'), 'c');
$_POST['foo']['required_1d_array'] = array('a', array('b'), 'c');
$_POST['foo']['required_scalar'] = array('a', array('b'), 'c');
When inputs are like this,
var_dump(filter_input_array_recursive(INPUT_POST, array(
'foo' => array(
'bar' => array(
'validated_float' => new FilterObject(
FILTER_VALIDATE_FLOAT,
array(
'options' => array(
'min_range' => 1234,
'max_range' => 1235,
),
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
)
),
),
'forced_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_FORCE_ARRAY),
'required_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
'required_scalar' => new FilterObject,
),
)));
this snippet will output...
array(1) {
["foo"]=>
array(4) {
["bar"]=>
array(1) {
["validated_float"]=>
float(1234.213)
}
["forced_1d_array"]=>
array(2) {
[0]=>
string(1) "a"
[2]=>
string(1) "c"
}
["required_1d_array"]=>
array(3) {
[0]=>
string(1) "a"
[1]=>
bool(false)
[2]=>
string(1) "c"
}
["required_scalar"]=>
bool(false)
}
}

Related

Iterate through complex multidimensional array (Trie data structure on PHP , code Improvement)

Recently I was faced with a coding challenge where I had to build a simple trie in php, I managed to do it using php and foreach loops but I'm not happy with the code itself (seems not solid as it should be) so I'm trying to implement it using php's iterators.
So, I have a complex array ( a trie ), for example:
array(
'a' => array(),
'b' => array(
'a' => array(
'c' => array(
'o' => array(
'n' => array()
)
)
)
),
'x' => array(
'x' => array(
'x' => array()
)
)
);
And I want to check if 'bacon' it's a word stored on this trie, the process to find it should be by iterating through the array and check if each node it's nested and exists, for instance: I need in the root the element with key 'b', then inside the array array['b'] , I need to check if I there is array['b']['a'] , then ['b']['a']['c'] and so on.
With a foreach loop I was able to do so by passing the new array by reference and check the keys. Now using a iterator it seems I'm hammering the code a bit ( and the fact that when doing foreachs php copies the array, makes me think that this solution might use a lot more memory than using iterators).
So the code until now it's a while loop that has a condition finished that stops on fail (the current array doesn't have the key I'm searching) or success ( the word it's complete ):
// OUTSIDE THE LOOP
$finished = false;
$string = 'bacon';
$string = str_split($string);
$queue = new SplQueue();
// Enqueue all the letters to the queue -> skipping this because it's boring
// FIRST WHILE LOOP
$iterator = new ArrayIterator($array);
$iterator->key(); // No match with queue -> check next key
// SECOND WHILELOOP
$iterator->next();
$iterator->key(); // Matches with the key I want do dequeue (B),
$next = new ArrayIterator($array[$iterator->key()]);
$queue->dequeue();
// THIRD WHILE LOOP
$next->key(); // Match [A] -> create new iterator
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue();
// 4TH WHILE LOOP
$next->key(); // Match [C] -> create new iterator
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue();
// 5TH WHILE LOOP
$next->key(); // Match [O] -> create new iterator
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue();
// 5TH WHILE LOOP
$next->key(); // Match [N]
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue(); // queue empty, throw success
So, up until now it's this I have, but the fact I'm creating a new ArrayIterator on each loop it's bothering me, so I was hoping to hear if someone has a better solution to this problem.
Thanks in advance.
Here is code for a recursive algorithm which will iterate any number of levels:
<?php
$trie = array(
'a' => array(),
'b' => array(
'a' => array(
'c' => array(
'o' => array(
'n' => array()
)
)
)
),
'x' => array(
'x' => array(
'x' => array()
)
)
);
/**
* #param string $word
* #param array $array
* #param int [$i = 0]
*/
function findWord($word, $array, $i = 0)
{
$letter = substr($word, $i, 1);
if (isset($array[$letter])) {
if ($i == strlen($word) - 1) {
return true;
} else if ($i < strlen($word)) {
return findWord($word, $array[$letter], $i + 1);
}
}
return false;
}
if (findWord('bacon', $trie)) {
die("Did find word.");
} else {
die("Didn't find word.");
}
Here is code for a iterative algorithm which will iterate any number of levels and should be memory and cpu efficient:
<?php
$trie = array(
'a' => array(),
'b' => array(
'a' => array(
'c' => array(
'o' => array(
'n' => array()
)
)
)
),
'x' => array(
'x' => array(
'x' => array()
)
)
);
/**
* #param string $word
* #param array $array
*/
function findWord($word, $array)
{
$tmpArray = $array;
for ($i = 0; $i < strlen($word); $i++)
{
$letter = substr($word, $i, 1);
if (isset($tmpArray[$letter])) {
if ($i == strlen($word) - 1) {
return true;
} else {
$tmpArray = $tmpArray[$letter];
}
} else {
break;
}
}
return false;
}
if (findWord('bacon', $trie)) {
die("Did find word.");
} else {
die("Didn't find word.");
}
This is a good challenge to solve this problem using iterators. Although I think that iterators are great, but they force you to think in terms of iterative approach. While for some problems it's ok, but for tasks like you described it makes more sense to use recursion.
So, I think that you should accept the answer of #cjohansson. As it is readable and understandable.
But just as a prove of concept here's my solution using RecursiveIteratorIterator. We have to extend this class and alter it a bit to suit our needs and also to reduce the number of unnecessary iterations:
class TrieRecursiveIteratorIterator extends RecursiveIteratorIterator
{
protected $word;
public function __construct(
$word,
Traversable $iterator,
$mode = RecursiveIteratorIterator::LEAVES_ONLY,
$flags = 0
) {
$this->word = str_split($word);
parent::__construct($iterator, $mode, $flags);
}
public function next()
{
if ($this->currentLetterMatched()) {
$this->updatePrefix();
$this->setPrefixed();
}
parent::next();
}
protected $prefix = [];
protected function updatePrefix()
{
$this->prefix[$this->getDepth()] = $this->key();
}
protected $prefixed = [];
protected function setPrefixed()
{
$this->prefixed = $this->current();
}
public function valid()
{
if (
$this->getDepth() < count($this->prefix)
|| count($this->word) === count($this->prefix)
) {
return false;
}
return parent::valid();
}
public function callHasChildren()
{
if ($this->currentLetterMatched()) {
return parent::callHasChildren();
}
return false;
}
protected function currentLetterMatched()
{
return isset($this->word[$this->getDepth()])
&& $this->key() === $this->word[$this->getDepth()];
}
public function testForMatches()
{
foreach ($this as $_) {
}
return $this;
}
public function getPrefix()
{
return implode('', $this->prefix);
}
public function getPrefixed()
{
return $this->prefixed;
}
public function matchFound()
{
return ($this->word === $this->prefix);
}
public function exactMatchFound()
{
return ($this->word === $this->prefix) && empty($this->prefixed);
}
public function prefixMatchFound()
{
return ($this->word === $this->prefix) && !empty($this->prefixed);
}
}
Then we can do following:
$iterator = new TrieRecursiveIteratorIterator(
$word,
new RecursiveArrayIterator($trie),
RecursiveIteratorIterator::SELF_FIRST
);
$iterator->testForMatches();
After that, we can ask our $iterator different things, such as:
If match was found: $iterator->matchFound();
If exact match was found: $iterator->exactMatchFound();
If there are words that prefixed with given word: $iterator->prefixMatchFound();
Get prefix itself (when either of matches were found the prefix will be equal to given word): $iterator->getPrefix();
Get endings prefixed with given word: $iterator->getPrefixed().
Here is working demo.
So as you can see this realization is not as straight forward as recursion one. And while I am a big fan of iterators and SPL usage, but this is not a silver bullet and you should chose tools that fits your current needs better.
Also, this is outside the domain, but my class violates Single responsibility principle. This was intentional for the sake of simplicity. In real life there would be the other class wich will use our iterator as dependency.

Maintain Element in PHP Array And Update in PHP Class

I have one PHP class as below (part of the code):
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
}
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
The calling code in index.php
$msg = 'Michael,18';
myclass::getHandle()->doProcess($msg);
In my webpage says index.php, it calls function doProcess() over and over again. When the function is called, string is passed and stored in an array. In the next call, if let's say same name is passed again, I want to update his age. My problem is I don't know how to check if the array $arrX contains the name. From my own finding, the array seems to be re-initiated (back to zero element) when the code is called. My code never does the update and always go to the array_push part. Hope somebody can give some thoughts on this. Thank you.
There is a ) missing in your else condition of your doProcess() function, it should read:
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
)); // <-- there was the missing )
}
Here is a complete running solution based on your code:
<?php
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
for ($i=0; $i<count(self::$arrX); $i++) {
if (is_array(self::$arrX[$i]) && self::$arrX[$i]['name'] == $det[0]) {
self::$arrX[$i]['age'] = $det[1];
break;
}
}
} else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
$mc = new myclass();
$mc->doProcess('Michael,18');
$mc->doProcess('John,23');
$mc->doProcess('Michael,19');
$mc->doProcess('John,25');
print_r($mc->returnProcess());
?>
You can test it here: PHP Runnable
As I said in comments, it looks like you want to maintain state between requests. You can't use pure PHP to do that, you should use an external storage solution instead. If it's available, try Redis, it has what you need and is quite simple to use. Or, if you're familiar with SQL, you could go with MySQL for example.
On a side note, you should read more about how PHP arrays work.
Instead of array_push, you could have just used self::$arrX[] = ...
Instead of that, you could have used an associative array, e.g. self::$arrX[$det[0]] = $det[1];, that would make lookup much easier (array_key_exists etc.)
Can you try updating the is_val_exists as follows:
private function is_val_exists($needle, $haystack) {
foreach($haystack as $element) {
if ($element['name'] == $needle) {
return true;
}
return false;
}

PHP: Modifying array with unknown structure at runtime; what is the most elegant solution?

PROBLEM
I have a function that takes in a nested array where the structure and nesting of the array is unknown at run-time. All that is known is some of the target fieldnames and desired values of some of the leafs.
QUESTIONS
1) I am hoping to modify this unknown structure and still have the code be readable and easily understood by fellow programmers. What (if any) solution will allow me to do things like this in PHP?
// Pseudo-code for things I would like to be able to do
// this is kinda like the same thing as XPATH, but for native PHP array
// find *every* fname with value of "Brad" and change it to "Brian"
$mydata->find_all('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
// find *the first* fave_color and set it to "Green"
$mydata->find('*:fave_color')->get(0)->set_equal_to('Green');
2) If there is nothing out there that will let me do this, is there something, anything, that at least comes close to the spirit of what I am hoping to accomplish here?
SAMPLE ARRAY
$mydata = array(
'people' => array(
array('fname'=>'Alice'),
array('fname'=>'Brad'),
array('fname'=>'Chris'),
),
'animals' => array(
array('fname'=>'Dino'),
array('fname'=>'Lassie'),
array('fname'=>'Brad'),
),
'settings' => array(
'user_prefs'=>array(
'localhost'=>array(
'fave_color'=>'blue',
),
),
),
'places' => array(
array('state'=>'New york',
'cities'=>array(
'name'=>'Albany',
'name'=>'Buffalo',
'name'=>'Corning',
),
'state'=>'California',
'cities'=>array(
'name'=>'Anaheim',
'name'=>'Bakersfield',
'name'=>'Carlsbad',
),
),
),
);
Although I maintain that you should stick with explicit manipulation as in my previous answer, boredom and intrigue got the better of me ;)
It probably has holes (and clearly lacks docs) but if you insist on this route, it should get you started:
class Finder {
protected $data;
public function __construct(&$data) {
if (!is_array($data)) {
throw new InvalidArgumentException;
}
$this->data = &$data;
}
public function all() {
return $this->find();
}
public function find($expression = null) {
if (!isset($expression)) {
return new Results($this->data);
}
$results = array();
$this->_find(explode(':', $expression), $this->data, $results);
return new Results($results);
}
protected function _find($parts, &$data, &$results) {
if (!$parts) {
return;
}
$currentParts = $parts;
$search = array_shift($currentParts);
if ($wildcard = $search == '*') {
$search = array_shift($currentParts);
}
foreach ($data as $key => &$value) {
if ($key === $search) {
if ($currentParts) {
$this->_find($currentParts, $value, $results);
} else {
$results[] = &$value;
}
} else if ($wildcard && is_array($value)) {
$this->_find($parts, $value, $results);
}
}
}
}
class Results {
protected $data;
public function __construct(&$data) {
$this->data = $data;
}
public function get($index, $limit = 1) {
$this->data = array_slice($this->data, $index, $limit);
return $this;
}
public function set_equal_to($value) {
foreach ($this->data as &$datum) {
$datum = $value;
}
}
public function __call($method, $args) {
if (!preg_match('/^where_?(key|value)_?(eq|contains)$/i', $method, $m)) {
throw new BadFunctionCallException;
}
if (!isset($args[0])) {
throw new InvalidArgumentException;
}
$operand = $args[0];
$isKey = strtolower($m[1]) == 'key';
$method = array('Results', '_compare' . (strtolower($m[2]) == 'eq' ? 'EqualTo' : 'Contains'));
$ret = array();
foreach ($this->data as $key => &$datum) {
if (call_user_func($method, $isKey ? $key : $datum, $operand)) {
$ret[] = &$datum;
}
}
$this->data = $ret;
return $this;
}
protected function _compareEqualTo($value, $test) {
return $value == $test;
}
protected function _compareContains($value, $test) {
return strpos($value, $test) !== false;
}
}
$finder = new Finder($mydata);
$finder->find('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
$finder->find('*:fave_color')->get(0)->set_equal_to('Green');
$finder->find('places:*:cities:*:name')->where_value_contains('ba')->set_equal_to('Stackoton');
print_r($mydata);
There's certainly no native solution for this and the syntax is rather strange. If you want the code to "be readable and easily understood by fellow programmers" please stick to methods that we're used to working with ;)
foreach ($mydata as $type => &$data) {
foreach ($data as &$member) {
if (isset($member['fname']) && $member['fname'] == 'Brad') {
$member['fname'] = 'Brian';
}
}
}
It's admittedly more verbose, but there's much less chance of confusion.

jQuery style Constructors in PHP

Is there a way to instantiate a new PHP object in a similar manner to those in jQuery? I'm talking about assigning a variable number of arguments when creating the object. For example, I know I could do something like:
...
//in my Class
__contruct($name, $height, $eye_colour, $car, $password) {
...
}
$p1 = new person("bob", "5'9", "Blue", "toyota", "password");
But I'd like to set only some of them maybe. So something like:
$p1 = new person({
name: "bob",
eyes: "blue"});
Which is more along the lines of how it is done in jQuery and other frameworks. Is this built in to PHP? Is there a way to do it? Or a reason I should avoid it?
the best method to do this is using an array:
class Sample
{
private $first = "default";
private $second = "default";
private $third = "default";
function __construct($params = array())
{
foreach($params as $key => $value)
{
if(isset($this->$key))
{
$this->$key = $value; //Update
}
}
}
}
And then construct with an array
$data = array(
'first' => "hello"
//Etc
);
$Object = new Sample($data);
class foo {
function __construct($args) {
foreach($args as $k => $v) $this->$k = $v;
echo $this->name;
}
}
new foo(array(
'name' => 'John'
));
The closest I could think of.
If you want to be more fancy and just want to allow certain keys, you can use __set() (only on php 5)
var $allowedKeys = array('name', 'age', 'hobby');
public function __set($k, $v) {
if(in_array($k, $this->allowedKeys)) {
$this->$k = $v;
}
}
get args won't work as PHP will see only one argument being passed.
public __contruct($options) {
$options = json_decode( $options );
....
// list of properties with ternary operator to set default values if not in $options
....
}
have a looksee at json_decode()
The closest I can think of is to use array() and extract().
...
//in your Class
__contruct($options = array()) {
// default values
$password = 'password';
$name = 'Untitled 1';
$eyes = '#353433';
// extract the options
extract ($options);
// stuff
...
}
And when creating it.
$p1 = new person(array(
'name' => "bob",
'eyes' => "blue"
));

Declarative access to structured PHP variable without foreach loops

Background
Assume I have the following nested variable in PHP.
$data = Array(
Array('lname' => 'Simpson','fname' => 'Homer','age' => '35','motto' => '_blank_'),
Array('lname' => 'Simpson','fname' => 'Marge','age' => '34','motto' => '_blank_'),
Array('lname' => 'Flintstone','fname' => 'Fred','age' => '33','motto' => '_blank_'),
Array('lname' => 'Flintstone','fname' => 'Wilma','age' => '29','motto' => '_blank_')
);
Assume also the standard methods for accessing specific values:
print($data[0]['fname']); // Homer
print($data[1]['age']); // 34
Question
Is there an existing library or framework that would allow me to easily
acess specific values declaratively, without using foreach loops?
$test = $data->get_record_by_fname['Homer']
print $test['age'] //35
If you really wanted to overkill everything, you could try an approach using magical methods!
class Simpsons
{
protected $_data = array();
public function __construct(array $data)
{
$this->_data = array_map(function ($i) { return (object)$i; }, $data);
}
public function __call($method, $args)
{
if (count($args) == 0)
return NULL;
foreach ($this->_data as $row)
{
if (property_exists($row, $method) && $row->$method == $args[0])
{
return $row;
}
}
return NULL;
}
}
Usage:
$p = new Simpsons($data); // Stored in the format provided
var_dump($p->fname('Homer')); // Gets the record with fname = Homer
Is there a particular reason you don't want to use foreach loops? If it's merely for conciseness, you could just declare the function yourself, it's fairly trivial:
function get_record($set, $field, $value) {
foreach($set as $key => $val) {
if($val[$field] === $value) return $set[$key];
}
return NULL;
}
Then your example would become:
$test = get_record($data, 'fname', 'Homer');
print $test['age']; //35
class SomeClass{
// Stores the Array of Data
public $data;
// Sets up the object. Only accepts arrays
public function __construct(array $data)
{
$this->data = $data;
}
// Gets a record based on the key/value pair
public function getByKey($key, $value)
{
foreach($this->data as $array)
{
if(is_array($array)
{
if(array_key_exists($key, $array) && $array[$key] == $value)
{
return $array;
}
}
}
}
}
$array = array( 1 => array("Test" => "Hello"));
$obj = new SomeClass($array);
$record = $obj->getByKey('Test', 'Hello');
This lets you get a record based on what a key/value pair inside the array is. Note, the type hinting in the constructor is PHP 5.3(?)
BTW, No, there is no way to escape the foreach as even internal PHP functions (anything beginning with array_) uses a foreach or some other type of loop. However, if you encapsulate the loop into a class, you don't have to think about it.

Categories