View JSON differences when HTTP testing in Laravel - php

I am running some HTTP tests in Laravel 5.4, mainly using the assertJson helper method with phpunit. When I run other tests on my models using assertEquals I usually get very good feedback about specifically which properties, fields, etc. are different than expected. However, the assertJson method only tells me that there are differences, but not what those differences are. For instance, let's say I have a route my/route that returns this JSON:
{
"name": "test",
"foo": "bar"
}
I might run this Laravel test:
$response = $this->get("my/route");
$response->assertJson([
'name' => 'test',
'foo' => 'baz',
]);
My test fails as expected. However, the resulting message is pretty unhelpful:
Failed asserting that an array has the subset Array &0 (
'name' => 'test'
'foo' => 'baz'
).
For a non-trivial example with larger response, it can get pretty annoying to try to figure out what is different between the JSON responses. Is there any way to view the specific differences between the expected and actual outputs, instead of just knowing that something is different between the two?

You could wrap your assertion in a try-catch and then if the assertion fails you'll be able to create a new message and throw a new exception.
/**
* #test
* #group new
*/
public function testExample()
{
$response = $this->get('test');
try {
$expected = [
'name' => 'test',
'foo' => 'barz',
];
$response->assertJson($expected);
} catch (\Exception $e) {
$exporter = new \SebastianBergmann\Exporter\Exporter();
$message = 'The following items we\'re expected to be different ' .
$exporter->export($this->arrayRecursiveDiff($expected, $response->decodeResponseJson()));
throw new \PHPUnit_Framework_ExpectationFailedException($message);
}
}
public function arrayRecursiveDiff($array1, $array2)
{
$return = [];
foreach ($array1 as $key => $value) {
if (is_array($array2) && array_key_exists($key, $array2)) {
if (is_array($value)) {
$recursiveDiff = $this->arrayRecursiveDiff($value, $array2[$key]);
if (count($recursiveDiff)) {
$return[$key] = $recursiveDiff;
}
} else {
if ($value != $array2[$key]) {
$return[$key] = $value;
}
}
} else {
$return[$key] = $value;
}
}
return $return;
}
Please note the arrayRecursiveDiff method isn't fully tested, however, there are quite a few different examples floating around for how to compare multidimensional arrays.
Hope this helps!

Related

How to remove recursion inside an object or array?

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();

What's the use of a callback function in PHP?

I'm in the process of learning PHP, and the concept of callback functions have me slightly confused. I understand that there are synchronous and asynchronous callbacks and that the common callback in PHP is synchronous. I have read through a lot of information regarding this topic, but I'm none the wiser still.
How is this:
function string($string, $callback) {
$results = array(
'upper' => strtoupper($string),
'lower' => strtolower($string)
);
if(is_callable($callback)) {
call_user_func($callback, $results);
}
}
string('Danny', function($name) {
echo $name['upper'];
}
);
Different or better than this:
function string($string, $case) {
$options = [
'upper' => strtoupper($string),
'lower' => strtolower($string)
];
echo $options[$case];
}
string('Danny', 'upper');
Here's a typical example where the callback is better than the direct value:
function intricateProcessToCalculateDefault() {
//Takes 3 seconds
}
function valueOrDefault($value, $default = null) {
if ($value === null) {
if (is_callable($default)) {
return $default();
}
return $default;
}
return $value;
}
Now consider these two examples:
$valueToUse = valueOrDefault(
$_GET["parameterWithFallback"] ?? null,
intricateProcessToCalculateDefault()
);
// or
$valueToUse = valueOrDefault(
$_GET["parameterWithFallback"] ?? null,
function () { return intricateProcessToCalculateDefault(); }
);
Now here's the thing. If you don't pass parameterWithCallback this will take 3 seconds either way. However if you do then intricateProcessToCalculateDefault will never be called if passed as a callback and will therefore save you time.
In your case, since the function code is ran in either case, there's no benefit.
PHP is mostly synchronous so there's no easy way to get asynchronous functions. Some libraries do offer them but that's usually tied with calling a shell executable or something similar.
Another use case is for inversion for control. There are many generic algorithms which can be implemented with a "placeholder" for user code for. array_map is such an example.
$array = [ "string1", "sting2" ];
$action = "upper";
array_map(function ($value) use ($action) {
return ucfirst(strtolower($value));
}, $array);
In this case control is inverted. A PHP function is calling your function instead of the other way around.
In this example with a callback you could easily pass a different function without changing the original code:
<?php
function string($string, $callback) {
$results = array(
'upper' => strtoupper($string),
'lower' => strtolower($string)
);
if(is_callable($callback)) {
call_user_func($callback, $results);
}
}
string('Danny', function($name) {
echo $name['upper'];
}
);
string('Danny', function($name) {
echo 'Mr.'.$name['upper'];
}
);
string('Danny', function($name) {
echo 'Mrs.'.$name['upper'];
}
);

How to avoid crash when key doesnt exist in array?

Im having a lot of difficulty with arrays in PHP. They require me to write a lot of codes such as isset(), empty(), array_key_exist(); And I really dont want to deal with these. If the key doesnt exist just handle it as a null.
$arr = [
'location' => 'Paris'
]
$arr['country'] // boom crash. How to walkaround this?
Any suggestions?
EDIT
I dont want to use any if condition. No isset(), array_key_exist, exceptions, etc. I just want them to be null if the key doesn't exist? Is this possible in PHP? The application is very abstract and data may vary on each request.
function getValue(array $array, $key) {
return isset($array[$key]) ? $array[$key] : null;
}
echo getValue($mysteryArray, 'mysteryKey');
Or:
$array += array_fill_keys(array('foo', 'bar', 'baz'), null);
echo $array['foo'];
My own function inspired from deceze. Works perfectly.
/**
* Fill array with null on nonexistent keys
*
* #param array $arg
* #param array $possible_keys
*/
function fillNull(array $arg, array $possible_keys){
foreach($possible_keys as $key){
$result[$key] = empty( $arg[$key] ) ? null : $arg[$key];
}
return $result;
}
You can use ArrayIterator or some class that gives you the interface you desire.
<?php
class MyArrayIterator extends ArrayIterator {
public function __construct($array, $flags=0) {
parent::__construct($array, $flags);
}
public function offsetGet($index) {
if (!$this->offsetExists($index)) {
return null;
}
return parent::offsetGet($index);
}
}
$arr = [
'location' => 'Paris'
];
$arrIt = new MyArrayIterator($arr);
echo $arrIt['country'];
echo "Only this is echoed";
#$arr['country'] - suppress errors, bad pratice.
&$arr['country'] - use reference, could add additional elements to array, bad pratice.

Php, check if object with property = value exists in array of objects

I think I could do this with a foreach loop like this:
foreach ($haystack as $item)
if (isset($item->$needle_field) && $item->$needle_field == $needle)
return true;
}
but i was wandering if it could be done without a loop?
something like:
if(in_array($item->$needle_field == $needle,$haystack)
return true;
Yes, in modern PHP you can determine if a specific object property contains a specific value without a classic loop by combining the forces of array_column() (which has evolved to also handle arrays of objects) and in_array().
Code: (Demo)
$objects = [
(object)['cats' => 2],
(object)['dogs' => 2],
(object)['fish' => 10],
(object)['birds' => 1],
];
$needleField = 'cats';
$needleValue = 2;
var_export(
in_array($needleValue, array_column($objects, $needleField))
);
// output: true
The advantage of this technique is the obviously concise syntax. This is a perfectly acceptable approach for relatively small volumes of data.
A possible disadvantage to this technique is that array_column() will be generating a new array of all of values that relate to the $needleField.
In my above demo, array_column() will only generate a single-element array because there is only one cats property in all of the objects. If we were processing a relatively large volume of data, then it would be inefficient to bother collecting all of the qualifying cats values and then run in_array() when only one match is necessary to return true.
For "large" volumes of data where performance is a primary criterion for script design, a classic foreach loop would be a better choice and as soon as an object satisfies the rules, then the loop should be halted via return or break.
Code: (Demo)
function hasPropertyValue(array $objects, $property, $value): bool {
foreach ($objects as $object) {
if (property_exists($object, $property) && $object->{$property} === $value) {
return true;
}
}
return false;
}
var_export(
hasPropertyValue($objects, $needleField, $needleValue)
);
It's possible, but it's not any better:
<?php
function make_subject($count, $success) {
$ret = array();
for ($i = 0; $i < $count; $i++) {
$object = new stdClass();
$object->foo = $success ? $i : null;
$ret[] = $object;
}
return $ret;
}
// Change true to false for failed test.
$subject = make_subject(10, true);
if (sizeof(array_filter($subject, function($value) {
return $value->foo === 3;
}))) {
echo 'Value 3 was found!';
} else {
echo 'Value 3 was not found.';
}
Outputs Value 3 was found!.
I advise you remain with the for loop: it's legible, unlike any tricks to save a line that you might find.
This will not work if the array you are searching is out of your control. But, if you are the one building the array of objects to be searched, you can structure it using the needle as array keys to be used with array_key_exists when you are searching.
For example, instead of making your $haystack array like this:
[
{
'needle_field' => $needle
},
...
]
Make it like this:
[
$needle => {
'needle_field' => $needle
},
...
]
And search like this:
if (array_key_exists($needle, $haystack)) {
return true;
}
Finally, if you need to, you can convert back to an integer indexed array by using array_values
$haystack = array_values($haystack);
This may not work in all situations but it worked great for me.
Maybe with array_key_exists:
if (array_key_exists($needle_field, $haystack) {
if ($haystack[$needle_field] == $needle) {
echo "$needle exists";
}
}

PHP Object, set multiple properties

Is it possible to set multiple properties at a time for an object in php?
Instead of doing:
$object->prop1 = $something;
$object->prop2 = $otherthing;
$object->prop3 = $morethings;
do something like:
$object = (object) array(
'prop1' => $something,
'prop2' => $otherthing,
'prop3' => $morethings
);
but without overwriting the object.
Not like the way you want. but this can be done by using a loop.
$map = array(
'prop1' => $something,
'prop2' => $otherthing,
'prop3' => $morethings
);
foreach($map as $k => $v)
$object->$k = $v;
See only 2 extra lines.
You should look at Object Oriented PHP Best Practices :
"since the setter functions return $this you can chain them like so:"
$object->setName('Bob')
->setHairColor('green')
->setAddress('someplace');
This incidentally is known as a fluent interface.
I would recommend you don't do it. Seriously, don't.
Your code is much MUCH cleaner the first way, it's clearer of your intentions, and you aren't obfocusing your code to the extent where sometime in the future someone would look at your code and think "What the hell was the idiot thinking"?
If you insist on doing something which is clearly the wrong way to go, you can always create an array, iterate it and set all the properties in a loop. I won't give you code though. It's evil.
You could write some setters for the object that return the object:
public function setSomething($something)
{
$this->something = $something;
return $this; //this will return the current object
}
You could then do:
$object->setSomething("something")
->setSomethingelse("somethingelse")
->setMoreThings("some more things");
You would need to write a setter for each property as a __set function is not capable of returning a value.
Alternatively, set a single function to accept an array of property => values and set everything?
public function setProperties($array)
{
foreach($array as $property => $value)
{
$this->{$property} = $value;
}
return $this;
}
and pass in the array:
$object->setProperties(array('something' => 'someText', 'somethingElse' => 'more text', 'moreThings'=>'a lot more text'));
I realise this is an old question but for the benefit of others that come across it, I solved this myself recently and wanted to share the result
<?php
//Just some setup
header('Content-Type: text/plain');
$account = (object) array(
'email' => 'foo',
'dob'=>((object)array(
'day'=>1,
'month'=>1,
'year'=>((object)array('century'=>1900,'decade'=>0))
))
);
var_dump($account);
echo "\n\n==============\n\n";
//The functions
function &getObjRef(&$obj,$prop) {
return $obj->{$prop};
}
function updateObjFromArray(&$obj,$array){
foreach ($array as $key=>$value) {
if(!is_array($value))
$obj->{$key} = $value;
else{
$ref = getObjRef($obj,$key);
updateObjFromArray($ref,$value);
}
}
}
//Test
updateObjFromArray($account,array(
'id' => '123',
'email' => 'user#domain.com',
'dob'=>array(
'day'=>19,
'month'=>11,
'year'=>array('century'=>1900,'decade'=>80)
)
));
var_dump($account);
Obviously there are no safeguards built in. The main caveat is that the updateObjFromArray function assumes that for any nested arrays within $array, the corresponding key in $obj already exists and is an object, this must be true or treating it like an object will throw an error.
Hope this helps! :)
I wouldn't actually do this....but for fun I would
$object = (object) ($props + (array) $object);
you end up with an stdClass composed of $objects public properties, so it loses its type.
Method objectThis() to transtypage class array properties or array to stdClass. Using direct transtypage (object) would remove numeric index, but using this method it will keep the numeric index.
public function objectThis($array = null) {
if (!$array) {
foreach ($this as $property_name => $property_values) {
if (is_array($property_values) && !empty($property_values)) {
$this->{$property_name} = $this->objectThis($property_values);
} else if (is_array($property_values) && empty($property_values)) {
$this->{$property_name} = new stdClass();
}
}
} else {
$object = new stdClass();
foreach ($array as $index => $values) {
if (is_array($values) && empty($values)) {
$object->{$index} = new stdClass();
} else if (is_array($values)) {
$object->{$index} = $this->objectThis($values);
} else if (is_object($values)) {
$object->{$index} = $this->objectThis($values);
} else {
$object->{$index} = $values;
}
}
return $object;
}
}

Categories