Mockery object argument validation issue - php

Consider the example classes (apologies for it being so convoluted, but it's as slim as possible):
class RecordLookup
{
private $records = [
13 => 'foo',
42 => 'bar',
];
function __construct($id)
{
$this->record = $this->records[$id];
}
public function getRecord()
{
return $this->record;
}
}
class RecordPage
{
public function run(RecordLookup $id)
{
return "Record is " . $id->getRecord();
}
}
class App
{
function __construct(RecordPage $page, $id)
{
$this->page = $page;
$this->record_lookup = new RecordLookup($id);
}
public function runPage()
{
return $this->page->run($this->record_lookup);
}
}
In which I want to test App whilst mocking RecordPage:
class AppTest extends \PHPUnit_Framework_TestCase
{
function testAppRunPage()
{
$mock_page = \Mockery::mock('RecordPage');
$mock_page
->shouldReceive('run')
->with(new RecordLookup(42))
->andReturn('bar');
$app = new App($mock_page, 42);
$this->assertEquals('Record is bar', $app->runPage());
}
}
Note: the expected object argument ->with(new RecordLookup(42)).
I would expect this to pass however Mockery returns throws No matching handler found for Mockery_0_RecordPage::run(object(RecordLookup)). Either the method was unexpected or its arguments matched no expected argument list for this method.
I'm assuming this is because a strict comparison is used for the arguments expected through with() and new RecordLookup(42) === new RecordLookup(42) evaluates as false. Note new RecordLookup(42) == new RecordLookup(42) evaluates as true so if there was someway of relaxing the comparison it would fix my problem.
Is there a proper way to handle expected instance arguments in Mockery? Maybe I'm using it incorrectly?

You can tell mockery that a RecordLookup instance (any) should be received:
$mock_page
->shouldReceive('run')
->with(\Mockery::type('RecordLookup'))
->andReturn('bar');
But this will match any instance of RecordLookup. If you need to dig inside the object and check if it's value is 42, then you can employ a custom validator:
$mock_page
->shouldReceive('run')
->with(\Mockery::on(function($argument) {
return
$argument instanceof RecordLookup &&
'bar' === $argument->getRecord()
;
}))
->andReturn('bar');
There are more options, well explained in the docs.

As an alternative, the documentation proposes to use equalTo($object).
For example:
$userRepository->shouldReceive('create')
->once()
->with(\Hamcrest\Core\IsEqual::equalTo(
new User(0, "Test", "Dummy", "fakelogin")));

Related

Check if something is a function before calling it

I have a class that looks a bit like this:
<?php
namespace App\Http\Controllers;
use Exception;
use Illuminate\Http\Request;
class FormatAddressController extends Controller
{
public function __construct()
{
$this->middleware(['api-auth']);
$this->middleware(['api-after']);
}
public function format(Request $request) {
// TODO first, get customer settings to see what functions to run, and how to run them
// but, assume that the settings come as an array where the key is the function name
// and the value is one of NULL, false, or settings to pass through to the function
$settings = ['isoAndCountry' => true, 'testFunc' => ['testSetting' => 'test setting value']];
$address = $request->input('address');
$errors = [];
foreach ($settings as $funcName => $funcSettings) {
try {
$address = $this->$funcName($funcSettings, $address); // every function has to return the modified address
} catch(Exception $e) {
$errors[$funcName] = $e;
}
}
return response()->json([
'address' => $address,
'errors' => $errors
]);
}
public function isoAndCountry($settings, $address) {
// TODO
return $address;
}
}
Now, when I call this function, isoAndCountry, through that settings loop I defined above, it works! It works just fine.
However I tried following this thread and checking is_callable and... it errors:
if (is_callable($this->$funcName)) {
try {
$address = $this->$funcName($funcSettings, $address); // every function has to return the modified address
} catch(Exception $e) {
$errors[$funcName] = $e;
}
}
How can I check if it's callable? Why doesn't this work?
You have to use method_exists here to check if the method really exists in the class.
foreach ($settings as $funcName => $funcSettings) {
if (method_exists($this, $funcName)) {
$this->$funcName($funcSettings, $address);
}
}
The reason why is_callable will not work in your scenario is because Laravel controllers has a __call magic method which will handle undefined methods, so running is_callable on any non existing methods would return true.
Take the following class as an example:
class A
{
public function __construct()
{
var_dump(is_callable([$this, 'testFunc']));
}
}
The output of new A would be false. However, if you add the following into the class:
public function __call($name, $arguments)
{
//
}
Now the output of the var_dump would return true.
You can read more about the __call scenario I've mentioned right here: https://www.php.net/manual/en/function.is-callable.php#118623
For more information about __call: https://www.php.net/manual/en/language.oop5.overloading.php#object.call
May be this can also solve the problem :
if(method_exists($this,$funcName)){ ... }
You can use
if (is_callable([$this, $funcName])) { ...
instead.
The way you have it written with is_callable($this->$funcName), it's going to look for a property called $funcName on $this, (which probably doesn't exist) and check if that's callable. If you use that array syntax it will evaluate the named method instead.
It may be simpler in this case to use
if (method_exists($this, $funcName)) {
since you're using it in another method of the same object, if the method exists it should be callable.
You need to differentiate between class properties and methods. For example in this class:
class A {
private $foo = null;
public function getFoo() {
return $this->foo;
}
}
the private $foo is property and can be checked by property_exists()
the public function getFoo() is method and can be checked by method_exists()
https://www.php.net/manual/en/function.property-exists.php
https://www.php.net/manual/en/function.method-exists.php
I think that $this->$funcName works only for properties.

Why PHP `__invoke` Not Working When Triggered from an Object Property

I wonder whether this is a bug or normal. Let’s say I have a class with some magical functions:
class Foo {
public function __toString() {
return '`__toString` called.';
}
public function __get($key) {
return '`__get(' . $key . ')` called.';
}
public function __invoke($x = "") {
return '`__invoke(' . $x . ')` called.';
}
}
And then create an instance in an object property like this:
$object = (object) [
'foo' => 'bar',
'baz' => new Foo
];
Then test it:
echo $object->baz;
echo $object->baz->qux;
echo $object->baz('%'); // :(
It is broken in the last echo: Call to undefined method stdClass::baz()
Currently, the only solution I can do is to store the __invoke part in a temporary variable and then call that variable as a function like this:
$x = $object->baz;
echo $x('%'); // :)
It works fine when I instantiate the class in an array property:
$array = [
'baz' => new Foo
];
echo $array['baz'];
echo $array['baz']->qux;
echo $array['baz']('%'); // :)
By the way, I need this ability on my object for something related to API:
$foo = (object) ['bar' => new MyClass];
echo $foo->bar; → should trigger __toString
echo $foo->bar->baz; → should trigger __get
echo $foo->bar(); → should trigger __invoke
echo $foo->bar->baz(); → should trigger __call
All of them should return a string.
Can this be done in PHP completely? Thanks.
No can do.
The line in question is simply ambigous, and the error message shows you how ... It is more logical to try to access the baz() method of your $object object.
That's just the context given by the parser when it sees $object->baz()
As already mentioned in the comments, you can remove that ambiguity, help the parser by telling it that $object->baz is itself an expression that needs to be executed first:
($object->baz)('arg');
PHP is also itself a program, and has to know how to execute something before executing it. If it could blindly try every possible "magic" method on every object in a $foo->bar->baz->qux chain, then it wouldn't be able to tell you what the error is when it is encountered - it would just silently crash.
I have solved my problem by detecting the existence of an __invoke method inside the __call method of a class.
class MyStdClass extends stdClass {
protected $data = [];
public function __construct(array $array) {
$this->data = $array;
}
public function __get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
public function __call($key, $args = []) {
if (isset($this->data[$key])) {
$test = $this->data[$key];
// not an object = not an instance, skip!
if (!is_object($test)) {
return $this->__get($key);
}
if (!empty($args) && get_class($test) && method_exists($test, '__invoke')) {
// or `return $test(...$args)`
return call_user_func([$test, '__invoke'], ...$args);
}
}
return $this->__get($key);
}
public function __set($key, $value = null) {
$this->data[$key] = $value;
}
public function __toString() {
return json_encode($this->data);
}
public function __isset($key) {}
public function __unset($key) {}
}
So, instead of converting the array into object with (object), here I use:
$object = new MyStdClass([
'foo' => 'bar',
'baz' => new Foo
]);

How can I achieve something like $app->error()->encode() in PHP?

I want to include this function in a class. This function will essentially json_encode the output of the previous function (I hope that makes sense).
I want to do something like this:
<?php
$app = new App;
// $app->error(); // Should return ['some error', 'some other error']
echo $app->error()->encode(); // Should return {'errors': ['some error', 'some other error']}.
Also what's the correct term for such function? I've been searching but couldn't find anything as I didn't really know what I was looking for.
Thanks!
Edit
I think you got that wrong. It's my mistake I didn't mention it before.
That's just an example. Just like in frameworks like Slim, where you can do something like:
$response->getBody()->write('Something');
I want to do something similar to that. Not just that. I want to learn how it's done.
Here is some boilerplate code you could use. The idea is that you should make the error method return an object of yet another class. That object should then in turn have the encode method.
In your example, you want $app->error() to return an array. For it to behave as an array, we can extend the ArrayObject class.
Secondly, you want that same $app->error() to expose another method encode. So you define that method in that same class mentioned above:
// Extend ArrayObject to make objects behave as arrays
class ErrorMsg extends ArrayObject {
// Add method to return JSON string
public function encode() {
return json_encode(array("errors" => $this->getArrayCopy()));
}
}
class App {
private $error;
public function doSomething() {
// For demo sake, just set an error:
$this->error = ["An error occurred in doSomething", "No further info"];
}
public function error() {
// This is the key: instantiate and return another object
return new ErrorMsg($this->error);
}
}
$app = new App;
// Set an error condition in $app
$app->doSomething();
// Loop over error array
foreach ($app->error() as $index => $error) {
// Display the error
echo "Error $index is: $error\n";
}
// Display the JSON encoding of the same.
echo $app->error()->encode() . "\n";
Output:
Error 0 is: An error occurred in doSomething
Error 1 is: No further info
{"errors":["An error occurred in doSomething","No further info"]}
See it run on eval.in
General idea to chain method calls
In general, when you want your objects to support chained -> notation, you must make sure to define each method as returning yet another object with its own methods. Then those methods can again return objects, with again exposed methods, etc. And so you can chain-call on and on.
So if you want to be able to write:
$a = new A();
$result = $a->b()->c()->d();
...then your code would look something like this:
class D {
// ...
}
class C {
private $d;
public function C() { // constructor
$this->d = new D();
}
public function d() {
return $this->d;
}
}
class B {
private $c;
public function B() { // constructor
$this->c = new C();
}
public function c() {
return $this->c;
}
}
class A {
private $b;
public function A() { // constructor
$this->b = new B();
}
public function b() {
return $this->b;
}
}
Of course, this is just the structure, and you'd pass some specific data to either the constructors and/or methods. But the main idea is that you never return simple types (String, Number, ...), but always objects.

Having trouble detecting 'return false'

I have a PHP class that can return FALSE.
For some weird reason, I am unable to detect the FALSE return.
I can detect any other return value without any trouble.
Dumbed down version....
error_reporting(E_ALL);
ini_set("display_errors", 1);
$run = new myClass();//class returns false
$response = $run->myMethod();
I've tested with the following...
!$response
!isset($response)
empty($response)
$response == false
$response === false
I'm unable to get any type of reaction for any of the conditions I'm familiar with.
If I set the return to 1 or ANYTHING other than false, $response will contain whatever the class returns.
It's not like this is my first day. (it's my 2nd).
I use return false; frequently and have never had any trouble until today.
Certainly I can fix this by returning something like 'wtf' and test for that condition, but I'd really like to figure out what I'm missing here.
Thanks for any ideas.
When you make a instance of your class it will always your return a object not the return of the construct.
<?php
class SomeClass
{
// construct function that will always return false
public function __construct()
{
return false;
}
}
class TestClass extends PHPUnit_Framework_TestCase
{
public function testReturn()
{
// check if this code returns false
$this->assertFalse(new SomeClass);
}
public function testAssert()
{
// Checks if the object return is actually of SomeClass
$this->assertInstanceOf('SomeClass', new SomeClass);
}
public function testReturnOfConstruct()
{
//checks the return of the construct
$object = new SomeClass;
$this->assertFalse($object->__construct());
}
}
Return of phpunit
phpunit --debug test3.php
PHPUnit 3.7.28 by Sebastian Bergmann.
Starting test 'TestClass::testReturn'.
F
Starting test 'TestClass::testAssert'.
.
Starting test 'TestClass::testReturnOfConstruct'.
.
Time: 7 ms, Memory: 5.25Mb
There was 1 failure:
1) TestClass::testReturn
Failed asserting that SomeClass Object () is false.
/var/www/test3.php:14
FAILURES!
Tests: 3, Assertions: 3, Failures: 1.
Are you trying to return false from within public function __construct() or public function MyClass()*? This won't work as the new MyClass() call will either trigger a PHP error, or return an instance of MyClass, which is never going to evaluate to false because it is an instance of an object and thus not false.
If your class does not need to be instantiated to function, you can create a static method to perform the logic you are trying to run like so:
<?php
class MyClass
{
const COLOUR_BLUE = 'blue';
public static function isSkyBlue()
{
if (isset($_POST['sky']) && $_POST['sky'] == self::COLOUR_BLUE) {
return true;
} else {
return false;
}
}
}
// Call doThings() statically to get its value
$response = MyClass::isSkyBlue();
if ($response === false) {
// Your logic goes here
}
Alternatively, if you are passing things to the constructor before it performs its logic, you can do the following:
<?php
class MyClass
{
const COLOUR_BLUE = 'blue';
protected $otherObject = null;
public function __construct($otherObject)
{
$this->otherObject = $otherObject;
}
public function isSkyBlue()
{
if ($this->otherObject->sky == self::COLOUR_BLUE) {
return true;
} else {
return false;
}
}
}
// Call doThings() statically to get its value
$response = new MyClass($otherObject)
if ($response->isSkyBlue() === false) {
// Your logic goes here
}
* Note that your constructor should always be called __construct() as using the class name for the constructor has been deprecated since PHP 5.
If you are using a constructor, it wont return anything...it is not a normal function

Add method in an std object in php

Is it possible to add a method/function in this way, like
$arr = array(
"nid"=> 20,
"title" => "Something",
"value" => "Something else",
"my_method" => function($arg){....}
);
or maybe like this
$node = (object) $arr;
$node->my_method=function($arg){...};
and if it's possible then how can I use that function/method?
This is now possible to achieve in PHP 7.1 with anonymous classes
$node = new class {
public $property;
public function myMethod($arg) {
...
}
};
// and access them,
$node->property;
$node->myMethod('arg');
You cannot dynamically add a method to the stdClass and execute it in the normal fashion. However, there are a few things you can do.
In your first example, you're creating a closure. You can execute that closure by issuing the command:
$arr['my_method']('Argument')
You can create a stdClass object and assign a closure to one of its properties, but due to a syntax conflict, you cannot directly execute it. Instead, you would have to do something like:
$node = new stdClass();
$node->method = function($arg) { ... }
$func = $node->method;
$func('Argument');
Attempting
$node->method('Argument')
would generate an error, because no method "method" exists on a stdClass.
See this SO answer for some slick hackery using the magic method __call.
Since PHP 7 it is also possible to directly invoke an anonymous function property:
$obj = new stdClass;
$obj->printMessage = function($message) { echo $message . "\n"; };
echo ($obj->printMessage)('Hello World'); // Hello World
Here the expression $obj->printMessage results in the anonymous function which is then directly executed with the argument 'Hello World'. It is however necessary to put the function expression in paranetheses before invoking it so the following will still fail:
echo $obj->printMessage('Hello World');
// Fatal error: Uncaught Error: Call to undefined method stdClass::printMessage()
Another solution would be to create an anonymous class and proxy the call via the magic function __call, with arrow functions you can even keep reference to context variables:
new Class ((new ReflectionClass("MyClass"))->getProperty("myProperty")) {
public function __construct(ReflectionProperty $ref)
{
$this->setAccessible = fn($o) => $ref->setAccessible($o);
$this->isInitialized = fn($o) => $ref->isInitialized($o);
$this->getValue = fn($o) => $ref->getValue($o);
}
public function __call($name, $arguments)
{
$fn = $this->$name;
return $fn(...$arguments);
}
}
class myclass {
function __call($method, $args) {
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
$obj = new myclass();
$obj->method = function($var) { echo $var; };
$obj->method('a');
Or you can create defult class and use...

Categories