* an 'args' key that provides arguments to pass to the
* constructor/callable.
*
- * Object construction using a specification having both 'class' and
- * 'args' members will call the constructor of the class using
- * ReflectionClass::newInstanceArgs. The use of ReflectionClass carries
- * a performance penalty and should not be used to create large numbers of
- * objects. If this is needed, consider introducing a factory method that
- * can be called via call_user_func_array() instead.
- *
* Values in the arguments collection which are Closure instances will be
* expanded by invoking them with no arguments before passing the
* resulting value on to the constructor/callable. This can be used to
if ( !$args ) {
$obj = new $clazz();
} else {
- $ref = new ReflectionClass( $clazz );
- $obj = $ref->newInstanceArgs( $args );
+ $obj = static::constructClassInstance( $clazz, $args );
}
} elseif ( isset( $spec['factory'] ) ) {
$obj = call_user_func_array( $spec['factory'], $args );
}
}, $list );
}
+
+ /**
+ * Construct an instance of the given class using the given arguments.
+ *
+ * PHP's `call_user_func_array()` doesn't work with object construction so
+ * we have to use other measures. Starting with PHP 5.6.0 we could use the
+ * "splat" operator (`...`) to unpack the array into an argument list.
+ * Sadly there is no way to conditionally include a syntax construct like
+ * a new operator in a way that allows older versions of PHP to still
+ * parse the file. Instead, we will try a loop unrolling technique that
+ * works for 0-10 arguments. If we are passed 11 or more arguments we will
+ * take the performance penalty of using
+ * `ReflectionClass::newInstanceArgs()` to construct the desired object.
+ *
+ * @param string $clazz Class name
+ * @param array $args Constructor arguments
+ * @return mixed Constructed instance
+ */
+ public static function constructClassInstance( $clazz, $args ) {
+ // TODO: when PHP min version supported is >=5.6.0 replace this
+ // function body with `return new $clazz( ... $args );`.
+ $obj = null;
+ switch ( count( $args ) ) {
+ case 0:
+ $obj = new $clazz();
+ break;
+ case 1:
+ $obj = new $clazz( $args[0] );
+ break;
+ case 2:
+ $obj = new $clazz( $args[0], $args[1] );
+ break;
+ case 3:
+ $obj = new $clazz( $args[0], $args[1], $args[2] );
+ break;
+ case 4:
+ $obj = new $clazz( $args[0], $args[1], $args[2], $args[3] );
+ break;
+ case 5:
+ $obj = new $clazz(
+ $args[0], $args[1], $args[2], $args[3], $args[4]
+ );
+ break;
+ case 6:
+ $obj = new $clazz(
+ $args[0], $args[1], $args[2], $args[3], $args[4],
+ $args[5]
+ );
+ break;
+ case 7:
+ $obj = new $clazz(
+ $args[0], $args[1], $args[2], $args[3], $args[4],
+ $args[5], $args[6]
+ );
+ break;
+ case 8:
+ $obj = new $clazz(
+ $args[0], $args[1], $args[2], $args[3], $args[4],
+ $args[5], $args[6], $args[7]
+ );
+ break;
+ case 9:
+ $obj = new $clazz(
+ $args[0], $args[1], $args[2], $args[3], $args[4],
+ $args[5], $args[6], $args[7], $args[8]
+ );
+ break;
+ case 10:
+ $obj = new $clazz(
+ $args[0], $args[1], $args[2], $args[3], $args[4],
+ $args[5], $args[6], $args[7], $args[8], $args[9]
+ );
+ break;
+ default:
+ // Fall back to using ReflectionClass and curse the developer
+ // who decided that 11+ args was a reasonable method
+ // signature.
+ $ref = new ReflectionClass( $clazz );
+ $obj = $ref->newInstanceArgs( $args );
+ }
+ return $obj;
+ }
}
$this->assertInternalType( 'string', $obj->setterArgs[0] );
$this->assertSame( 'unwrapped', $obj->setterArgs[0] );
}
+
+ /**
+ * @covers ObjectFactory::constructClassInstance
+ * @dataProvider provideConstructClassInstance
+ */
+ public function testConstructClassInstance( $args ) {
+ $obj = ObjectFactory::constructClassInstance(
+ 'ObjectFactoryTestFixture', $args
+ );
+ $this->assertSame( $args, $obj->args );
+ }
+
+ public function provideConstructClassInstance() {
+ // These args go to 11. I thought about making 10 one louder, but 11!
+ return array(
+ '0 args' => array( array() ),
+ '1 args' => array( array( 1, ) ),
+ '2 args' => array( array( 1, 2, ) ),
+ '3 args' => array( array( 1, 2, 3, ) ),
+ '4 args' => array( array( 1, 2, 3, 4, ) ),
+ '5 args' => array( array( 1, 2, 3, 4, 5, ) ),
+ '6 args' => array( array( 1, 2, 3, 4, 5, 6, ) ),
+ '7 args' => array( array( 1, 2, 3, 4, 5, 6, 7, ) ),
+ '8 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, ) ),
+ '9 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, ) ),
+ '10 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) ),
+ '11 args' => array( array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ) ),
+ );
+ }
}
class ObjectFactoryTestFixture {