Added callable reflection to `add_with_auto_arguments`

This allows the dependencies of the callable function to be automatically resolved out of the container too!
This commit is contained in:
Christopher Allford 2020-07-21 08:01:28 -07:00 committed by Nestor Soriano
parent 408295720c
commit 811ac747e6
2 changed files with 25 additions and 32 deletions

View File

@ -39,11 +39,11 @@ abstract class AbstractServiceProvider extends \League\Container\ServiceProvider
protected function add_with_auto_arguments( string $class_name, $concrete = null, bool $shared = false ) : DefinitionInterface {
$definition = new Definition( $class_name, $concrete );
$constructor = $this->verify_class_and_concrete( $class_name, $concrete );
$function = $this->reflect_class_or_callable( $class_name, $concrete );
if ( ! is_null( $constructor ) ) {
$constructor_arguments = $constructor->getParameters();
foreach ( $constructor_arguments as $argument ) {
if ( ! is_null( $function ) ) {
$arguments = $function->getParameters();
foreach ( $arguments as $argument ) {
if ( $argument->isDefaultValueAvailable() ) {
$default_value = $argument->getDefaultValue();
$definition->addArgument( new RawArgument( $default_value ) );
@ -72,10 +72,10 @@ abstract class AbstractServiceProvider extends \League\Container\ServiceProvider
* @param string $class_name The class name to check.
* @param mixed $concrete The concrete to check.
*
* @return \ReflectionMethod|null A ReflectionMethod representing the class constructor, if $concrete is null or a class name; null otherwise.
* @throws ContainerException Class has a private constructor, or can't reflect class, or the concrete is invalid.
* @return \ReflectionFunctionAbstract|null A reflection instance for the $class_name constructor or $concrete constructor or callable; null otherwise.
* @throws ContainerException Class has a private constructor, can't reflect class, or the concrete is invalid.
*/
private function verify_class_and_concrete( string $class_name, $concrete ) {
private function reflect_class_or_callable( string $class_name, $concrete ) {
if ( ! isset( $concrete ) || is_string( $concrete ) && class_exists( $concrete ) ) {
try {
$class = $concrete ?? $class_name;
@ -88,8 +88,12 @@ abstract class AbstractServiceProvider extends \League\Container\ServiceProvider
} catch ( \ReflectionException $ex ) {
throw new ContainerException( "AbstractServiceProvider::add_with_auto_arguments: error when reflecting class '$class': {$ex->getMessage()}" );
}
} elseif ( ! is_object( $concrete ) && ! is_callable( $concrete ) ) {
throw new ContainerException( 'AbstractServiceProvider::add_with_auto_arguments: concrete must be a valid class name, function name, object, or callable.' );
} elseif ( is_callable( $concrete ) ) {
try {
return new \ReflectionFunction( $concrete );
} catch ( \ReflectionException $ex ) {
throw new ContainerException( "AbstractServiceProvider::add_with_auto_arguments: error when reflecting callable: {$ex->getMessage()}" );
}
}
return null;

View File

@ -69,12 +69,13 @@ class AbstractServiceProviderTest extends \WC_Unit_Test_Case {
*/
public static function setUpBeforeClass() {
/**
* Return a new instance of DependencyClass.
* Return a new instance of ClassWithDependencies.
*
* @return DependencyClass The new instance.
* @param DependencyClass $dependency The dependency to inject.
* @return ClassWithDependencies The new instance.
*/
function get_new_dependency_class() {
return new DependencyClass();
function get_new_dependency_class( DependencyClass $dependency ) {
return new ClassWithDependencies( $dependency );
};
}
@ -88,21 +89,6 @@ class AbstractServiceProviderTest extends \WC_Unit_Test_Case {
$this->sut->add_with_auto_arguments( 'foobar' );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed concrete is neither an existing class name, an existing function, an object, or a callback.
*
* @testWith ["foobar"]
* [1234]
*
* @param mixed $concrete The concrete to use to register the class.
*/
public function test_add_with_auto_arguments_throws_on_non_class_passed_as_concrete( $concrete ) {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( 'AbstractServiceProvider::add_with_auto_arguments: concrete must be a valid class name, function name, object, or callable.' );
$this->sut->add_with_auto_arguments( get_class( $this ), $concrete );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed class has a private constructor.
*/
@ -199,26 +185,29 @@ class AbstractServiceProviderTest extends \WC_Unit_Test_Case {
* @testdox 'add_with_auto_arguments' should properly register the supplied class when a concrete that is a closure is passed.
*/
public function test_add_with_auto_arguments_works_as_expected_when_concrete_is_a_closure() {
$callable = function() {
return new DependencyClass();
$this->container->share( DependencyClass::class );
$callable = function( DependencyClass $dependency ) {
return new ClassWithDependencies( $dependency );
};
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, $callable );
$resolved = $this->container->get( ClassWithDependencies::class );
$this->assertInstanceOf( DependencyClass::class, $resolved );
$this->assertInstanceOf( ClassWithDependencies::class, $resolved );
}
/**
* @testdox 'add_with_auto_arguments' should properly register the supplied class when a concrete that is a function name is passed.
*/
public function test_add_with_auto_arguments_works_as_expected_when_concrete_is_a_function_name() {
$this->container->share( DependencyClass::class );
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, __NAMESPACE__ . '\get_new_dependency_class' );
$resolved = $this->container->get( ClassWithDependencies::class );
$this->assertInstanceOf( DependencyClass::class, $resolved );
$this->assertInstanceOf( ClassWithDependencies::class, $resolved );
}
}