3

I am trying to call a class' constructor through a callable, so I had the following code:

$callable = array('Foo', '__construct');

However calling this will throw the following error:

Fatal error: Non-static method Foo::__construct() cannot be called statically

I understand that the constructor is not a static method, but I can't use an existing instance to call the constructor for a new instance (as it will just call the constructor on the existing object again), is there any way at all to call a constructor like this?

Choraimy
  • 166
  • 1
  • 11

3 Answers3

4

If you're looking for a simple way to dynamically choose which class to construct, you can use a variable name with the new keyword, like so:

$inst = new $class_name;
// or, if the constructor takes arguments, provide those in the normal way:
$inst = new $class_name('foo', 'bar');

However, if what you need is a way of passing the constructor to something which is already expecting a callable, the best I can think of is to wrap it in an anonymous function:

$callable = function() { return new Foo; }
call_user_func( $callable );

Or using the short single-expression closure syntax introduced in PHP 7.4:

$callable = fn() => new Foo;
call_user_func( $callable );
IMSoP
  • 89,526
  • 13
  • 117
  • 169
3

If you really have to use call_user_func, this might work, though it's not clear why you would want to do this:

$reflection = new ReflectionClass("Foo");
$instance = $reflection->newInstanceWithoutConstructor();
call_user_func(array($instance, '__construct'));
laurent
  • 88,262
  • 77
  • 290
  • 428
  • Argh, just 1 minute before I wanted to post a [ReflectionClass::newInstance](http://www.php.net/manual/en/reflectionclass.newinstance.php) answer. – Markus Malkusch Jun 09 '14 at 21:47
  • The same class also has the two very handy methods `newInstance` and `newInstanceArgs` which call the constructor with arguments. `newInstance` takes a variable argument list, while `newInstanceArgs` takes all arguments as array. – MauganRa Jun 19 '17 at 08:23
  • It should be noted that repeatedly calling `call_user_func([$instance, '__construct'])` with this solution does not create new instances. Instead, it repeatedly calls `__construct()` on the same instance. If you want to create new instances with call_user_func(), you need one of the other answers. – donquixote Feb 16 '20 at 14:56
1

The more correct answer, as @MauganRa already mentioned in a comment, is using \ReflectionClass::newInstance() or \ReflectionClass::newInstanceArgs(). I think this should be expressed in a full answer.

Now if you want to pass this around as a callback and use call_user_func() or call_user_func_array(), try this:

$callable = [new \ReflectionClass('C'), 'newInstance'];

Or a more fleshed-out example:

class Car {
  private $color;
  private $size;

  public function __construct($color, $size) {
    $this->color = $color;
    $this->size = $size;
  }

  public function describe() {
      return "A $this->size $this->color car.";
  }
}

$callable = [new \ReflectionClass('Car'), 'newInstance'];

$cars = [];
$cars[] = call_user_func($callable, 'red', 'big')->describe();
$cars[] = call_user_func_array($callable, ['blue', 'small'])->describe();

var_export($cars);

Demo: https://3v4l.org/7fvQL

I am not aware of another / better solution.

donquixote
  • 4,877
  • 3
  • 31
  • 54