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.
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 'm using this in my code:
call_user_func_array ( array ($controller, $method ), $this->params );
but I found out that the code below does the same thing:
$controller->$method($this->params);
Is there any difference between the two versions?
Thanks
Adam Ramadhan
They are not the same.
If $method is showAction and $this->params is array(2, 'some-slug'), then the first call would be equivalent to:
$controller->showAction(2, 'some-slug');
Whereas the second would be:
$controller->showAction(array(2, 'some-slug'));
Which one you want to use depends on how the rest of your system works (your controllers in particular). I personally would probably go with the first.
They work alike. The only significant difference is that $controller->$nonexistant() would generate a fatal error. While call_user_func_array fails with just an E_WARNING should $method not exist.
Fun fact. Should your $controller harbor a closure $method, then you would actually have to combine both approaches:
call_user_func_array ( $controller->$method, $this->params );
They are doing the same thing, but the second form is shorter, clearer, and faster. Prefer it.
$controller->$method($this->params);
In this case your function will get an array of params and who know how many of them can be and who know what inside $params[0] can be
function myaction($params){
echo $params[0].$params[1].$params[2];
}
In another case you can get exactly a variable from array of params
call_user_func_array ( array ($controller, $method ), $this->params );
Prime example
You have URL like
http://example.com/newsShow/150/10-20-2018
or like that
http://example.com/newsShow/150/10-20-2018/someotherthings/that/user/can/type
in both case you will get only what you need to get
call_user_func_array ( array ($controller, myaction ), $this->params );
function myaction($newsid,$newsdate){
echo $newsid; // will be 150
echo $newsdate; // will be 10-20-2018
}
I know it is possible to use optional arguments as follows:
function doSomething($do, $something = "something") {
}
doSomething("do");
doSomething("do", "nothing");
But suppose you have the following situation:
function doSomething($do, $something = "something", $or = "or", $nothing = "nothing") {
}
doSomething("do", $or=>"and", $nothing=>"something");
So in the above line it would default $something to "something", even though I am setting values for everything else. I know this is possible in .net - I use it all the time. But I need to do this in PHP if possible.
Can anyone tell me if this is possible? I am altering the Omnistar Affiliate program which I have integrated into Interspire Shopping Cart - so I want to keep a function working as normal for any places where I dont change the call to the function, but in one place (which I am extending) I want to specify additional parameters. I dont want to create another function unless I absolutely have to.
No, in PHP that is not possible as of writing. Use array arguments:
function doSomething($arguments = array()) {
// set defaults
$arguments = array_merge(array(
"argument" => "default value",
), $arguments);
var_dump($arguments);
}
Example usage:
doSomething(); // with all defaults, or:
doSomething(array("argument" => "other value"));
When changing an existing method:
//function doSomething($bar, $baz) {
function doSomething($bar, $baz, $arguments = array()) {
// $bar and $baz remain in place, old code works
}
Have a look at func_get_args: http://au2.php.net/manual/en/function.func-get-args.php
Named arguments are not currently available in PHP (5.3).
To get around this, you commonly see a function receiving an argument array() and then using extract() to use the supplied arguments in local variables or array_merge() to default them.
Your original example would look something like:
$args = array('do' => 'do', 'or' => 'not', 'nothing' => 'something');
doSomething($args);
PHP has no named parameters. You'll have to decide on one workaround.
Most commonly an array parameter is used. But another clever method is using URL parameters, if you only need literal values:
function with_options($any) {
parse_str($any); // or extract() for array params
}
with_options("param=123&and=and&or=or");
Combine this approach with default parameters as it suits your particular use case.
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);
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]));