I have a function that takes variadic arguments, that I obtain from func_get_args().
This function needs to call a constructor with those arguments. However, I don't know how to do it.
With call_user_func, you can call functions with an array of arguments, but how would you call a constructor from it? I can't just pass the array of arguments to it; it must believe I've called it "normally".
Thank you!
For PHP < 5.3 it's not easily doable without first creating an instance of the class with call_user_func_array. However with Reflection this is pretty trivial:
$reflection = new ReflectionClass( 'yourClassName' );
$instance = $reflection->newInstanceArgs( $yourArrayOfConstructorArguments );
If for some reason you can not use ReflectionClass::newInstanceArgs here is another solution using eval():
function make_instance($class, $args) {
$arglist = array();
$i = 0;
foreach($args => &$v) {
$arglist[] = $n = '_arg_'.$i++;
$$n = &$v;
}
$arglist = '$'.implode(',$',$arglist);
eval("\$obj = new $class($arglist);");
return $obj;
}
$instance = make_instance('yourClassName', $yourArrayOfConstructorArguments);
Note that using this function enables you to pass arguments by reference to the constructor, which is not acceptable with ReflectionClass::newInstanceArgs.
Related
I have a the following class:
class MyClass {
public function __construct($id = 0, $humanIdentifier = '') {
$this->id = $id;
$this->humanID = $humanIdentifier;
}
}
So from my interpretation I should be able to pass either $id or $humanIdentifier to that constructor, neither or both if I wanted. However, when I call the code below I am finding that its the $id in the constructor args being set to hello world and not the $humanIdentifier, despite me specifying the $humanIdentifier when calling the constructor. Can anyone see where I am going wrong?
$o = new MyClass($humanIdentifier='hello world');
Edit: As of PHP8, named arguments are now supported. This wasn’t the case at the time of this post.
PHP does not support named arguments, it will set the value according to the order in which you pass the parameters.
In your case, you're not passing $humanIdentifier, but the result of the expression $humanIdentifier='hello world', to which $this->id is later set.
The only way I know to mimick named arguments in PHP are arrays. So you could do (in PHP7) :
public function __construct(array $config)
{
$this->id = $config['id'] ?? 0;
$this->humanId = $config['humanId'] ?? '';
}
Can anyone see where I am going wrong?
Yes, you think these are named parameters. They are not. They are positional parameters. So you'd call it like this:
new MyClass(0, 'hello world')
Adding support for named parameters has been suggested and rejected in the past. A newer RFC is proposed, but it still is to be refined and implemented.
You need to overload the constructor, but php does not have built-in functionality for it but there's a great workaround for it in documentation:
http://php.net/manual/en/language.oop5.decon.php#Hcom99903
Also here's a discussion why it might be a bad idea: Why can't I overload constructors in PHP?
like another answer said, php does not support named arguments. You can accomplish something similar with:
class MyClass {
public function __construct($args = array('id' => 0, 'humanIdentifier' => '') {.
// some conditional logic to emulate the default values concept
if(!isset($args['id'])){
$this->id = 0;
}else{
$this->id = $args['id'];
}
if(!isset($args['humanIdentifier'])){
$this->humanID = '';
}else{
$this->humanID = $args['humanIdentifier'];
}
}
}
you can then call it like:
new MyClass(array('humanIdentifier'=>'hello world'));
and the default id will be there. I am sure you can come up with some fancy iteration to accomplish this if there are enough parameters to make it worth while.
You can not create new object of class by this way:
$o = new MyClass($humanIdentifier='hello world');
You can use array as parameter of __construct:
class MyClass {
public function __construct(array $arg) {
$this->id = isset($arg['id']) ? $arg['id'] : 0;
$this->humanID = isset($arg['humanID']) ? $arg['humanID'] : 0;
}
}
Then you can create new object of class by this way:
$o = new MyClass(['humanId'=>hello world']);
Say I have a callable stored as a variable:
$callable = function($foo = 'bar', $baz = ...) { return...; }
How would I get 'bar'?
if (is_callable($callable)) {
return func_get_args();
}
Unfortunately func_get_args() is for the current function, is it possible to get a key value pair of arguments?
You can use reflection:
$f = new ReflectionFunction($callable);
$params = $f->getParameters();
echo $params[0]->getDefaultValue();
You may want to use get_defined_vars to accomplish this, this function will return an array of all defined variables, specifically by accessing the callable index from the output array.
I came across this question because I was looking for getting the arguments for a callable which is not just the function itself. My case is
class MyClass{
public function f(){
// do some stuff
}
}
$myclass = new MyClass();
$callable = array($myclass, "f);
This is a valid callback in php. In this case the solution given by #Marek does not work.
I worked around with phps is_callable function. You can get the name of the function by using the third parameter. Then you have to check whether your callback is a function or a (class/object) method. Otherwise the Reflection-classes will mess up.
if($callable instanceof Closure){
$name = "";
is_callable($callable, false, $name);
if(strpos($name, "::") !== false){
$r = new ReflectionMethod($name);
}
else{
$r = new ReflectionFunction($name);
}
}
else{
$r = new ReflectionFunction($callable);
}
$parameters = $r->getParameters();
// ...
This also returns the correct value for ReflectionFunctionAbstract::isStatic() even though the $name always uses :: which normally indicates a static function (with some exceptions).
Note: In PHP>=7.0 this may be easier using Closures. There you can do someting like
$closure = Closure::fromCallable($callable);
$r = new ReflectionFunction($closure);
You may also cause have to distinguish between ReflectionFunction and ReflectionMethod but I can't test this because I am not using PHP>=7.0.
The signature for my method looks like this:
public function ProgramRuleFilter(&$program, $today=null) {
When I invoke it like this,
$programs = array_filter($programs, array($this,'ProgramRuleFilter'));
Everything works as expected. The ProgramRuleFilter method updates the $program array and then returns true/false if it succeeded which correctly filters $programs.
However, now I want to pass an extra argument to the filter, $today. How can I do that?
I'm trying to invoke it like this:
$programs = array_filter($programs, new CallbackArgs(array($this,'ProgramRuleFilter'),$today));
Using this little class as a wrapper:
class CallbackArgs {
private $callback;
private $args;
function __construct() {
$args = func_get_args();
$this->callback = array_shift($args);
$this->args = $args;
}
function __invoke(&$arg) {
return call_user_func_array($this->callback, array_merge(array($arg),$this->args));
}
}
But the programs aren't being updated, so somewhere along the line it lost the reference to the original object. I'm not sure how to fix this.
The second argument to array_filter must be a callback; which means that array_filter itself will be calling your filter function. There is no way to tell array_filter to call that function in any other way, so you'll need to find a way to get the value of $today into your function some other way.
This is a perfect example of when to use a closure, which will let you bind some data (in this case, the value of $today) into a function / callback. Assuming you are using PHP 5.3 or later:
// Assuming $today has already been set
$object = $this; // PHP 5.4 eliminates the need for this
$programs = array_filter( $programs, function( $x ) use ( $today, $object ){
return $object->ProgramRuleFilter( $x, $today );
});
This defines a closure inline, using the values of $today and $object from the parent scope, and then just calls your existing function ProgramRuleFilter on that $object. (The somewhat unusual $object = $this gets around the fact that otherwise, the closure would not be able to call a method on your object instance. But in PHP 5.4, you can replace $object with $this inside the closure.)
Now, this is a somewhat inelegant way to do it, because all this closure does is hand off the work to the ProgramRuleFilter function. A better way would be to use the closure instead of the function. So:
// Assuming $today has already been set
$filter = function( $x ) use ( $today ){
// Cut and paste the contents of ProgramRuleFilter() here,
// and make it operate on $x and $today
};
$programs = array_filter( $programs, $filter );
Which variation works best for you will depend on the implementation of the rest of your app. Good luck!
I wrote a new method to handle it:
public static function array_filter_args($array, $callback) {
$args = array_slice(func_get_args(),2);
foreach($array as $key=>&$value) {
if(!call_user_func_array($callback, array_merge(array(&$value),$args))) {
unset($array[$key]);
}
}
return $array;
}
Called like this:
$programs = ArrayHelper::array_filter_args($programs, array($this,'ProgramRuleFilter'), $today);
I didn't know you could do this array(&$value), but I thought I'd try it, and it looks like it works. I'm guessing that array_merge is the culprit that dereferences the variable otherwise.
I have a function that does something similar to this:
function load_class($name){
require_once('classes/'.$name.'.php');
return new $name();
}
what I want to do is modify it so I can do something like this
function load_class($name, $vars = array()){
require_once('classes/'.$name.'.php');
return new $name($array[0], $array[1]);
}
The general gist of it is.
I want to be able to pass in an array of values that, gets used as the parameters for the class.
I dont want to pass in the actual array.
is this possible?
Of course, it's called var args and you want to unpack them. http://php.net/manual/en/function.func-get-arg.php. Check out the examples... unpacking an array of arguments in php.
See Also How to pass variable number of arguments to a PHP function
if you are trying to load classes then you could use __autoload function
more information here
You can call functions this way with call_user_func_array, but in the case of a class constructor, you should use ReflectionClass::newInstanceArgs:
class MyClass {
function __construct($x, $y, $z) { }
}
$class = new ReflectionClass("MyClass");
$params = array(1, 2, 3);
// just like "$instance = new MyClass(1,2,3);"
$instance = $class->newInstanceArgs($params);
Your code might look like this:
function load_class($name, $vars = array()){
require_once('classes/'.$name.'.php');
$class = new ReflectionClass($name);
return $class->newInstanceArgs($vars);
}
I have this fetch function:
public static function fetch($class, $key)
{
try
{
$obj = new $class($key);
}
catch(Exception $e)
{
return false;
}
return $obj;
}
It creates a new instance by calling that class's constructor and passing in the key. Now, how would I make it so I can pass in an array of arguments in $key, and have it like:
$obj = new $class($key[0], $key[1]...);
So that it works for one or more keys?
Hopefully that was clear enough.
Using PHP 5
This is an interesting question. If it wasn't a constructor function you were trying to give dynamic arguments to, then normally you could use call_user_func_array(). However, since the new operator is involved, there doesn't seem to be an elegant way to do this.
Reflection seems to be the consensus from what I could find. The following snippet is taken from the user comments on call_user_func_array(), and illustrates the usage quite nicely:
<?php
// arguments you wish to pass to constructor of new object
$args = array('a', 'b');
// class name of new object
$className = 'myCommand';
// make a reflection object
$reflectionObj = new ReflectionClass($className);
// use Reflection to create a new instance, using the $args
$command = $reflectionObj->newInstanceArgs($args);
// this is the same as: new myCommand('a', 'b');
?>
To shorten it up for your case, you can use:
$reflectionObject = new ReflectionClass($class);
$obj = $reflectionObject->newInstanceArgs($key);
Use reflection:
$classReflection = new ReflectionClass($class);
$obj = $classReflection->newInstanceArgs($key);
My library solves this this:
// Returns a new instance of a `$classNameOrObj`.
function fuNew($classNameOrObj, $constructionParams = array()) {
$class = new ReflectionClass($classNameOrObj);
if (empty($constructionParams)) { return $class->newInstance(); }
return $class->newInstanceArgs($constructionParams); }
The empty() test is required because newInstanceArgs() will complain if you give it an empty array, stupidly.
What does the constructor of the class look like? Does it accept an arbitrary number of arguments? It might be better to accept an array of keys instead of a list of key arguments.
call_user_func_array could probably do what you want:
$obj = new $object_class();
call_user_func_array(array($obj, '__construct'), $args);
Note that this calls the constructor twice, which could have negative side effects.