What does JSON_ERROR_RECURSION mean from json_encode? - php

I get this error on PHP7 every about 100 requests for some odd reason and I cannot get rid of it until I restart the fpm demon, however, the real problem is I cannot explain what the error is to start diagnosing this.
I took a look at the documentation http://php.net/manual/en/function.json-last-error.php which was not very useful and there does not seem to be any real links hanging about.
I know this error is not actually related to recursion depth (JSON_ERROR_DEPTH) so what does this error actually mean?
This is the var_dump() of the array that is failing:
array (
'ns' => 'user',
'where' => '{"_id":"MongoDB\\\\BSON\\\\ObjectID(5505a4f647ac1824618b4567)","status":10}',
'projection' =>
array (
),
'sort' =>
array (
),
'limit' => NULL,
'skip' => NULL,
)

JSON_ERROR_RECURSION indicates that the data passed to json_encode() contains one or more recursive references.
$data = array();
$data['foo'] = &$data; // <-- recursive reference here
var_dump(json_encode($data)); // bool(false)
var_dump(json_last_error_msg()); // string(18) "Recursion detected"
var_dump(json_last_error() === JSON_ERROR_RECURSION); // bool(true)
Code like this will work though:
$data['foo'] = 'hello';
$data['bar'] = &$data['foo'];
But not like this (another recursive reference):
$data['foo'] = [1, 2, 3];
$data['foo'][] = &$data['foo'];
Recursive reference means that the reference points to a variable that in turn contains the same reference again.

Most likely need to update mongodb driver (or maybe another extension). Extension with bug may write to shared memory (immutable arrays) - it is not allowed.
It is can be detected with opcache.protect_memory (http://php.net/manual/en/opcache.configuration.php#ini.opcache.protect-memory)
http://news.php.net/php.internals/98210

Related

Can i use square brackets for declaring array variables in php [duplicate]

In CoffeeScript, Clojure, ES6 and many other languages we have destructuring of objects/maps/etc somewhat like this:
obj = {keyA: 'Hello from A', keyB: 'Hello from B'}
{keyA, keyB} = obj
I've found the list function in php which lets you destructure arrays like so:
$info = array('coffee', 'brown', 'caffeine');
list($drink, $color, $power) = $info;
Is there a way to destructure objects or associative arrays in PHP? If not in the core libs maybe someone wrote some smart helper function?
For PHP 7.0 and below that is beyond the functionality of list. The docs state:
list only works on numerical arrays and assumes the numerical indices start at 0.
One of the things that could suit your purpose would be the extract() function which imports variables from an array into the current symbol table. While with list you are able to define variable names explicitly, extract() does not give you this freedom.
Extracting an associative array
With extract you could do something like that:
<?php
$info = [ 'drink' => 'coffee', 'color' => 'brown', 'power' => 'caffeine' ];
extract($info);
var_dump($drink); // string(6) "coffee"
var_dump($color); // string(5) "brown"
var_dump($power); // string(8) "caffeine"
Extracting an Object
Extracting an object works almost the same. Since extract only takes an array as an argument we need to get the objects properties as an array. get_object_vars does that for you. It returns an associative array with all public properties as key and their values as value.
<?php
class User {
public $name = 'Thomas';
}
$user = new User();
extract( get_object_vars($user) );
var_dump($name); // string(6) "Thomas"
Pitfalls
extract() is not the same as list since it does not allow you to explicitly define the variable names that get exported to the symbol table. The variable names correspond the array keys by default.
list is a language construct while extract() is a function
It might happen that you overwrite variables that you have defined beforehand unintentionally
Your array keys might be invalid as variable names
With the $flags parameter that you can pass as second argument to extract() you can influence the behavior in case of colliding or invalid variables. But still it's important to know how extract() works and to use it with cauton.
Edit: As of PHP 7.1 this is possible:
http://php.net/manual/en/migration71.new-features.php#migration71.new-features.support-for-keys-in-list
You can now specify keys in list(), or its new shorthand [] syntax. This enables destructuring of arrays with non-integer or non-sequential keys.
https://php.net/manual/en/migration71.new-features.php#migration71.new-features.symmetric-array-destructuring
The shorthand array syntax ([]) may now be used to destructure arrays for assignments (including within foreach), as an alternative to the existing list() syntax, which is still supported.
For example this:
$test_arr = ['a' => 1, 'b' => 2];
list('a' => $a, 'b' => $b) = $test_arr;
var_dump($a);
var_dump($b);
Will output the following as of 7.1.0
int(1)
int(2)
I noticed the accepted answer missed out examples that use the short-hand notation, security issues with using extract, and IDE issues.
Numerical Array Destructuring (PHP 7.1)
As of PHP 7.1 numerical array destructuring (Symetric array destructuring) is supported like so:
<?php
$data = [55, 'John', 'UK'];
[$id, $name] = $data; // short-hand (recommended)
list($id, $name) = $data; // long-hand
Notice that you can miss items out if you don't want them.
Associative Array Destructuring (PHP 7.1)
You can also destructure associative arrays (Support for keys in list) like so:
<?php
$data = ['id' => 55, 'firstName' => 'John', 'country' => 'UK']
['id' => $id, 'firstName' => $name] = $data; // short-hand (recommended)
list('id' => $id, 'firstName' => $name) = $data; // long-hand
Notice that you can miss items out if you don't want them. Also the variable name can be different to the property name.
Object Destructuring (PHP 7.1)
Unfortunately there is no object destructuring. However you can convert an object to an associative array using get_object_vars, and then use associative array destructuring.
<?php
class User {
public $id;
public $name;
public $country;
}
$user = new User();
$user->id = 55;
$user->name = 'John';
$user->country = 'UK';
['id' => $id, 'firstName' => $name] = get_object_vars($user)
However, this can break some IDE features. These are some issues I noticed when using PHPStorm 2019.1:
IDE's may no longer understand the type for the variables, so you would need to add some #var Type PHPDocs to maintain auto-complete functionality
Does not work well with refactoring tools. For example, if you rename one of the properties, the array destructuring portion will not also automatically rename.
So I recommend just doing it the normal way:
$id = $user->id
$name = $user->firstName
Do NOT use extract
With extract, all variables are always set. There it is a really bad idea to use it because:
It can lead to security issues. Even if your careful, it can lead to non-obvious security holes in the future. If you do use it, don't use it with user input (e.g. $_GET, $_POST), unless you want to make a malicious hacker's day.
Can lead to hard to detect bugs
If the class or array changes in the future, by introducing new properties, it can break your code if it coincides with an already used variable, unless you use the EXTR_SKIP flag or similar
Variable variables are one way to achieve this:
$args = ['a' => 1, 'b' => 2, 'c' => 3];
foreach (['a', 'c'] as $v) $$v = $args[$v];
// $a is 1, $b is undefined, $c is 3
It's really not pretty, and thankfully this has been addressed in 7.1 by https://wiki.php.net/rfc/short_list_syntax . This would let you say ['a' => $a, 'c' => $c] = $args; in the above example.
Since 7.1 includes a way to use a different name for your var than the assoc array key. This is pretty straight-forward using variable variables here too:
foreach (['a' => 'eh', 'b' => 'bee'] as $k => $v) $$v = $args[$k];
// $eh is 1, $bee is 2
Some developers, and some coding styles, define $$var as an anti-pattern similar to using eval, extract, and the GPR magic variables directly. This is because using variable variables makes code harder to understand, which leads directly to bugs and prevents static code analysis tools from functioning.
If you do adopt $$var, it can be helpful to use the ${$var} form instead, which makes it obvious that the author didn't simply type one too many $'s, and may spare the author immediate negative feedback when their code is audited.
One simple solution is to read the Object as an Array. So assuming you use #Yahya Uddin's User object, you can do like so:
['id' => $id, 'firstName' => $name] = (array)$user
// $id = 55, name = 'john'
This will tell PHP to read this objective as an associative Array.

Accessing nested property on stdClass Object with string representation of the node

Given a variable that holds this string:
$property = 'parent->requestdata->inputs->firstname';
And an object:
$obj->parent->requestdata->inputs->firstname = 'Travis';
How do I access the value 'Travis' using the string? I tried this:
$obj->{$property}
But it looks for a property called 'parent->requestdata->inputs->firstname' not the property located at $obj->parent->requestdtaa->inputs->firstname`
I've tried various types of concatenation, use of var_export(), and others. I can explode it into an array and then loop the array like in this question.
But the variable '$property' can hold a value that goes 16 levels deep. And, the data I'm parsing can have hundreds of properties I need to import, so looping through and returning the value at each iteration until I get to level 16 X 100 items seems really inefficient; especially given that I know the actual location of the property at the start.
How do I get the value 'Travis' given (stdClass)$obj and (string)$property?
My initial searches didn't yield many results, however, after thinking up a broader range of search terms I found other questions on SO that addressed similar problems. I've come up with three solutions. All will work, but not all will work for everyone.
Solution 1 - Looping
Using an approach similar to the question referenced in my original question or the loop proposed by #miken32 will work.
Solution 2 - anonymous function
The string can be exploded into an array. The array can then be parsed using array_reduce() to produce the result. In my case, the working code (with a check for incorrect/non-existent property names/spellings) was this (PHP 7+):
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
$property = 'parent->requestdata->inputs->firstname';
$name = array_reduce(explode('->', $property), function ($previous, $current) {
return is_numeric($current) ? ($previous[$current] ?? null) : ($previous->$current ?? null); }, $obj);
var_dump($name); //outputs Travis
see this question for potentially relevant information and the code I based my answer on.
Solution 3 - symfony property access component
In my case, it was easy to use composer to require this component. It allows access to properties on arrays and objects using simple strings. You can read about how to use it on the symfony website. The main benefit for me over the other options was the included error checking.
My code ended up looking like this:
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
//don't forget to include the component at the top of your class
//'use Symfony\Component\PropertyAccess\PropertyAccess;'
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
//NOTE: syfony uses dot notation. I could not get standard '->' object notation to work.
$property = 'parent.requestdata.inputs.firstname';
//create symfony property access factory
$propertyAccessor = PropertyAccess::createPropertyAccessor();
//get the desired value
$name = $propertyAccessor->getValue($obj, $property);
var_dump($name); //outputs 'Travis'
All three options will work. Choose the one that works for you.
You're right that you'll have to do a loop iteration for each nested object, but you don't need to loop through "hundreds of properties" for each of them, you just access the one you're looking for:
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
$property = "parent->requestdata->inputs->firstname";
$props = explode("->", $property);
while ($props && $obj !== null) {
$prop = array_shift($props);
$obj = $obj->$prop ?? null;
}
var_dump($obj);
Totally untested but seems like it should work and be fairly performant.

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;
}

PHP: Strange effect when passing array into MongoCollection#insert()

In PHP arrays are assigned by value. Hence, when I do something like this:
$a = ['a' => 'ape'];
$b = $a;
$a['b'] = 'banana';
Only $a will have the key 'b', but $b won't be modified. That also applies when either array is assigned by reference into a function and that function modifies the array.
However, there's a strange thing happening if I try to insert a document using the PECL MongoDB extension. In the following unit test the second assertion fails for me:
public function testUpdateGeneratesId()
{
$doc1 = ['author' => 'j.henning', 'title' => 'My Blog Entry'];
$doc2 = $doc1;
$this->blog->insert($doc1, ['w' => 1]);
$this->assertNotNull($doc1['_id']);
$this->assertArrayNotHasKey('_id', $doc2);
}
The #insert() generates the key '_id' on the inserted document, but I would expect the key only to be added to $doc1. However, it's created on both arrays.
Can anybody reproduce this behavior or give any explanation for it?
This sounds like a bug related to https://jira.mongodb.org/browse/PHP-410 - can you please file a new bug at https://jira.mongodb.org/browse/PHP for this?

Is there a way to set and get values from a multidimensional array through Kohana's built in session handler?

In PHP I often do the following:
$_SESSION['var']['foo'] = array('bar1' => 1, 'bar2' => 2);
// ...
$_SESSION['var']['foo']['bar2'] = 3;
// ...
echo $_SESSION['var']['foo']['bar2']; // 3
I'm wondering what the recommended way of storing multidimensional arrays in a session with Kohana.
I know I can do the following, but I don't know how to make it work with multidimensional, specifically the get portion:
Session::instance()->set('var', array(
'foo' => array(
'bar1' => 1,
'bar2' => 2,
),
));
// ...
// how do I set just bar2?
// ...
// this gets the whole array, but how do I get just bar2?
Session::instance()->get('var');
So, the questions are:
How do I set just bar2?
How do I get just bar2?
Is there a way to do either of these in Kohana 3?
I'd love to use the native sessions, but we are trying to use database sessions.
The short answer is that there is no way to do that, given the current implementation of Kohana sessions. You have two alternatives:
Either get and set the entire array, editing the bits you need each time:
$array = Session::instance()->get('var');
$array['foo']['bar2'] = 'baz';
Session::instance()->set('var', $array);
Or you override the Kohana_Session->get() and ->set() methods (which are defined here on github).
Keep in mind that, given the wonderful "layered" filesystem in Kohana, you can actually extend the class, modifying just the method you need, without editing the core Kohana code.
My idea would be to change the $key parameter to accept either strings or arrays. If you pass in an array, it should interpret each element in the array as a "deeper" level.
$key = array('var', 'foo', 'bar2');
Session::instance()->get($key, $default);
Session::instance()->set($key, 'baz');
$session = & Session::instance()->as_array();
$session['foo']['bar2'] = 'baz';
UPD. Also, you can use Arr::path():
$bar2 = arr::path(Session::instance()->as_array(), 'foo.bar2');// returns 'baz'
$bars = arr::path(Session::instance()->as_array(), '*.bar2'); // returns array of bar2's

Categories