Related
I have this example array.
$data = new stdClass();
$data->foo = [
'foo1' => &$data,
'foo2' => 23,
];
$data->bar = new stdClass();
$data->nar->object = [
'bar1' => &$data->bar,
'bar2' => 43,
];
I want to parse this to:
$data = new stdClass();
$data->foo = [
'foo1' => "RECURSION DETECTED",
'foo2' => 23,
];
$data->bar = new stdClass();
$data->nar->object = [
'bar1' => "RECURSION DETECTED",
'bar2' => 43,
];
I need it, because json_encode can't encode data when recursion is detected.
I tried so many times and in different ways, I did a lot of research, but I did not find anything to really help me.
My last attempt was:
function _stack(&$object, &$stack = [], $key = 'original')
{
if (isObjectOrArray($object)) {
if (!in_array($object, $stack, true)) {
if (is_object($object)) {
$stack[$key] = &$object;
}
foreach ($object as $key => &$value) {
_stack($value, $stack, $key);
}
}
}
return $stack;
}
function _remove($object, $stack, $objectO = false, $key = 'original')
{
/**
* #var $objectO false | object
*/
if (!$objectO) {
$objectO = $object;
}
if (isObjectOrArray($object)) {
foreach ($object as $prop => $value) {
if (is_object($objectO)) {
if (in_array($object->{$prop}, $stack, true) && $prop !== $key) {
$objectO->{$prop} = "RECURSION DETECTED";
} else {
$objectO->{$prop} = _remove($object->{$prop}, $stack, $objectO->{$prop}, $prop);
}
} else {
if (in_array($object[$prop], $stack, true) && $prop !== $key) {
$objectO[$prop] = "RECURSION DETECTED";
} else {
$objectO[$prop] = _remove($object[$prop], $stack, $objectO[$prop], $prop);
}
}
}
}
return $objectO;
}
First i crate an stack with original objects (not reference / pointer).
The key is passed to the function, within itself in recursion, so I know exactly where recursion meets the original object. I need it so I can then tell what the pointer is and what the original object is.
After create stack i run the same looping, but the current value inside foreach statement is an object and he is inside stack and the current key is diferent of current key pass to the function call, the reference / pointer is breaked.
Array
(
[foo1] => RECURSION DETECTED
[foo2] => 23
)
But at the end of all function calls I get only:
RECURSION DETECTED
I am still looking at another way since this is interesting, but it is easy to replace the reference pointer in a serialized string and then unserialize it:
$data = unserialize(preg_replace('/R:\d+/', 's:18:"RECURSION DETECTED"', serialize($data)));
Another option for PHP >= 7.3.0 is exporting and forcing it to break the references. var_export will complain about recursion, however it will happily display it with the references replaced with NULL. var_export has a second argument to return the output instead of displaying, but this doesn't work with recursion so I buffered and captured the output.
ob_start();
#var_export($data);
$var = ob_get_clean();
eval("\$data = $var;");
For PHP < 7.3.0 you can use the above code with your own class that implements __set_state instead of stdClass:
class myClass {
public static function __set_state($array) {
$o = new self;
foreach($array as $key => $val) {
$o->$key = $val;
}
return $o;
}
}
$data = new myClass();
I created a cache from xml, and by a construct I generate the object which finally become the arrays. And everything would be ok, if the key of these arrays wasnt "0". I dont know how it works. I searched the information how to change the class, or how to replace the keys. I am stuck. Could you help me with this.
$xml = simplexml_load_file($cache);
}
class Property {
public $xmlClass;
public $elemClass = '';
public $result_array = [];
public $data = '';
public function __construct($xml,$elem) {
$this->xmlClass=$xml;
$this->elemClass=$elem;
foreach($xml->list->movie as $value) {
$data = $value->$elem;
$this->result_array[] = $data;
}
}
public function getResult() {
return $this->result_array;
}
}
$result_zn = new Property($xml,'zn');
$result_au = new Property($xml,'au');
$result_ti = new Property($xml, 'ti');
$zn = $result_zn->getResult();
$au = $result_au->getResult();
$ti = $result_ti->getResult();
I think you can use the function array_values() to get the key 0,like this:
$arr = array(
'1' => 'cat',
'2' => 'dog'
);
$newarr = array_values($arr);
print_r($newarr);
and the result is :
Array ( [0] => cat [1] => dog )
Based on this documentation , how to pass second parameter to the rule method?
This is my custom rule
public function email_exists($email, $exclude_id=NULL)
{
if ( $exclude_id !== NULL ) $this->db->where_not_in('id', $exclude_id);
$result = $this->db->select('id')->from('users')->where('email', $email)->get();
if ( $result->num_rows() > 0 ) {
$this->form_validation->set_message('email_exists', '{field} has been used by other user.');
return FALSE;
} else {
return TRUE;
}
}
and this is how i call it from controller
$rules = [
[
'field' => 'email',
'label' => 'Email',
'rules' => [
'required',
'trim',
'valid_email',
'xss_clean',
['email_exists', [$this->m_user, 'email_exists']]
]
]
];
$this->form_validation->set_rules($rules);
How can I pass second parameter to email_exists method?
Its seems CI does not provide a mechanism for this. I found several approaches to solve this. First way, you can hack the file system (Form_validation.php) and modify some script at line 728
if ( preg_match('/(.*?)\[(.*)\]/', $rule[1], $rulea) ) {
$method = $rulea[1];
$extra = $rulea[2];
} else {
$method = $rule[1];
$extra = NULL;
}
$result = is_array($rule)
? $rule[0]->{$method}($postdata, $extra)
: $rule($postdata);
Second way you can extends CI_Form_validation core and add your custom rule in it. I found the detail about this on codeigniter documentation.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Form_validation extends CI_Form_validation
{
public function __construct()
{
parent::__construct();
}
public function check_conflict_email($str, $exclude_id=NULL)
{
if ( $exclude_id !== NULL ) $this->CI->db->where_not_in('id', $exclude_id);
$result = $this->CI->db->select('id')->from('users')->where('email', $str)->get();
if ( $result->num_rows() > 0 ) {
$this->set_message('check_conflict_email', '{field} has been used by other user.');
return FALSE;
} else {
return TRUE;
}
}
}
/* End of file MY_Form_validation.php */
/* Location: ./application/libraries/MY_Form_validation.php */
Third way, and I think this is the best way to do it. Thanks to skunkbad for provide the solution
$rules = [
[
'field' => 'email',
'label' => 'Email',
'rules' => [
'required',
'trim',
'valid_email',
'xss_clean',
[
'email_exists',
function( $str ) use ( $second_param ){
return $this->m_user->email_exists( $str, $second_param );
}
]
]
]
];
Just do it the right way (at least for CI 2.1+) as described in the docs:
$this->form_validation->set_rules('uri', 'URI', 'callback_check_uri['.$this->input->post('id').']');
// Later:
function check_uri($field, $id){
// your callback code here
}
If this is not working than make an hidden field in your form for $exclude_id and check that directly in your callback via
$exclude_id = $this->input->post('exclude_id');//or whatever the field name is
More here
I use CI 3.1.10 and this issue still exists, I extend the library and use the same way as callback
array('username_callable[param]' => array($this->some_model, 'some_method'))
Extended Form_validation library:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Form_validation extends CI_Form_validation {
/**
* Executes the Validation routines
*
* #param array
* #param array
* #param mixed
* #param int
* #return mixed
*/
protected function _execute($row, $rules, $postdata = NULL, $cycles = 0)
{
// If the $_POST data is an array we will run a recursive call
//
// Note: We MUST check if the array is empty or not!
// Otherwise empty arrays will always pass validation.
if (is_array($postdata) && ! empty($postdata))
{
foreach ($postdata as $key => $val)
{
$this->_execute($row, $rules, $val, $key);
}
return;
}
$rules = $this->_prepare_rules($rules);
foreach ($rules as $rule)
{
$_in_array = FALSE;
// We set the $postdata variable with the current data in our master array so that
// each cycle of the loop is dealing with the processed data from the last cycle
if ($row['is_array'] === TRUE && is_array($this->_field_data[$row['field']]['postdata']))
{
// We shouldn't need this safety, but just in case there isn't an array index
// associated with this cycle we'll bail out
if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles]))
{
continue;
}
$postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
$_in_array = TRUE;
}
else
{
// If we get an array field, but it's not expected - then it is most likely
// somebody messing with the form on the client side, so we'll just consider
// it an empty field
$postdata = is_array($this->_field_data[$row['field']]['postdata'])
? NULL
: $this->_field_data[$row['field']]['postdata'];
}
// Is the rule a callback?
$callback = $callable = FALSE;
if (is_string($rule))
{
if (strpos($rule, 'callback_') === 0)
{
$rule = substr($rule, 9);
$callback = TRUE;
}
}
elseif (is_callable($rule))
{
$callable = TRUE;
}
elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1]))
{
// We have a "named" callable, so save the name
$callable = $rule[0];
$rule = $rule[1];
}
// Strip the parameter (if exists) from the rule
// Rules can contain a parameter: max_length[5]
$param = FALSE;
if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match))
{
$rule = $match[1];
$param = $match[2];
}
elseif ( is_string($callable) && preg_match('/(.*?)\[(.*)\]/', $callable, $match))
{
$param = $match[2];
}
// Ignore empty, non-required inputs with a few exceptions ...
if (
($postdata === NULL OR $postdata === '')
&& $callback === FALSE
&& $callable === FALSE
&& ! in_array($rule, array('required', 'isset', 'matches'), TRUE)
)
{
continue;
}
// Call the function that corresponds to the rule
if ($callback OR $callable !== FALSE)
{
if ($callback)
{
if ( ! method_exists($this->CI, $rule))
{
log_message('debug', 'Unable to find callback validation rule: '.$rule);
$result = FALSE;
}
else
{
// Run the function and grab the result
$result = $this->CI->$rule($postdata, $param);
}
}
else
{
$result = is_array($rule)
? $rule[0]->{$rule[1]}($postdata, $param)
: $rule($postdata);
// Is $callable set to a rule name?
if ($callable !== FALSE)
{
$rule = $callable;
}
}
// Re-assign the result to the master data array
if ($_in_array === TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
}
}
elseif ( ! method_exists($this, $rule))
{
// If our own wrapper function doesn't exist we see if a native PHP function does.
// Users can use any native PHP function call that has one param.
if (function_exists($rule))
{
// Native PHP functions issue warnings if you pass them more parameters than they use
$result = ($param !== FALSE) ? $rule($postdata, $param) : $rule($postdata);
if ($_in_array === TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
}
}
else
{
log_message('debug', 'Unable to find validation rule: '.$rule);
$result = FALSE;
}
}
else
{
$result = $this->$rule($postdata, $param);
if ($_in_array === TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
}
}
// Did the rule test negatively? If so, grab the error.
if ($result === FALSE)
{
// Callable rules might not have named error messages
if ( ! is_string($rule))
{
$line = $this->CI->lang->line('form_validation_error_message_not_set').'(Anonymous function)';
}
else
{
$line = $this->_get_error_message($rule, $row['field']);
}
// Is the parameter we are inserting into the error message the name
// of another field? If so we need to grab its "field label"
if (isset($this->_field_data[$param], $this->_field_data[$param]['label']))
{
$param = $this->_translate_fieldname($this->_field_data[$param]['label']);
}
// Build the error message
$message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']), $param);
// Save the error message
$this->_field_data[$row['field']]['error'] = $message;
if ( ! isset($this->_error_array[$row['field']]))
{
$this->_error_array[$row['field']] = $message;
}
return;
}
}
}
}
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)
}
}
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"
));