I am trying to create a mock object in PHP and PHPUnit. So far, I have this:
$object = $this->getMock('object',
array('set_properties',
'get_events'),
array(),
'object_test',
null);
$object
->expects($this->once())
->method('get_events')
->will($this->returnValue(array()));
$mo = new multiple_object($object);
Ignoring my hideously ambiguous object names for the minute, I understand that what I've done is
- Created a mock object, with 2 methods to configure,
- Configured the 'get_events' method to return a blank array, and
- Dropped the mock into the constructor.
What I'd like to do now is configure the second method, but I can't find anything explaining how to do that. I want to do something like
$object
->expects($this->once())
->method('get_events')
->will($this->returnValue(array()))
->expects($this->once())
->method('set_properties')
->with($this->equalTo(array()))
or some such, but that doesn't work. How should I do that?
Tangentially, does this indicate I've structured my code poorly, if I need to configured more than one method to test?
I don't have any experience with PHPUnit, but my guess would be something like this:
$object
->expects($this->once())
->method('get_events')
->will($this->returnValue(array()));
$object
->expects($this->once())
->method('set_properties')
->with($this->equalTo(array()));
Have you tried it already?
Edit:
Ok, by doing some code search, I found some examples that might help you out
Check this example
They use it like this:
public function testMailForUidOrMail()
{
$ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes',
'_search', '_count',
'_firstEntry'));
$ldap->expects($this->any())
->method('_getAttributes')
->will($this->returnValue(array (
'mail' =>
array (
'count' => 1,
0 => 'wrobel#example.org',
),
0 => 'mail',
'count' => 1)));
$ldap->expects($this->any())
->method('_search')
->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
$ldap->expects($this->any())
->method('_count')
->will($this->returnValue(1));
$ldap->expects($this->any())
->method('_firstEntry')
->will($this->returnValue(1));
(...)
}
Maybe your problem is somewhere else?
Let me know if that helped.
Edit2:
Can you try this:
$object = $this->getMock('object', array('set_properties','get_events'));
$object
->expects($this->once())
->method('get_events')
->will($this->returnValue(array()));
$object
->expects($this->once())
->method('set_properties')
->with($this->equalTo(array()));
The people looking for a solution to call the "same" method on the mock object multiple times, possibly with different parameters and return values, can use #Cody A. Ray's answer from this post.
Here is the answer from the post in case the links ever become invalid:
For others who are looking to both match input parameters and provide return values for multiple calls.. this works for me:
$mock
->method('myMockedMethod')
->withConsecutive([$argA1, $argA2], [$argB1, $argB2], [$argC1, $argC2])
->willReturnOnConsecutiveCalls($retValue1, $retValue2, $retValue3);
Related
I call an object that returns an array given certain chained methods:
Songs::duration('>', 2)->artist('Unknown')->genre('Metal')->stars(5)->getAllAsArray();
The problem lies that every time I want to get this array, for example, in another script, I have to chain everything again. Now imagine that in over 10 scripts.
Is there a way to recall the chained methods for later use?
Since you can't cache the result, you could cache the structure of the call chain in an array.
$chain = [
'duration' => ['>', 2],
'artist' => 'Unknown',
'genre' => 'Metal',
'stars' => 5,
'getAllAsArray' => null
];
You could use that with a function that emulates the chained call using the cached array:
function callChain($object, $chain) {
foreach ($chain as $method => $params) {
$params = is_array($params) ? $params : (array) $params;
$object = call_user_func_array([$object, $method], $params);
}
return $object;
}
$result = callChain('Songs', $chain);
If you can not cache your results as suggested, as I commented, here are a couple ideas. If your application allows for mixing of functions (as in you are permitted by standards of your company's development rules) and classes, you can use a function wrapper:
// The function can be as complex as you want
// You can make '>', 2 args too if they are going to be different all the time
function getArtists($array)
{
return \Songs::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(getArtists(array('Unkown','Metal',5)));
If you are only allowed to use classes and __callStatic() is not forbidden in your development and is also available in the version of PHP you are using, you might try that:
// If you have access to the Songs class
public __callStatic($name,$args=false)
{
// This should explode your method name
// so you have two important elements of your chain
// Unknown_Metal() should produce "Unknown" and "Metal" as key 0 and 1
$settings = explode("_",$name);
// Args should be in an array, so if you have 1 value, should be in key 0
$stars = (isset($args[0]))? $args[0] : 5;
// return the contents
return self::duration('>', 2)->artist($settings[0])->genre($settings[1])->stars($stars)->getAllAsArray();
}
This should return the same as your chain:
print_r(\Songs::Unknown_Metal(5));
It should be noted that overloading is hard to follow because there is no concrete method called Unknown_Metal so it's harder to debug. Also note I have not tested this particular set-up out locally, but I have notated what should happen where.
If those are not allowed, I would then make a method to shorten that chain:
public function getArtists($array)
{
// Note, '>', 2 can be args too, I just didn't add them
return self::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(\Songs::getArtists(array('Unkown','Metal',5)));
I wrote a lib doing exactly what you're looking for, implementing the principle suggested by Don't Panic in a high quality way: https://packagist.org/packages/jclaveau/php-deferred-callchain
In your case you would code
$search = DeferredCallChain::new_(Songs::class) // or shorter: later(Songs::class)
->duration('>',2) // static syntax "::" cannot handle chaining sadly
->artist('Unknown')
->genre('Metal')
->stars(5)
->getAllAsArray();
print_r( $search($myFirstDBSongs) );
print_r( $search($mySecondDBSongs) );
Hoping it will match your needs!
I am working on something where I need to be able to pass an indexed array of args to a method, much like how call_user_func_array works. I would use call_user_func_array but it is not an OOP approach, which is undesired, and it requires the method to be static, which breaks the target class's OO.
I have tried to use ReflectionClass but to no avail. You cannot invoke arguments to a method of the class, only the constructor. This is unfortunately, not desireable.
So I took to the man pages and looked at ReflectionFunction but there is no way to instantiate the class, point it to a method, and then invokeArgs with it.
Example using ReflectionFunction ( remember, this question is tagged PHP 5.4, hence the syntax):
$call = new \ReflectionFunction( "(ExampleClass())->exampleMethod" );
$call->invokeArgs( ["argument1", "argument2"] );
This fails with:
Function (Index())->Index() does not exist
Example using ReflectionMethod
$call = new \ReflectionMethod( "ExampleClass", "exampleMethod" );
$call->invokeArgs( new ExampleClass(), ["argument1", "argument2"] );
print_r( $call );
This fails with:
ReflectionMethod Object
(
[name] => Index
[class] => Index
)
The arguments are never passed to the method.
The desired results are:
class ExampleClass() {
public function exampleMethod( $exampleArg1, $exampleArg2 ){
// do something here
echo "Argument 1: {$exampleArg1}\n";
echo "Argument 2: {$exampleArg2}\n";
}
}
$array = [ 'exampleArg1Value', 'exampleArg2Value' ];
If I passed $array to an instance of ExampleClass->exampleMethod(), I would only have one argument, which would be an array. Instead, I need to be able to pull the individual arguments.
I was thinking that if there was a way to call ReflectorFunction on a ReflectorClass I would in in ship-shape and on my way, but it doesn't look like that is possible.
Does anyone have anything they have used to accomplish this previously?
AFAIK, the following should work:
$call = new \ReflectionMethod( "ExampleClass", "exampleMethod" );
$call->invokeArgs( new ExampleClass(), ["argument1", "argument2"] );
print_r( $call );
What minor version is PHP? Are you on 5.4.7?
I have written my own dependency injector, and I also construct classes with the parameters dynamicly. Here is some code that should get your going:
$type = 'ExampleClass';
$reflector = new \ReflectionClass( $type );
if ( !$reflector->isInstantiable() )
throw new \Exception( "Resolution target [$type] is not instantiable." );
$constructor = $reflector->getConstructor();
$parameters = $constructor->getParameters();
At this point you have a array of parameters, needed for construction. You can now substitute the parameters with the values and construct the class.
For some reason, something got stuck, somewhere.
$call = new \ReflectionMethod( "ExampleClass", "exampleMethod" );
$call->invokeArgs( new ExampleClass(), ["argument1", "argument2"] );
Now returns
Argument 1: argument1
Argument 2: argument2
I am going to try to reproduce the issue. It is on a fresh php 5.4.7 install with php-cli and fpm.
in PHP, I'm considering doing something like this:
function foo(){
echo 'bar';
}
$fn = 'foo';
$fn();
It works, but is it considered bad practice?
I have an multidimensional array of elements that each have a corresponding function. I would like to store that function name, and call the corresponding functions for each element when traversing the array.
something like:
function render_el1(){ echo 'et';}
function render_el2(){ echo 'to';}
$elements = array(
'el_1' => array(
'name' => 'Element One'
, 'func' => 'render_el1'
)
, 'el_2' => array(
'name' => 'Element Two'
, 'func' => 'render_el2'
)
);
foreach($elements as $element => $options){
$fn = $options['func'];
echo '<h1>'.$options['name'].'</h1>';
if (function_exists($fn)) {
$fn();
}
}
Any comments to this approach is highly welcome, and I'd also like to know what this method is called in programming terms.
Not sure it is bad practice, but it makes your code hard to understand : to understand your short (5 lines) example, I've had to think :-(
Using call_user_func() and other functions of the same kind could have at least one advantage : looking at the code, one would immediatly understand you are calling a function in a way that's not the one we're generally used to.
You want to register functions into an array in your second example and then call them for what looks like a render process. This is similar to using function pointers in C (or paint event callbacks etc). It is an okay approach if you don't want to/can't use polymorphism (the feature that makes OOP worthwhile).
Your approach is simpler at that stage, but will probably get more bloated if you are adding more sophisticated code.
I am a total NOOB in programming (but this is only my second question on stackoverflow :-) ).
By a foreach function I get 5 different string values for $Loncoord, $Latcoord, $gui;
this I can see with the print_r in the code written below:
"-5.68166666667","+24.6513888889","IMG_3308",
But I now want to create 5 different markers in the $map->addMarkerByCoords (function is it ?).
print_r ("$Loncoord");
print_r ("$Latcoord");
print_r ("$gui");
$map->addMarkerByCoords("$Loncoord","$Latcoord","$gui",'OldChicago');
Is this possible?
Do I need to put them in a array and call these in the (function ?) or do I need to use a foreach function?
I tried both for a week now but I can't get it working.
Can you help me?
The answers you produced gave me a turn in the right direction.
Thank you for the quick responses and the explaining part.
But for the addMarkerByCoord (function! (stupid me)) I found this in the googlemaps API:
function addMarkerByCoords($lon,$lat,$title = '',$html = '',$tooltip = '') {
$_marker['lon'] = $lon;
$_marker['lat'] = $lat;
$_marker['html'] = (is_array($html) || strlen($html) > 0) ? $html : $title;
$_marker['title'] = $title;
$_marker['tooltip'] = $tooltip;
$this->_markers[] = $_marker;
$this->adjustCenterCoords($_marker['lon'],$_marker['lat']);
// return index of marker
return count($this->_markers) - 1;
}
It depends on the implementation of map::addMarkerByCoords()
The method (not a function) name, and its signature, suggests that you are only able to add one coord at a time. But to be sure you'ld need to know the methods true signature. So the question is: does the method allow arrays as arguments?
Usually, a method that allows you to add multiple items at once, has the plural name of the intended action in it's name:
map::addMarkersByCoords() // note the s after Marker
If the 'map' class is your own implementation, you are free to implement it the way you like of course, but in that case keep the descriptive names of the methods in mind. So, add one marker:
map::addMarkerByCoords()
Add multiple markers at once:
map::addMarkersByCoords()
Typically you would implement the plural method as something like this:
public function addMarkersByCoords( array $markers )
{
foreach( $markers as $marker )
{
$this->addMarkerByCoord( $marker[ 'long' ], $marker[ 'lat' ], $marker[ 'img ' ], $marker[ 'name' ] );
}
}
Basically, the plural method accepts one array, and adds each individual marker by calling the singular method.
If you wanna get even more OOP, you could implement the plural and singular method to accept (an array of) Marker objects. But that is not particalarly relevant for this discussion.
Also, the suggested expantion of the Map's interface with a plural method doesn't nessecarily mean you can't add multiple markers outside the object with calling the singular method in a foreach loop. It's up to your preference really.
If you want to call the addMarkerByCoords for 5 times with 5 different values for each parameter then you can build an array for every parameter and then iterate with the foreach function:
$Loncoord=array(1,2,3,4,5);
$Latcoord=array(1,2,3,4,5);
$gui=array(1,2,3,4,5);
$city=array('OldChicago','bla','bla','bla','bla');
foreach($Loncoord as $k=>$v)
$map->addMarkerByCoords($Loncoord[$k],$Latcoord[$k],$gui[$k],$city[$k]);
Try losing some of the quotes...
$map->addMarkerByCoords($Loncoord,$Latcoord,$gui,'OldChicago');
To answer the question properly though, we would need to know what addMarkerByCoords was expecting you to pass to it.
Say I have a class with a private dispatch table.
$this->dispatch = array(
1 => $this->someFunction,
2 => $this->anotherFunction
);
If I then call
$this->dispatch[1]();
I get an error that the method is not a string. When I make it a string like this:
$this->dispatch = array(
1 => '$this->someFunction'
);
This produces
Fatal error: Call to undefined function $this->someFunction()
I have also tried using:
call_user_func(array(SomeClass,$this->dispatch[1]));
Resulting in Message: call_user_func(SomeClass::$this->someFunction) [function.call-user-func]: First argument is expected to be a valid callback.
Edit: I realized that this didn't really make sense since it is calling SomeClass::$this when $this is SomeClass. I have tried this a few ways, with the array containing
array($this, $disptach[1])
This still does not accomplish what I need.
End edit
This works if I do not have a class and just have a dispatch file with some functions. For example, this works:
$dispatch = array(
1 => someFunction,
2 => anotherFunction
);
I'm wondering if there is a way that I can still keep these as private methods in the class yet still use them with the dispatch table.
You can store the name of the method in dispatch like:
$this->dispatch = array('somemethod', 'anothermethod');
and then use:
$method = $this->dispatch[1];
$this->$method();
The call_user_func*-Family of functions should work like this:
$this->dispatch = array('somemethod', 'anothermethod');
...
call_user_func(array($this,$this->dispatch[1]));