From b15a06c496431054cd53d5fe6eae9ebaa627dc7a Mon Sep 17 00:00:00 2001 From: mwjames Date: Sun, 2 Oct 2016 17:18:30 +0200 Subject: [PATCH 1/2] Add CallbackContainerBuilder --- README.md | 58 +++- composer.json | 4 +- src/CallbackContainer.php | 6 +- src/CallbackContainerBuilder.php | 278 ++++++++++++++++++ src/CallbackContainerFactory.php | 49 +++ src/CallbackInstantiator.php | 65 ---- src/CallbackLoader.php | 24 -- src/ContainerBuilder.php | 75 +++++ src/DeferredCallbackLoader.php | 232 --------------- src/Exception/FileNotFoundException.php | 15 + .../InvalidParameterTypeException.php | 15 + .../ServiceCircularReferenceException.php | 24 ++ src/Exception/ServiceNotFoundException.php | 15 + .../ServiceTypeMismatchException.php | 26 ++ src/NullCallbackLoader.php | 63 ---- ...tantiator.php => NullContainerBuilder.php} | 11 +- src/ServicesManager.php | 40 +-- tests/bootstrap.php | 1 + .../Fixtures/FakeCallbackContainer.php | 48 +++ .../phpunit/Fixtures/fakeCallbackFromFile.php | 30 ++ ...t.php => CallbackContainerBuilderTest.php} | 116 ++++---- .../Unit/CallbackContainerFactoryTest.php | 65 ++++ tests/phpunit/Unit/FooCallbackContainer.php | 47 --- .../Unit/NullCallbackInstantiatorTest.php | 59 ---- ...rTest.php => NullContainerBuilderTest.php} | 16 +- tests/phpunit/Unit/ServicesManagerTest.php | 35 ++- 26 files changed, 804 insertions(+), 613 deletions(-) create mode 100644 src/CallbackContainerBuilder.php create mode 100644 src/CallbackContainerFactory.php delete mode 100644 src/CallbackInstantiator.php delete mode 100644 src/CallbackLoader.php create mode 100644 src/ContainerBuilder.php delete mode 100644 src/DeferredCallbackLoader.php create mode 100644 src/Exception/FileNotFoundException.php create mode 100644 src/Exception/InvalidParameterTypeException.php create mode 100644 src/Exception/ServiceCircularReferenceException.php create mode 100644 src/Exception/ServiceNotFoundException.php create mode 100644 src/Exception/ServiceTypeMismatchException.php delete mode 100644 src/NullCallbackLoader.php rename src/{NullCallbackInstantiator.php => NullContainerBuilder.php} (87%) create mode 100644 tests/phpunit/Fixtures/FakeCallbackContainer.php create mode 100644 tests/phpunit/Fixtures/fakeCallbackFromFile.php rename tests/phpunit/Unit/{DeferredCallbackLoaderTest.php => CallbackContainerBuilderTest.php} (71%) create mode 100644 tests/phpunit/Unit/CallbackContainerFactoryTest.php delete mode 100644 tests/phpunit/Unit/FooCallbackContainer.php delete mode 100644 tests/phpunit/Unit/NullCallbackInstantiatorTest.php rename tests/phpunit/Unit/{NullCallbackLoaderTest.php => NullContainerBuilderTest.php} (70%) diff --git a/README.md b/README.md index e545a51..9a241cb 100644 --- a/README.md +++ b/README.md @@ -32,38 +32,76 @@ the dependency to your [composer.json][composer]. ```php class FooCallbackContainer implements CallbackContainer { - public function register( CallbackInstantiator $callbackInstantiator ) { - $this->addCallbackHandlers( $callbackInstantiator); + public function register( ContainerBuilder $containerBuilder ) { + $this->addCallbackHandlers( $containerBuilder); } - private function addCallbackHandlers( $callbackInstantiator ) { + private function addCallbackHandlers( $containerBuilder ) { + + $containerBuilder->registerCallback( 'Foo', function( ContainerBuilder $containerBuilder, array $input ) { + $containerBuilder->registerExpectedReturnType( 'Foo', '\stdClass' ); - $callbackInstantiator->registerCallback( 'Foo', function( array $input ) { $stdClass = new \stdClass; $stdClass->input = $input; return $stdClass; } ); - - $callbackInstantiator->registerExpectedReturnType( 'Foo', '\stdClass' ); } } ``` ```php -$callbackInstantiator = new DeferredCallbackLoader(); +use Onoi\CallbackContainer\CallbackContainerFactory; + +$callbackContainerFactory = new CallbackContainerFactory(); +$containerBuilder = $callbackContainerFactory->newCallbackContainerBuilder(); -$callbackInstantiator->registerCallbackContainer( new FooCallbackContainer() ); -$instance = $callbackInstantiator->create( +$containerBuilder->registerCallbackContainer( new FooCallbackContainer() ); + +$instance = $containerBuilder->create( 'Foo', array( 'a', 'b' ) ); -$instance = $callbackInstantiator->singleton( +$instance = $containerBuilder->singleton( 'Foo', array( 'aa', 'bb' ) ); ``` +```php +return array( + + /** + * @return Closure + */ + 'SomeServiceFromFile' => function( $containerBuilder ) { + return new \stdClass; + }, + + /** + * @return Closure + */ + 'AnotherServiceFromFile' => function( $containerBuilder, $argument1, $argument2 ) { + $containerBuilder->registerExpectedReturnType( 'AnotherServiceFromFile', '\stdClass' ) + + $service = $containerBuilder->create( 'SomeServiceFromFile' ); + $service->argument1 = $argument1; + $service->argument2 = $argument2; + + return $service; + } +); +``` +```php +use Onoi\CallbackContainer\CallbackContainerFactory; +$callbackContainerFactory = new CallbackContainerFactory(); +$containerBuilder = $callbackContainerFactory->newCallbackContainerBuilder(); + +$containerBuilder->registerFromFile( __DIR__ . '/Foo.php' ); +$someServiceFromFile = $containerBuilder->create( 'SomeServiceFromFile' ); +$anotherServiceFromFile = $containerBuilder->create( 'AnotherServiceFromFile', 'Foo', 'Bar' ); + +``` If a callback handler is registered with an expected return type then any mismatch of a returning instance will throw a `RuntimeException`. diff --git a/composer.json b/composer.json index 95d7e2d..f335946 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { "name": "onoi/callback-container", "type": "library", - "description": "A simple callback container registry library", + "description": "A very simple callback container/builder library", "keywords": [ - "cache" + "container" ], "homepage": "https://github.com/onoi/callback-container", "license": "GPL-2.0+", diff --git a/src/CallbackContainer.php b/src/CallbackContainer.php index d497028..4c3b808 100644 --- a/src/CallbackContainer.php +++ b/src/CallbackContainer.php @@ -3,7 +3,7 @@ namespace Onoi\CallbackContainer; /** - * Interface describing a container to be registered with a CallbackLoader. + * Interface describing a container to be registered with a ContainerBuilder. * * @license GNU GPL v2+ * @since 1.0 @@ -15,8 +15,8 @@ interface CallbackContainer { /** * @since 1.0 * - * @param CallbackLoader $callbackLoader + * @param ContainerBuilder $containerBuilder */ - public function register( CallbackLoader $callbackLoader ); + public function register( ContainerBuilder $containerBuilder ); } diff --git a/src/CallbackContainerBuilder.php b/src/CallbackContainerBuilder.php new file mode 100644 index 0000000..7ac2dea --- /dev/null +++ b/src/CallbackContainerBuilder.php @@ -0,0 +1,278 @@ +registerCallbackContainer( $callbackContainer ); + } + } + + /** + * @since 1.0 + * + * @param CallbackContainer $callbackContainer + */ + public function registerCallbackContainer( CallbackContainer $callbackContainer ) { + $callbackContainer->register( $this ); + } + + /** + * @since 1.2 + * + * @param string $file + * @throws FileNotFoundException + */ + public function registerFromFile( $file ) { + + if ( !is_readable( ( $file = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $file ) ) ) ) { + throw new FileNotFoundException( "Cannot access or read {$file}" ); + } + + $defintions = require_once $file; + + foreach ( $defintions as $serviceName => $callback ) { + + if ( !is_callable( $callback ) ) { + continue; + } + + $this->registerCallback( $serviceName, $callback ); + } + } + + /** + * @since 1.0 + * + * {@inheritDoc} + */ + public function registerCallback( $serviceName, Closure $callback ) { + + if ( !is_string( $serviceName ) ) { + throw new InvalidParameterTypeException( "Expected a string" ); + } + + $this->registry[$serviceName] = $callback; + } + + /** + * If you are not running PHPUnit or for that matter any other testing + * environment then you are not suppose to use this function. + * + * @since 1.0 + * + * @param string $serviceName + * @param mixed $instance + */ + public function registerObject( $serviceName, $instance ) { + + if ( !is_string( $serviceName ) ) { + throw new InvalidParameterTypeException( "Expected a string" ); + } + + unset( $this->singletons[$serviceName] ); + + $this->registry[$serviceName] = $instance; + $this->singletons[$serviceName]['#'] = $instance; + } + + /** + * @since 1.0 + * + * {@inheritDoc} + */ + public function registerExpectedReturnType( $serviceName, $type ) { + + if ( !is_string( $serviceName ) || !is_string( $type ) ) { + throw new InvalidParameterTypeException( "Expected a string" ); + } + + $this->expectedReturnTypeByHandler[$serviceName] = $type; + } + + /** + * @see PSR-11 ContainerInterface + * @since 1.2 + * + * {@inheritDoc} + */ + public function has( $id ) { + return $this->isRegistered( $id ); + } + + /** + * @see PSR-11 ContainerInterface + * @since 1.2 + * + * {@inheritDoc} + */ + public function get( $id ) { + + // A call to the get method with a non-existing id SHOULD throw a Psr\Container\Exception\NotFoundExceptionInterface. + if ( !$this->has( $id ) ) { + // throw new NotFoundExceptionInterface( "Unknown {$id} handler or service" ); + } + + $parameters = func_get_args(); + array_shift( $parameters ); + + return $this->getReturnValueFromCallbackHandlerFor( $id, $parameters ); + } + + /** + * @since 1.2 + * + * {@inheritDoc} + */ + public function isRegistered( $serviceName ) { + return isset( $this->registry[$serviceName] ); + } + + /** + * @since 1.0 + * + * {@inheritDoc} + */ + public function create( $serviceName ) { + + $parameters = func_get_args(); + array_shift( $parameters ); + + return $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ); + } + + /** + * @since 1.0 + * + * {@inheritDoc} + */ + public function singleton( $serviceName ) { + + $parameters = func_get_args(); + array_shift( $parameters ); + + $fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#'; + + $instance = $this->getReturnValueFromSingletonFor( $serviceName, $fingerprint ); + + if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) ) { + return $instance; + } + + $instance = $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ); + + $this->singletons[$serviceName][$fingerprint] = function() use ( $instance ) { + static $singleton; + return $singleton = $singleton === null ? $instance : $singleton; + }; + + return $instance; + } + + /** + * @since 1.0 + * + * @param string $serviceName + */ + public function deregister( $serviceName ) { + unset( $this->registry[$serviceName] ); + unset( $this->singletons[$serviceName] ); + unset( $this->expectedReturnTypeByHandler[$serviceName] ); + } + + protected function addRecursiveMarkerFor( $serviceName ) { + + if ( !is_string( $serviceName ) ) { + throw new InvalidParameterTypeException( "Expected a string" ); + } + + if ( !isset( $this->recursiveMarker[$serviceName] ) ) { + $this->recursiveMarker[$serviceName] = 0; + } + + $this->recursiveMarker[$serviceName]++; + + if ( $this->recursiveMarker[$serviceName] > 1 ) { + throw new ServiceCircularReferenceException( $serviceName ); + } + } + + protected function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) { + + $instance = null; + $this->addRecursiveMarkerFor( $serviceName ); + + if ( !isset( $this->registry[$serviceName] ) ) { + throw new ServiceNotFoundException( "$serviceName is an unknown service." ); + } + + // Shift the ContainerBuilder to the first position in the parameter list + array_unshift( $parameters, $this ); + $service = $this->registry[$serviceName]; + + $instance = is_callable( $service ) ? call_user_func_array( $service, $parameters ) : $service; + $this->recursiveMarker[$serviceName]--; + + if ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) { + return $instance; + } + + throw new ServiceTypeMismatchException( $serviceName, $this->expectedReturnTypeByHandler[$serviceName], ( is_object( $instance ) ? get_class( $instance ) : $instance ) ); + } + + private function getReturnValueFromSingletonFor( $serviceName, $fingerprint ) { + + $instance = null; + $this->addRecursiveMarkerFor( $serviceName ); + + if ( isset( $this->singletons[$serviceName][$fingerprint] ) ) { + $service = $this->singletons[$serviceName][$fingerprint]; + $instance = is_callable( $service ) ? call_user_func( $service ) : $service; + } + + $this->recursiveMarker[$serviceName]--; + + return $instance; + } + +} diff --git a/src/CallbackContainerFactory.php b/src/CallbackContainerFactory.php new file mode 100644 index 0000000..0eff734 --- /dev/null +++ b/src/CallbackContainerFactory.php @@ -0,0 +1,49 @@ +newCallbackContainerBuilder(); + } + + return new ServicesManager( $containerBuilder ); + } + +} diff --git a/src/CallbackInstantiator.php b/src/CallbackInstantiator.php deleted file mode 100644 index cec2948..0000000 --- a/src/CallbackInstantiator.php +++ /dev/null @@ -1,65 +0,0 @@ -registerCallbackContainer( $callbackContainer ); - } - } - - /** - * @since 1.0 - * - * @param CallbackContainer $callbackContainer - */ - public function registerCallbackContainer( CallbackContainer $callbackContainer ) { - $callbackContainer->register( $this ); - } - - /** - * @since 1.0 - * - * {@inheritDoc} - */ - public function registerCallback( $handlerName, Closure $callback ) { - - if ( !is_string( $handlerName ) ) { - throw new InvalidArgumentException( "Expected a string" ); - } - - $this->registry[$handlerName] = $callback; - } - - /** - * If you are not running PHPUnit or for that matter any other testing - * environment then you are not suppose to use this function. - * - * @since 1.0 - * - * @param string $handlerName - * @param mixed $instance - */ - public function registerObject( $handlerName, $instance ) { - - if ( !is_string( $handlerName ) ) { - throw new InvalidArgumentException( "Expected a string" ); - } - - unset( $this->singletons[$handlerName] ); - - $this->registry[$handlerName] = $instance; - $this->singletons[$handlerName]['#'] = $instance; - } - - /** - * @since 1.0 - * - * {@inheritDoc} - */ - public function registerExpectedReturnType( $handlerName, $type ) { - - if ( !is_string( $handlerName ) || !is_string( $type ) ) { - throw new InvalidArgumentException( "Expected a string" ); - } - - $this->expectedReturnTypeByHandler[$handlerName] = $type; - } - - /** - * @since 1.2 - * - * {@inheritDoc} - */ - public function isRegistered( $handlerName ) { - return isset( $this->registry[$handlerName] ); - } - - /** - * @since 1.0 - * - * {@inheritDoc} - */ - public function create( $handlerName ) { - - $parameters = func_get_args(); - array_shift( $parameters ); - - return $this->getReturnValueFromCallbackHandlerFor( $handlerName, $parameters ); - } - - /** - * @since 1.0 - * @deprecated since 1.1 - * - * {@inheritDoc} - */ - public function load( $handlerName ) { - - $parameters = func_get_args(); - array_shift( $parameters ); - - return $this->getReturnValueFromCallbackHandlerFor( $handlerName, $parameters ); - } - - /** - * @since 1.0 - * - * {@inheritDoc} - */ - public function singleton( $handlerName ) { - - $parameters = func_get_args(); - array_shift( $parameters ); - - $fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#'; - - $instance = $this->getReturnValueFromSingletonFor( $handlerName, $fingerprint ); - - if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$handlerName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$handlerName] ) ) ) { - return $instance; - } - - $instance = $this->getReturnValueFromCallbackHandlerFor( $handlerName, $parameters ); - - $this->singletons[$handlerName][$fingerprint] = function() use ( $instance ) { - static $singleton; - return $singleton = $singleton === null ? $instance : $singleton; - }; - - return $instance; - } - - /** - * @since 1.0 - * - * @param string $handlerName - */ - public function deregister( $handlerName ) { - unset( $this->registry[$handlerName] ); - unset( $this->singletons[$handlerName] ); - unset( $this->expectedReturnTypeByHandler[$handlerName] ); - } - - private function addRecursiveMarkerFor( $handlerName ) { - - if ( !is_string( $handlerName ) ) { - throw new InvalidArgumentException( "Expected a string" ); - } - - if ( !isset( $this->recursiveMarker[$handlerName] ) ) { - $this->recursiveMarker[$handlerName] = 0; - } - - $this->recursiveMarker[$handlerName]++; - - if ( $this->recursiveMarker[$handlerName] > 1 ) { - throw new RuntimeException( "Oh boy, your execution chain for $handlerName caused a circular reference." ); - } - } - - private function getReturnValueFromCallbackHandlerFor( $handlerName, $parameters ) { - - $instance = null; - - $this->addRecursiveMarkerFor( $handlerName ); - - if ( isset( $this->registry[$handlerName] ) ) { - $instance = is_callable( $this->registry[$handlerName] ) ? call_user_func_array( $this->registry[$handlerName], $parameters ) : $this->registry[$handlerName]; - } - - $this->recursiveMarker[$handlerName]--; - - if ( !isset( $this->expectedReturnTypeByHandler[$handlerName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$handlerName] ) ) { - return $instance; - } - - throw new RuntimeException( "Expected " . $this->expectedReturnTypeByHandler[$handlerName] . " type for {$handlerName} could not be match to " . ( is_object( $instance ) ? get_class( $instance ) : $instance ) ); - } - - private function getReturnValueFromSingletonFor( $handlerName, $fingerprint ) { - - $instance = null; - - $this->addRecursiveMarkerFor( $handlerName ); - - if ( isset( $this->singletons[$handlerName][$fingerprint] ) ) { - $instance = is_callable( $this->singletons[$handlerName][$fingerprint] ) ? call_user_func( $this->singletons[$handlerName][$fingerprint] ) : $this->singletons[$handlerName][$fingerprint]; - } - - $this->recursiveMarker[$handlerName]--; - - return $instance; - } - -} diff --git a/src/Exception/FileNotFoundException.php b/src/Exception/FileNotFoundException.php new file mode 100644 index 0000000..dd37c9a --- /dev/null +++ b/src/Exception/FileNotFoundException.php @@ -0,0 +1,15 @@ +callbackInstantiator = $callbackInstantiator; - } - - /** - * @since 1.2 - * - * @return ServicesManager - */ - public static function newManager() { - return new self( new DeferredCallbackLoader() ); + public function __construct( ContainerBuilder $containerBuilder ) { + $this->containerBuilder = $containerBuilder; } /** @@ -53,10 +44,10 @@ public function add( $serviceName, $service, $type = null ) { }; } - $this->callbackInstantiator->registerCallback( $serviceName, $service ); + $this->containerBuilder->registerCallback( $serviceName, $service ); if ( $type !== null ) { - $this->callbackInstantiator->registerExpectedReturnType( $serviceName, $type ); + $this->containerBuilder->registerExpectedReturnType( $serviceName, $type ); } } @@ -68,7 +59,7 @@ public function add( $serviceName, $service, $type = null ) { * @return boolean */ public function has( $serviceName ) { - return $this->callbackInstantiator->isRegistered( $serviceName ); + return $this->containerBuilder->isRegistered( $serviceName ); } /** @@ -77,14 +68,15 @@ public function has( $serviceName ) { * @param string $serviceName * * @return mixed + * @throws ServiceNotFoundException */ public function getBy( $serviceName ) { - if ( !$this->callbackInstantiator->isRegistered( $serviceName ) ) { - throw new RuntimeException( "$serviceName is an unknown service." ); + if ( !$this->containerBuilder->isRegistered( $serviceName ) ) { + throw new ServiceNotFoundException( "$serviceName is an unknown service." ); } - return $this->callbackInstantiator->singleton( $serviceName ); + return $this->containerBuilder->singleton( $serviceName ); } /** @@ -93,7 +85,7 @@ public function getBy( $serviceName ) { * @param string $serviceName */ public function removeBy( $serviceName ) { - $this->callbackInstantiator->deregister( $serviceName ); + $this->containerBuilder->deregister( $serviceName ); } /** @@ -103,7 +95,7 @@ public function removeBy( $serviceName ) { * @param mixed $service */ public function overrideWith( $serviceName, $service ) { - $this->callbackInstantiator->registerObject( $serviceName, $service ); + $this->containerBuilder->registerObject( $serviceName, $service ); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6e0d44c..1b0ca2e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -24,3 +24,4 @@ $autoLoader = require $path; $autoLoader->addPsr4( 'Onoi\\CallbackContainer\\Tests\\', __DIR__ . '/phpunit/Unit' ); +$autoLoader->addPsr4( 'Onoi\\CallbackContainer\\Fixtures\\', __DIR__ . '/phpunit/Fixtures' ); diff --git a/tests/phpunit/Fixtures/FakeCallbackContainer.php b/tests/phpunit/Fixtures/FakeCallbackContainer.php new file mode 100644 index 0000000..0e2e41f --- /dev/null +++ b/tests/phpunit/Fixtures/FakeCallbackContainer.php @@ -0,0 +1,48 @@ +addCallbackHandlers( $containerBuilder ); + } + + private function addCallbackHandlers( $containerBuilder ) { + + $containerBuilder->registerCallback( 'Foo', function( $containerBuilder ) { + return new \stdClass; + } ); + + $containerBuilder->registerExpectedReturnType( 'Foo', '\stdClass' ); + + $containerBuilder->registerCallback( 'FooWithArgument', function( $containerBuilder, $argument ) { + $containerBuilder->registerExpectedReturnType( 'FooWithArgument', '\stdClass' ); + + $stdClass = new \stdClass; + $stdClass->argument = $argument; + + return $stdClass; + } ); + + $containerBuilder->registerCallback( 'FooWithNullArgument', function( $containerBuilder, $argument = null ) { + $containerBuilder->registerExpectedReturnType( 'FooWithNullArgument', '\stdClass' ); + + $stdClass = new \stdClass; + $stdClass->argument = $argument; + $stdClass->argumentWithArgument = $containerBuilder->create( 'FooWithArgument', $argument ); + + return $stdClass; + } ); + } + +} diff --git a/tests/phpunit/Fixtures/fakeCallbackFromFile.php b/tests/phpunit/Fixtures/fakeCallbackFromFile.php new file mode 100644 index 0000000..1275e0d --- /dev/null +++ b/tests/phpunit/Fixtures/fakeCallbackFromFile.php @@ -0,0 +1,30 @@ + function( $containerBuilder ) { ... } + * ) + * + * @license GNU GPL v2+ + * @since 1.2 + * + * @author mwjames + */ +return array( + + /** + * @return Closure + */ + 'SomeStdClassFromFile' => function( $containerBuilder ) { + return new \stdClass; + }, + + /** + * @return string + */ + 'InvalidDefinition' => 'Foo' +); \ No newline at end of file diff --git a/tests/phpunit/Unit/DeferredCallbackLoaderTest.php b/tests/phpunit/Unit/CallbackContainerBuilderTest.php similarity index 71% rename from tests/phpunit/Unit/DeferredCallbackLoaderTest.php rename to tests/phpunit/Unit/CallbackContainerBuilderTest.php index eb880ef..6fca13a 100644 --- a/tests/phpunit/Unit/DeferredCallbackLoaderTest.php +++ b/tests/phpunit/Unit/CallbackContainerBuilderTest.php @@ -2,10 +2,11 @@ namespace Onoi\CallbackContainer\Tests; -use Onoi\CallbackContainer\DeferredCallbackLoader; +use Onoi\CallbackContainer\CallbackContainerBuilder; +use Onoi\CallbackContainer\Fixtures\FakeCallbackContainer; /** - * @covers \Onoi\CallbackContainer\DeferredCallbackLoader + * @covers \Onoi\CallbackContainer\CallbackContainerBuilder * @group onoi-callback-container * * @license GNU GPL v2+ @@ -13,13 +14,13 @@ * * @author mwjames */ -class DeferredCallbackLoaderTest extends \PHPUnit_Framework_TestCase { +class CallbackContainerBuilderTest extends \PHPUnit_Framework_TestCase { public function testCanConstruct() { $this->assertInstanceOf( - '\Onoi\CallbackContainer\DeferredCallbackLoader', - new DeferredCallbackLoader() + '\Onoi\CallbackContainer\CallbackContainerBuilder', + new CallbackContainerBuilder() ); } @@ -33,14 +34,14 @@ public function testCanConstructWithCallbackContainer() { ->method( 'register' ); $this->assertInstanceOf( - '\Onoi\CallbackContainer\DeferredCallbackLoader', - new DeferredCallbackLoader( $callbackContainer ) + '\Onoi\CallbackContainer\CallbackContainerBuilder', + new CallbackContainerBuilder( $callbackContainer ) ); } public function testRegisterCallback() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return new \stdClass; @@ -63,7 +64,7 @@ public function testRegisterCallback() { public function testDeregisterCallback() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return 'abc'; @@ -75,15 +76,11 @@ public function testDeregisterCallback() { ); $instance->deregister( 'Foo' ); - - $this->assertNull( - $instance->singleton( 'Foo' ) - ); } public function testLoadCallbackHandlerWithExpectedReturnType() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return new \stdClass; @@ -99,7 +96,7 @@ public function testLoadCallbackHandlerWithExpectedReturnType() { public function testLoadCallbackHandlerWithoutExpectedReturnType() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return 'abc'; @@ -109,17 +106,12 @@ public function testLoadCallbackHandlerWithoutExpectedReturnType() { 'abc', $instance->create( 'Foo' ) ); - - $this->assertEquals( - 'abc', - $instance->load( 'Foo' ) - ); } public function testRegisterCallbackContainer() { - $instance = new DeferredCallbackLoader(); - $instance->registerCallbackContainer( new FooCallbackContainer() ); + $instance = new CallbackContainerBuilder(); + $instance->registerCallbackContainer( new FakeCallbackContainer() ); $this->assertEquals( new \stdClass, @@ -132,11 +124,22 @@ public function testRegisterCallbackContainer() { ); } + public function testRegisterFromFile() { + + $instance = new CallbackContainerBuilder(); + $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' ); + + $this->assertEquals( + new \stdClass, + $instance->create( 'SomeStdClassFromFile' ) + ); + } + public function testRegisterObject() { $expected = new \stdClass; - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerExpectedReturnType( 'Foo', '\stdClass' ); $instance->registerObject( 'Foo', $expected ); @@ -158,7 +161,7 @@ public function testInjectInstanceForExistingRegisteredCallbackHandler() { ->disableOriginalConstructor() ->getMock(); - $instance = new DeferredCallbackLoader( new FooCallbackContainer() ); + $instance = new CallbackContainerBuilder( new FakeCallbackContainer() ); $instance->singleton( 'Foo' ); $instance->registerObject( 'Foo', $stdClass ); @@ -180,8 +183,8 @@ public function testOverrideSingletonInstanceOnRegisteredCallbackHandlerWithArgu ->disableOriginalConstructor() ->getMock(); - $instance = new DeferredCallbackLoader( - new FooCallbackContainer() + $instance = new CallbackContainerBuilder( + new FakeCallbackContainer() ); $instance->singleton( 'FooWithNullArgument', $argument ); @@ -207,9 +210,9 @@ public function testOverrideSingletonInstanceOnRegisteredCallbackHandlerWithArgu public function testLoadParameterizedCallbackHandler() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); - $instance->registerCallback( 'Foo', function( $a, $b, $c ) { + $instance->registerCallback( 'Foo', function( $containerBuilder, $a, $b, $c ) { $stdClass = new \stdClass; $stdClass->a = $a; $stdClass->b = $b; @@ -236,9 +239,9 @@ public function testLoadParameterizedCallbackHandler() { public function testRecursiveBuildToLoadParameterizedCallbackHandler() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); - $instance->registerCallback( 'Foo', function( $a, $b = null, $c ) { + $instance->registerCallback( 'Foo', function( $containerBuilder, $a, $b = null, $c ) { $stdClass = new \stdClass; $stdClass->a = $a; $stdClass->c = $c; @@ -248,7 +251,7 @@ public function testRecursiveBuildToLoadParameterizedCallbackHandler() { $instance->registerExpectedReturnType( 'Foo', '\stdClass' ); - $instance->registerCallback( 'Bar', function( $a, $b, $c ) use( $instance ) { + $instance->registerCallback( 'Bar', function( $containerBuilder, $a, $b, $c ) use( $instance ) { return $instance->create( 'Foo', $a, $b, $c ); } ); @@ -265,7 +268,7 @@ public function testRecursiveBuildToLoadParameterizedCallbackHandler() { public function testSingleton() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return new \stdClass; @@ -283,9 +286,9 @@ public function testSingleton() { public function testFingerprintedParameterizedSingletonCallbackHandler() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); - $instance->registerCallback( 'Foo', function( $a, array $b ) { + $instance->registerCallback( 'Foo', function( $containerBuilder, $a, array $b ) { $stdClass = new \stdClass; $stdClass->a = $a; $stdClass->b = $b; @@ -306,27 +309,25 @@ public function testFingerprintedParameterizedSingletonCallbackHandler() { ); } - public function testUnregisteredCallbackHandlerIsToReturnNull() { + public function testUnregisteredServiceOnCreateThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); - $this->assertNull( - $instance->create( 'Foo' ) - ); + $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' ); + $instance->create( 'Foo' ); } - public function testUnregisteredCallbackHandlerForSingletonIsToReturnNull() { + public function testUnregisteredServiceOnSingletonThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); - $this->assertNull( - $instance->singleton( 'Foo' ) - ); + $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' ); + $instance->singleton( 'Foo' ); } public function testTryToLoadCallbackHandlerWithTypeMismatchThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return new \stdClass; @@ -340,7 +341,7 @@ public function testTryToLoadCallbackHandlerWithTypeMismatchThrowsException() { public function testTryToUseInvalidNameForCallbackHandlerOnLoadThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->create( new \stdClass ); @@ -348,7 +349,7 @@ public function testTryToUseInvalidNameForCallbackHandlerOnLoadThrowsException() public function testTryToUseInvalidNameForCallbackHandlerOnSingletonThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->singleton( new \stdClass ); @@ -356,7 +357,7 @@ public function testTryToUseInvalidNameForCallbackHandlerOnSingletonThrowsExcept public function testTryToLoadCallbackHandlerWithCircularReferenceThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'RuntimeException' ); @@ -370,21 +371,20 @@ public function testTryToLoadCallbackHandlerWithCircularReferenceThrowsException public function testTryToLoadSingletonCallbackHandlerWithCircularReferenceThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'RuntimeException' ); - $instance->registerCallback( '\stdClass', function() use ( $instance ) { + $instance->registerCallback( 'Foo', function() use ( $instance ) { return $instance->singleton( 'Foo' ); } ); - $instance->registerExpectedReturnType( 'Foo', '\stdClass' ); $instance->singleton( 'Foo' ); } public function testTryToUseInvalidNameOnCallbackHandlerRegistrationThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->registerCallback( new \stdClass, function() { @@ -394,7 +394,7 @@ public function testTryToUseInvalidNameOnCallbackHandlerRegistrationThrowsExcept public function testTryToUseInvalidNameOnObjectRegistrationThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->registerObject( new \stdClass, new \stdClass ); @@ -402,10 +402,18 @@ public function testTryToUseInvalidNameOnObjectRegistrationThrowsException() { public function testTryToUseInvalidNameOnTypeRegistrationThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->registerExpectedReturnType( new \stdClass, 'Bar' ); } + public function testTryToRegisterFromInvalidFileThrowsException() { + + $instance = new CallbackContainerBuilder(); + + $this->setExpectedException( 'RuntimeException' ); + $instance->registerFromFile( 'Foo' ); + } + } diff --git a/tests/phpunit/Unit/CallbackContainerFactoryTest.php b/tests/phpunit/Unit/CallbackContainerFactoryTest.php new file mode 100644 index 0000000..67efa64 --- /dev/null +++ b/tests/phpunit/Unit/CallbackContainerFactoryTest.php @@ -0,0 +1,65 @@ +assertInstanceOf( + '\Onoi\CallbackContainer\CallbackContainerFactory', + new CallbackContainerFactory() + ); + } + + public function testCanConstructCallbackContainerBuilder() { + + $instance = new CallbackContainerFactory(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\CallbackContainerBuilder', + $instance->newCallbackContainerBuilder() + ); + } + + public function testCanConstructNullContainerBuilder() { + + $instance = new CallbackContainerFactory(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\NullContainerBuilder', + $instance->newNullContainerBuilder() + ); + } + + public function testCanConstructServicesManager() { + + $instance = new CallbackContainerFactory(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\ServicesManager', + $instance->newServicesManager() + ); + + $containerBuilder = $this->getMockBuilder( '\Onoi\CallbackContainer\ContainerBuilder' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\ServicesManager', + $instance->newServicesManager( $containerBuilder ) + ); + } + +} diff --git a/tests/phpunit/Unit/FooCallbackContainer.php b/tests/phpunit/Unit/FooCallbackContainer.php deleted file mode 100644 index 846636f..0000000 --- a/tests/phpunit/Unit/FooCallbackContainer.php +++ /dev/null @@ -1,47 +0,0 @@ -addCallbackHandlers( $callbackLoader); - } - - private function addCallbackHandlers( $callbackLoader ) { - - $callbackLoader->registerCallback( 'Foo', function() { - return new \stdClass; - } ); - - $callbackLoader->registerExpectedReturnType( 'Foo', '\stdClass' ); - - $callbackLoader->registerCallback( 'FooWithArgument', function( $argument ) use( $callbackLoader ) { - $callbackLoader->registerExpectedReturnType( 'FooWithArgument', '\stdClass' ); - - $stdClass = new \stdClass; - $stdClass->argument = $argument; - - return $stdClass; - } ); - - $callbackLoader->registerCallback( 'FooWithNullArgument', function( $argument = null ) use( $callbackLoader ) { - $callbackLoader->registerExpectedReturnType( 'FooWithNullArgument', '\stdClass' ); - - $stdClass = new \stdClass; - $stdClass->argument = $argument; - - return $stdClass; - } ); - } - -} diff --git a/tests/phpunit/Unit/NullCallbackInstantiatorTest.php b/tests/phpunit/Unit/NullCallbackInstantiatorTest.php deleted file mode 100644 index 2a864f6..0000000 --- a/tests/phpunit/Unit/NullCallbackInstantiatorTest.php +++ /dev/null @@ -1,59 +0,0 @@ -assertInstanceOf( - '\Onoi\CallbackContainer\NullCallbackInstantiator', - new NullCallbackInstantiator() - ); - } - - public function testInterfaceMethods() { - - $instance = new NullCallbackInstantiator(); - - $this->assertNull( - $instance->create( 'Foo' ) - ); - - $this->assertNull( - $instance->singleton( 'Foo' ) - ); - - $this->assertFalse( - $instance->isRegistered( 'Foo' ) - ); - - $this->assertNull( - $instance->registerExpectedReturnType( 'Foo', 'bar' ) - ); - - $this->assertNull( - $instance->registerCallback( 'Foo', function() {} ) - ); - - $callbackContainer = $this->getMockBuilder( '\Onoi\CallbackContainer\CallbackContainer' ) - ->disableOriginalConstructor() - ->getMock(); - - $this->assertNull( - $instance->registerCallbackContainer( $callbackContainer ) - ); - } - -} diff --git a/tests/phpunit/Unit/NullCallbackLoaderTest.php b/tests/phpunit/Unit/NullContainerBuilderTest.php similarity index 70% rename from tests/phpunit/Unit/NullCallbackLoaderTest.php rename to tests/phpunit/Unit/NullContainerBuilderTest.php index a8362ed..1fd1686 100644 --- a/tests/phpunit/Unit/NullCallbackLoaderTest.php +++ b/tests/phpunit/Unit/NullContainerBuilderTest.php @@ -2,10 +2,10 @@ namespace Onoi\CallbackContainer\Tests; -use Onoi\CallbackContainer\NullCallbackLoader; +use Onoi\CallbackContainer\NullContainerBuilder; /** - * @covers \Onoi\CallbackContainer\NullCallbackLoader + * @covers \Onoi\CallbackContainer\NullContainerBuilder * @group onoi-callback-container * * @license GNU GPL v2+ @@ -13,23 +13,19 @@ * * @author mwjames */ -class NullCallbackLoaderTest extends \PHPUnit_Framework_TestCase { +class NullContainerBuilderTest extends \PHPUnit_Framework_TestCase { public function testCanConstruct() { $this->assertInstanceOf( - '\Onoi\CallbackContainer\NullCallbackLoader', - new NullCallbackLoader() + '\Onoi\CallbackContainer\NullContainerBuilder', + new NullContainerBuilder() ); } public function testInterfaceMethods() { - $instance = new NullCallbackLoader(); - - $this->assertNull( - $instance->load( 'Foo' ) - ); + $instance = new NullContainerBuilder(); $this->assertNull( $instance->create( 'Foo' ) diff --git a/tests/phpunit/Unit/ServicesManagerTest.php b/tests/phpunit/Unit/ServicesManagerTest.php index 8a0b8d2..2c60f5f 100644 --- a/tests/phpunit/Unit/ServicesManagerTest.php +++ b/tests/phpunit/Unit/ServicesManagerTest.php @@ -3,6 +3,7 @@ namespace Onoi\CallbackContainer\Tests; use Onoi\CallbackContainer\ServicesManager; +use Onoi\CallbackContainer\CallbackContainerFactory; /** * @covers \Onoi\CallbackContainer\ServicesManager @@ -15,26 +16,30 @@ */ class ServicesManagerTest extends \PHPUnit_Framework_TestCase { + private $servicesManager; + + protected function setUp() { + parent::setUp(); + + $callbackContainerFactory = new CallbackContainerFactory(); + $this->servicesManager = $callbackContainerFactory->newServicesManager(); + } + public function testCanConstruct() { - $callbackInstantiator = $this->getMockBuilder( '\Onoi\CallbackContainer\CallbackInstantiator' ) + $containerBuilder = $this->getMockBuilder( '\Onoi\CallbackContainer\ContainerBuilder' ) ->disableOriginalConstructor() ->getMock(); $this->assertInstanceOf( '\Onoi\CallbackContainer\ServicesManager', - new ServicesManager( $callbackInstantiator ) - ); - - $this->assertInstanceOf( - '\Onoi\CallbackContainer\ServicesManager', - ServicesManager::newManager() + new ServicesManager( $containerBuilder ) ); } public function testAddServiceWithScalarType() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', 123 ); $this->assertTrue( @@ -49,7 +54,7 @@ public function testAddServiceWithScalarType() { public function testAddServiceWithObjectType() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this ); $this->assertTrue( @@ -64,7 +69,7 @@ public function testAddServiceWithObjectType() { public function testRemoveService() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this ); $this->assertTrue( @@ -80,7 +85,7 @@ public function testRemoveService() { public function testOverrideUntypedService() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this ); $this->assertTrue( @@ -97,7 +102,7 @@ public function testOverrideUntypedService() { public function testTryToOverrideTypedServiceWithIncompatibleTypeThrowsException() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this, '\PHPUnit_Framework_TestCase' ); $this->assertTrue( @@ -106,15 +111,15 @@ public function testTryToOverrideTypedServiceWithIncompatibleTypeThrowsException $instance->overrideWith( 'Foo', 123 ); - $this->setExpectedException( 'RuntimeException' ); + $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceTypeMismatchException' ); $instance->getBy( 'Foo' ); } public function testTryToAccessToUnknownServiceThrowsException() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; - $this->setExpectedException( 'RuntimeException' ); + $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' ); $instance->getBy( 'Foo' ); } From 068f0f76acfcd277dc03c91a20b84ff82e3ed52d Mon Sep 17 00:00:00 2001 From: mwjames Date: Sat, 26 Nov 2016 05:50:49 +0100 Subject: [PATCH 2/2] Add CallbackContainerBuilder, LoggableContainerBuilder --- .travis.yml | 2 +- README.md | 43 +++- composer.json | 5 +- docs/example.loggable.output.md | 49 ++++ src/BacktraceSniffer.php | 82 ++++++ src/CallFuncMemorySniffer.php | 97 +++++++ src/CallbackContainer.php | 2 +- src/CallbackContainerBuilder.php | 102 +++----- src/CallbackContainerFactory.php | 38 +++ src/ContainerBuilder.php | 30 +-- src/ContainerRegistry.php | 56 +++++ src/LoggableContainerBuilder.php | 236 ++++++++++++++++++ src/NullContainerBuilder.php | 23 +- src/ServicesManager.php | 17 +- .../phpunit/Fixtures/fakeCallbackFromFile.php | 33 +++ tests/phpunit/Unit/BacktraceSnifferTest.php | 74 ++++++ .../Unit/CallFuncMemorySnifferTest.php | 74 ++++++ .../Unit/CallbackContainerBuilderTest.php | 51 +++- .../Unit/CallbackContainerFactoryTest.php | 38 +++ .../Unit/LoggableContainerBuilderTest.php | 108 ++++++++ .../phpunit/Unit/NullContainerBuilderTest.php | 8 + tests/phpunit/Unit/ServicesManagerTest.php | 12 +- tests/phpunit/Unit/SpyLogger.php | 40 +++ 23 files changed, 1088 insertions(+), 132 deletions(-) create mode 100644 docs/example.loggable.output.md create mode 100644 src/BacktraceSniffer.php create mode 100644 src/CallFuncMemorySniffer.php create mode 100644 src/ContainerRegistry.php create mode 100644 src/LoggableContainerBuilder.php create mode 100644 tests/phpunit/Unit/BacktraceSnifferTest.php create mode 100644 tests/phpunit/Unit/CallFuncMemorySnifferTest.php create mode 100644 tests/phpunit/Unit/LoggableContainerBuilderTest.php create mode 100644 tests/phpunit/Unit/SpyLogger.php diff --git a/.travis.yml b/.travis.yml index 8f5d818..9703efa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ matrix: - env: TYPE=coverage php: 5.6 - env: TYPE=UNIT; - php: 5.3 + php: 5.5 - env: TYPE=UNIT; php: hhvm - env: TYPE=UNIT; diff --git a/README.md b/README.md index 9a241cb..056417f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ [![Dependency Status](https://www.versioneye.com/php/onoi:callback-container/badge.png)](https://www.versioneye.com/php/onoi:callback-container) A simple object instantiator to lazy load registered callback handlers. Part of the -code base has been extracted from [Semantic MediaWiki][smw] and is now being deployed as independent library. +code base has been extracted from [Semantic MediaWiki][smw] and is now being +deployed as independent library. ## Requirements @@ -22,13 +23,15 @@ the dependency to your [composer.json][composer]. ```json { "require": { - "onoi/callback-container": "~1.1" + "onoi/callback-container": "~2.0" } } ``` ## Usage +### CallbackContainerBuilder and CallbackContainer + ```php class FooCallbackContainer implements CallbackContainer { @@ -105,6 +108,34 @@ $anotherServiceFromFile = $containerBuilder->create( 'AnotherServiceFromFile', ' If a callback handler is registered with an expected return type then any mismatch of a returning instance will throw a `RuntimeException`. +### LoggableContainerBuilder + +`LoggableContainerBuilder` is provided as facade to enable logging and sniffing of +registered callback instances. + +```php +use Onoi\CallbackContainer\CallbackContainerFactory; + +$callbackContainerFactory = new CallbackContainerFactory(); + +$containerBuilder = $callbackContainerFactory->newLoggableContainerBuilder( + $callbackContainerFactory->newCallbackContainerBuilder(), + $callbackContainerFactory->newBacktraceSniffer( 10 ), + $callbackContainerFactory->newCallFuncMemorySniffer() +); + +$containerBuilder->registerCallbackContainer( + new FooCallbackContainer() +); + +$containerBuilder->setLogger( + $containerBuilder->singleton( 'SomeLogger' ) +); + +``` + +If a `Psr\Log\LoggerInterface` is set then a similar [output](docs/example.loggable.output.md) will be produced. + ## Contribution and support If you want to contribute work to the project please subscribe to the @@ -122,6 +153,12 @@ The library provides unit tests that covers the core-functionality normally run ## Release notes +- 2.0.0 (2016-11-26) + - Added `CallbackContainerFactory` + - Added `LoggableContainerBuilder` + - Added `CallbackContainerBuilder::registerFromFile` to allow loading callback + definitions from a file + - 1.1.0 (2016-09-07) - Added `ServicesManager` as convenience class to manage on-the-fly services independent of an active `DeferredCallbackLoader` instance @@ -131,7 +168,7 @@ The library provides unit tests that covers the core-functionality normally run - Deprecated the `CallbackLoader` interface in favour of the `CallbackInstantiator` interface - Deprecated the `NullCallbackLoader` class in favour of the `NullCallbackInstantiator` class -- 1.0.0 Initial release (2015-09-08) +- 1.0.0 (2015-09-08) - Added the `CallbackContainer` and `CallbackLoader` interface - Added the `DeferredCallbackLoader` and `NullCallbackLoader` implementation diff --git a/composer.json b/composer.json index f335946..08c42eb 100644 --- a/composer.json +++ b/composer.json @@ -14,11 +14,12 @@ } ], "require": { - "php": ">=5.3.2" + "php": ">=5.5", + "psr/log": "~1.0" }, "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { diff --git a/docs/example.loggable.output.md b/docs/example.loggable.output.md new file mode 100644 index 0000000..c11cf63 --- /dev/null +++ b/docs/example.loggable.output.md @@ -0,0 +1,49 @@ +An example output produced by the `LoggableContainerBuilder`. + +``` + "NamespaceExaminer": { + "prototype": 3, + "prototype-backtrace": [ + [ + "require", + "require_once", + "call_user_func", + "{closure}", + "SemanticCite::onExtensionFunction", + "SCI\\HookRegistry::__construct", + "SCI\\HookRegistry::addCallbackHandlers", + "SMW\\ApplicationFactory::getNamespaceExaminer", + "Onoi\\CallbackContainer\\LoggableContainerBuilder::create" + ], + [ + "OutputPage::addParserOutput", + "OutputPage::addParserOutputMetadata", + "Hooks::run", + "call_user_func_array", + "SMW\\MediaWiki\\Hooks\\HookRegistry::SMW\\MediaWiki\\Hooks\\{closure}", + "SMW\\MediaWiki\\Hooks\\OutputPageParserOutput::process", + "SMW\\MediaWiki\\Hooks\\OutputPageParserOutput::canPerformUpdate", + "SMW\\MediaWiki\\Hooks\\OutputPageParserOutput::isSemanticEnabledNamespace", + "SMW\\ApplicationFactory::getNamespaceExaminer", + "Onoi\\CallbackContainer\\LoggableContainerBuilder::create" + ], + [ + "SkinTemplate::outputPage", + "SkinTemplate::prepareQuickTemplate", + "Hooks::run", + "call_user_func_array", + "SBL\\HookRegistry::SBL\\{closure}", + "SBL\\SkinTemplateOutputModifier::modifyOutput", + "SBL\\SkinTemplateOutputModifier::canModifyOutput", + "SBL\\SkinTemplateOutputModifier::isEnabled", + "SMW\\ApplicationFactory::getNamespaceExaminer", + "Onoi\\CallbackContainer\\LoggableContainerBuilder::create" + ] + ], + "singleton": 0, + "singleton-memory": [], + "singleton-time": [], + "prototype-memory-median": 1432, + "prototype-time-median": 0.00018199284871419 + }, +``` \ No newline at end of file diff --git a/src/BacktraceSniffer.php b/src/BacktraceSniffer.php new file mode 100644 index 0000000..628c748 --- /dev/null +++ b/src/BacktraceSniffer.php @@ -0,0 +1,82 @@ +depth = $depth; + } + + /** + * @see MediaWiki::wfGetCaller + * @since 2.0 + * + * @return $string + */ + public function getCaller( $depth = null ) { + + $depth = $depth === null ? $this->depth : $depth; + $backtrace = $this->getBackTrace( $depth + 1 ); + + if ( isset( $backtrace[$depth] ) ) { + return $this->doFormatStackFrame( $backtrace[$depth] ); + } + + return 'unknown'; + } + + /** + * @see MediaWiki::wfGetCallers + * @since 2.0 + * + * @return array + */ + public function getCallers( $depth = null ) { + + $depth = $depth === null ? $this->depth : $depth; + $backtrace = array_reverse( $this->getBackTrace() ); + + + if ( !$depth || $depth > count( $backtrace ) - 1 ) { + $depth = count( $backtrace ) - 1; + } + + $backtrace = array_slice( $backtrace, -$depth - 1, $depth ); + + return array_map( array( $this, 'doFormatStackFrame' ), $backtrace ); + } + + private function doFormatStackFrame( $frame ) { + return isset( $frame['class'] ) ? $frame['class'] . '::' . $frame['function'] : $frame['function']; + } + + private function getBackTrace( $limit = 0 ) { + static $disabled = null; + + if ( $disabled || ( $disabled = !function_exists( 'debug_backtrace' ) ) === true ) { + return array(); + } + + if ( $limit && version_compare( PHP_VERSION, '5.4.0', '>=' ) ) { + return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 ); + } else { + return array_slice( debug_backtrace(), 1 ); + } + } + +} diff --git a/src/CallFuncMemorySniffer.php b/src/CallFuncMemorySniffer.php new file mode 100644 index 0000000..b2f8413 --- /dev/null +++ b/src/CallFuncMemorySniffer.php @@ -0,0 +1,97 @@ + self::$max ? self::$memory : self::$max; + self::$memory = memory_get_usage(); + } + + /** + * @since 2.0 + * + * @param Closure|callable $func + * @param array|null $args + * + * @return mixed + * @throws RuntimeException + */ + public function call( $func, $args = null ) { + + if ( !is_callable( $func ) ) { + throw new RuntimeException( "Function is not callable" ); + } + + // HHVM ... Fatal error: Call to undefined function + if ( !function_exists( 'register_tick_function' ) ) { + return is_array( $args ) ? call_user_func_array( $func, $args ): call_user_func( $func ); + } + + declare( ticks=1 ); + + self::$memory = memory_get_usage(); + self::$max = 0; + + register_tick_function( + 'call_user_func_array', + array( '\Onoi\CallbackContainer\CallFuncMemorySniffer', 'memoryTick' ), + array() + ); + + $this->time = microtime( true ); + $result = is_array( $args ) ? call_user_func_array( $func, $args ): call_user_func( $func ); + $this->time = microtime( true ) - $this->time; + + unregister_tick_function( 'call_user_func_array' ); + + return $result; + } + + /** + * @since 2.0 + * + * @return integer + */ + public function getMemoryUsed() { + return self::$max; + } + + /** + * @since 2.0 + * + * @return float + */ + public function getTimeUsed() { + return $this->time; + } + +} diff --git a/src/CallbackContainer.php b/src/CallbackContainer.php index 4c3b808..15fb92d 100644 --- a/src/CallbackContainer.php +++ b/src/CallbackContainer.php @@ -15,7 +15,7 @@ interface CallbackContainer { /** * @since 1.0 * - * @param ContainerBuilder $containerBuilder + * @param ContainerLoader $containerLoader */ public function register( ContainerBuilder $containerBuilder ); diff --git a/src/CallbackContainerBuilder.php b/src/CallbackContainerBuilder.php index 7ac2dea..22f49e2 100644 --- a/src/CallbackContainerBuilder.php +++ b/src/CallbackContainerBuilder.php @@ -11,7 +11,7 @@ /** * @license GNU GPL v2+ - * @since 1.0 + * @since 2.0 * * @author mwjames */ @@ -38,7 +38,7 @@ class CallbackContainerBuilder implements ContainerBuilder { protected $recursiveMarker = array(); /** - * @since 1.0 + * @since 2.0 * * @param CallbackContainer|null $callbackContainer */ @@ -49,7 +49,7 @@ public function __construct( CallbackContainer $callbackContainer = null ) { } /** - * @since 1.0 + * @since 2.0 * * @param CallbackContainer $callbackContainer */ @@ -58,7 +58,7 @@ public function registerCallbackContainer( CallbackContainer $callbackContainer } /** - * @since 1.2 + * @since 2.0 * * @param string $file * @throws FileNotFoundException @@ -69,7 +69,7 @@ public function registerFromFile( $file ) { throw new FileNotFoundException( "Cannot access or read {$file}" ); } - $defintions = require_once $file; + $defintions = require $file; foreach ( $defintions as $serviceName => $callback ) { @@ -82,7 +82,7 @@ public function registerFromFile( $file ) { } /** - * @since 1.0 + * @since 2.0 * * {@inheritDoc} */ @@ -99,7 +99,7 @@ public function registerCallback( $serviceName, Closure $callback ) { * If you are not running PHPUnit or for that matter any other testing * environment then you are not suppose to use this function. * - * @since 1.0 + * @since 2.0 * * @param string $serviceName * @param mixed $instance @@ -117,7 +117,7 @@ public function registerObject( $serviceName, $instance ) { } /** - * @since 1.0 + * @since 2.0 * * {@inheritDoc} */ @@ -131,36 +131,7 @@ public function registerExpectedReturnType( $serviceName, $type ) { } /** - * @see PSR-11 ContainerInterface - * @since 1.2 - * - * {@inheritDoc} - */ - public function has( $id ) { - return $this->isRegistered( $id ); - } - - /** - * @see PSR-11 ContainerInterface - * @since 1.2 - * - * {@inheritDoc} - */ - public function get( $id ) { - - // A call to the get method with a non-existing id SHOULD throw a Psr\Container\Exception\NotFoundExceptionInterface. - if ( !$this->has( $id ) ) { - // throw new NotFoundExceptionInterface( "Unknown {$id} handler or service" ); - } - - $parameters = func_get_args(); - array_shift( $parameters ); - - return $this->getReturnValueFromCallbackHandlerFor( $id, $parameters ); - } - - /** - * @since 1.2 + * @since 2.0 * * {@inheritDoc} */ @@ -169,48 +140,25 @@ public function isRegistered( $serviceName ) { } /** - * @since 1.0 + * @since 2.0 * * {@inheritDoc} */ public function create( $serviceName ) { - - $parameters = func_get_args(); - array_shift( $parameters ); - - return $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ); + return $this->getReturnValueFromCallbackHandlerFor( $serviceName, func_get_args() ); } /** - * @since 1.0 + * @since 2.0 * * {@inheritDoc} */ public function singleton( $serviceName ) { - - $parameters = func_get_args(); - array_shift( $parameters ); - - $fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#'; - - $instance = $this->getReturnValueFromSingletonFor( $serviceName, $fingerprint ); - - if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) ) { - return $instance; - } - - $instance = $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ); - - $this->singletons[$serviceName][$fingerprint] = function() use ( $instance ) { - static $singleton; - return $singleton = $singleton === null ? $instance : $singleton; - }; - - return $instance; + return $this->getReturnValueFromSingletonFor( $serviceName, func_get_args() ); } /** - * @since 1.0 + * @since 2.0 * * @param string $serviceName */ @@ -220,7 +168,7 @@ public function deregister( $serviceName ) { unset( $this->expectedReturnTypeByHandler[$serviceName] ); } - protected function addRecursiveMarkerFor( $serviceName ) { + private function addRecursiveMarkerFor( $serviceName ) { if ( !is_string( $serviceName ) ) { throw new InvalidParameterTypeException( "Expected a string" ); @@ -237,7 +185,7 @@ protected function addRecursiveMarkerFor( $serviceName ) { } } - protected function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) { + private function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) { $instance = null; $this->addRecursiveMarkerFor( $serviceName ); @@ -246,6 +194,9 @@ protected function getReturnValueFromCallbackHandlerFor( $serviceName, $paramete throw new ServiceNotFoundException( "$serviceName is an unknown service." ); } + // Remove the ServiceName + array_shift( $parameters ); + // Shift the ContainerBuilder to the first position in the parameter list array_unshift( $parameters, $this ); $service = $this->registry[$serviceName]; @@ -260,9 +211,11 @@ protected function getReturnValueFromCallbackHandlerFor( $serviceName, $paramete throw new ServiceTypeMismatchException( $serviceName, $this->expectedReturnTypeByHandler[$serviceName], ( is_object( $instance ) ? get_class( $instance ) : $instance ) ); } - private function getReturnValueFromSingletonFor( $serviceName, $fingerprint ) { + private function getReturnValueFromSingletonFor( $serviceName, $parameters ) { $instance = null; + $fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#'; + $this->addRecursiveMarkerFor( $serviceName ); if ( isset( $this->singletons[$serviceName][$fingerprint] ) ) { @@ -272,6 +225,17 @@ private function getReturnValueFromSingletonFor( $serviceName, $fingerprint ) { $this->recursiveMarker[$serviceName]--; + if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) ) { + return $instance; + } + + $instance = $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ); + + $this->singletons[$serviceName][$fingerprint] = function() use ( $instance ) { + static $singleton; + return $singleton = $singleton === null ? $instance : $singleton; + }; + return $instance; } diff --git a/src/CallbackContainerFactory.php b/src/CallbackContainerFactory.php index 0eff734..cd553d2 100644 --- a/src/CallbackContainerFactory.php +++ b/src/CallbackContainerFactory.php @@ -21,6 +21,24 @@ public function newCallbackContainerBuilder( CallbackContainer $callbackContaine return new CallbackContainerBuilder( $callbackContainer ); } + /** + * @since 2.0 + * + * @param CallbackContainer|null $callbackContainer + * @param BacktraceSniffer|null $backtraceSniffer + * @param CallFuncMemorySniffer|null $callFuncMemorySniffer + * + * @return LoggableContainerBuilder + */ + public function newLoggableContainerBuilder( ContainerBuilder $containerBuilder = null, BacktraceSniffer $backtraceSniffer = null, CallFuncMemorySniffer $callFuncMemorySniffer = null ) { + + if ( $containerBuilder === null ) { + $containerBuilder = $this->newCallbackContainerBuilder(); + } + + return new LoggableContainerBuilder( $containerBuilder, $backtraceSniffer, $callFuncMemorySniffer ); + } + /** * @since 2.0 * @@ -46,4 +64,24 @@ public function newServicesManager( ContainerBuilder $containerBuilder = null ) return new ServicesManager( $containerBuilder ); } + /** + * @since 2.0 + * + * @param integer $depth + * + * @return BacktraceSniffer + */ + public function newBacktraceSniffer( $depth = 1 ) { + return new BacktraceSniffer( $depth ); + } + + /** + * @since 2.0 + * + * @return CallFuncMemorySniffer + */ + public function newCallFuncMemorySniffer() { + return new CallFuncMemorySniffer(); + } + } diff --git a/src/ContainerBuilder.php b/src/ContainerBuilder.php index f9055c1..b7e8e12 100644 --- a/src/ContainerBuilder.php +++ b/src/ContainerBuilder.php @@ -6,37 +6,11 @@ /** * @license GNU GPL v2+ - * @since 2.0 + * @since 1.2 * * @author mwjames */ -interface ContainerBuilder { - - /** - * @since 1.0 - * - * @param string $serviceName - * @param Closure $callback - */ - public function registerCallback( $serviceName, Closure $callback ); - - /** - * @since 1.1 - * - * @param CallbackContainer $callbackContainer - */ - public function registerCallbackContainer( CallbackContainer $callbackContainer ); - - /** - * Registers the expected return type of an instance that is called either - * via ContainerBuilder::create or ContainerBuilder::singleton. - * - * @since 1.0 - * - * @param string $serviceName - * @param string $type - */ - public function registerExpectedReturnType( $serviceName, $type ); +interface ContainerBuilder extends ContainerRegistry { /** * @since 1.2 diff --git a/src/ContainerRegistry.php b/src/ContainerRegistry.php new file mode 100644 index 0000000..0ebb462 --- /dev/null +++ b/src/ContainerRegistry.php @@ -0,0 +1,56 @@ +containerBuilder = $containerBuilder; + $this->backtraceSniffer = $backtraceSniffer; + $this->callFuncMemorySniffer = $callFuncMemorySniffer; + } + + /** + * @see LoggerAwareInterface::setLogger + * + * @since 2.5 + * + * @param LoggerInterface $logger + */ + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function registerCallbackContainer( CallbackContainer $callbackContainer ) { + $this->containerBuilder->registerCallbackContainer( $callbackContainer ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function registerCallback( $serviceName, Closure $callback ) { + $this->containerBuilder->registerCallback( $serviceName, $callback ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function registerExpectedReturnType( $serviceName, $type ) { + $this->containerBuilder->registerExpectedReturnType( $serviceName, $type ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function registerObject( $serviceName, $instance ) { + $this->containerBuilder->registerObject( $serviceName, $instance ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function registerFromFile( $file ) { + $this->containerBuilder->registerFromFile( $file ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function isRegistered( $serviceName ) { + return $this->containerBuilder->isRegistered( $serviceName ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function create( $serviceName ) { + + $this->initLog( $serviceName ); + $this->logs[$serviceName]['prototype']++; + + if ( $this->backtraceSniffer !== null ) { + $this->logs[$serviceName]['prototype-backtrace'][] = $this->backtraceSniffer->getCallers(); + } + + if ( $this->callFuncMemorySniffer !== null ) { + $instance = $this->callFuncMemorySniffer->call( array( $this->containerBuilder, 'create' ), func_get_args() ); + $this->logs[$serviceName]['prototype-memory'][] = $this->callFuncMemorySniffer->getMemoryUsed(); + $this->logs[$serviceName]['prototype-time'][] = $this->callFuncMemorySniffer->getTimeUsed(); + } else { + $instance = call_user_func_array( array( $this->containerBuilder, 'create' ), func_get_args() ); + } + + return $instance; + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function singleton( $serviceName ) { + + $this->initLog( $serviceName ); + $this->logs[$serviceName]['singleton']++; + + if ( $this->callFuncMemorySniffer !== null ) { + $instance = $this->callFuncMemorySniffer->call( array( $this->containerBuilder, 'singleton' ), func_get_args() ); + $this->logs[$serviceName]['singleton-memory'][] = $this->callFuncMemorySniffer->getMemoryUsed(); + $this->logs[$serviceName]['singleton-time'][] = $this->callFuncMemorySniffer->getTimeUsed(); + } else { + $instance = call_user_func_array( array( $this->containerBuilder, 'singleton' ), func_get_args() ); + } + + return $instance; + } + + private function initLog( $serviceName ) { + + if ( isset( $this->logs[$serviceName] ) ) { + return; + } + + $this->logs[$serviceName] = array( + 'prototype' => 0, + 'prototype-backtrace' => array(), + 'prototype-memory' => array(), + 'prototype-time' => array(), + 'singleton' => 0, + 'singleton-memory' => array(), + 'singleton-time' => array(), + ); + + if ( $this->backtraceSniffer === null ) { + unset( $this->logs[$serviceName]['prototype-backtrace'] ); + } + + if ( $this->callFuncMemorySniffer === null ) { + unset( $this->logs[$serviceName]['prototype-memory'] ); + unset( $this->logs[$serviceName]['singleton-memory'] ); + unset( $this->logs[$serviceName]['prototype-time'] ); + unset( $this->logs[$serviceName]['singleton-time'] ); + } + } + + function __destruct() { + call_user_func_array( $this->logWithCallback(), array( $this->logger ) ); + } + + private function buildLogs() { + + foreach ( $this->logs as $serviceName => $record ) { + + $count = $this->logs[$serviceName]['singleton']; + $this->calcMedian( 'singleton-memory', $serviceName, $record,$count ); + $this->calcMedian( 'singleton-time', $serviceName, $record, $count ); + + $count = $this->logs[$serviceName]['prototype']; + $this->calcMedian( 'prototype-memory', $serviceName, $record, $count ); + $this->calcMedian( 'prototype-time', $serviceName, $record, $count ); + } + + // PHP 5.4+ + $flag = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : 0; + + return json_encode( $this->logs, $flag ); + } + + private function calcMedian( $type, $serviceName, $record, $count ) { + if ( isset( $record[$type] ) && $count > 0 ) { + $this->logs[$serviceName][$type . '-median'] = array_sum( $record[$type] ) / $count; + unset( $this->logs[$serviceName][$type] ); + } + } + + private function logWithCallback( $logger = null ) { + return function( $logger = null ) { + + if ( $logger === null ) { + return; + } + + $context = array(); + $logger->info( $this->buildLogs(), $context ); + }; + } + +} diff --git a/src/NullContainerBuilder.php b/src/NullContainerBuilder.php index 4f2ddc6..d05cdcd 100644 --- a/src/NullContainerBuilder.php +++ b/src/NullContainerBuilder.php @@ -5,7 +5,6 @@ /** * @license GNU GPL v2+ * @since 1.0 - * @deprecated since 1.1, use NullCallbackInstantiator * * @author mwjames */ @@ -19,18 +18,32 @@ class NullContainerBuilder implements ContainerBuilder { public function registerCallback( $handlerName, \Closure $callback ) {} /** - * @since 1.0 + * @since 1.1 * * {@inheritDoc} */ - public function registerExpectedReturnType( $handlerName, $type ) {} + public function registerCallbackContainer( CallbackContainer $callbackContainer ) {} /** - * @since 1.1 + * @since 2.0 * * {@inheritDoc} */ - public function registerCallbackContainer( CallbackContainer $callbackContainer ) {} + public function registerFromFile( $file ) {} + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function registerObject( $serviceName, $instance ) {} + + /** + * @since 1.0 + * + * {@inheritDoc} + */ + public function registerExpectedReturnType( $handlerName, $type ) {} /** * @since 1.2 diff --git a/src/ServicesManager.php b/src/ServicesManager.php index 90e9f5b..f5f1cbe 100644 --- a/src/ServicesManager.php +++ b/src/ServicesManager.php @@ -34,9 +34,9 @@ public function __construct( ContainerBuilder $containerBuilder ) { * * @param string $serviceName * @param mixed $service - * @param string|null $type + * @param string|null $expectedReturnType */ - public function add( $serviceName, $service, $type = null ) { + public function add( $serviceName, $service, $expectedReturnType = null ) { if ( !is_callable( $service ) ) { $service = function() use( $service ) { @@ -46,8 +46,8 @@ public function add( $serviceName, $service, $type = null ) { $this->containerBuilder->registerCallback( $serviceName, $service ); - if ( $type !== null ) { - $this->containerBuilder->registerExpectedReturnType( $serviceName, $type ); + if ( $expectedReturnType !== null ) { + $this->containerBuilder->registerExpectedReturnType( $serviceName, $expectedReturnType ); } } @@ -70,13 +70,16 @@ public function has( $serviceName ) { * @return mixed * @throws ServiceNotFoundException */ - public function getBy( $serviceName ) { + public function get( $serviceName ) { if ( !$this->containerBuilder->isRegistered( $serviceName ) ) { throw new ServiceNotFoundException( "$serviceName is an unknown service." ); } - return $this->containerBuilder->singleton( $serviceName ); + $parameters = func_get_args(); + array_unshift( $parameters, $serviceName ); + + return call_user_func_array( array( $this->containerBuilder, 'create' ), $parameters ); } /** @@ -84,7 +87,7 @@ public function getBy( $serviceName ) { * * @param string $serviceName */ - public function removeBy( $serviceName ) { + public function remove( $serviceName ) { $this->containerBuilder->deregister( $serviceName ); } diff --git a/tests/phpunit/Fixtures/fakeCallbackFromFile.php b/tests/phpunit/Fixtures/fakeCallbackFromFile.php index 1275e0d..cc14f82 100644 --- a/tests/phpunit/Fixtures/fakeCallbackFromFile.php +++ b/tests/phpunit/Fixtures/fakeCallbackFromFile.php @@ -3,6 +3,8 @@ namespace Onoi\CallbackContainer\Tests; /** + * @codeCoverageIgnore + * * Expected registration form: * * return array( @@ -23,6 +25,37 @@ return new \stdClass; }, + /** + * @return Closure + */ + 'AnotherStdClassFromFileWithInterFactory' => function( $containerBuilder ) { + $containerBuilder->registerExpectedReturnType( 'AnotherStdClassFromFile', '\stdClass' ); + return $containerBuilder->create( 'SomeStdClassFromFile' ); + }, + + /** + * @return Closure + */ + 'AnotherStdClassFromFileWithInterFactoryAndArgument' => function( $containerBuilder, $arg ) { + $instance = $containerBuilder->create( 'AnotherStdClassFromFileWithInterFactory' ); + $instance->argument = $arg; + return $instance; + }, + + /** + * @return Closure + */ + 'serviceCallFromFileWithCircularReference' => function( $containerBuilder ) { + return $containerBuilder->create( 'serviceFromFileWithForcedCircularReference' ); + }, + + /** + * @return Closure + */ + 'serviceFromFileWithForcedCircularReference' => function( $containerBuilder ) { + return $containerBuilder->create( 'serviceCallFromFileWithCircularReference' ); + }, + /** * @return string */ diff --git a/tests/phpunit/Unit/BacktraceSnifferTest.php b/tests/phpunit/Unit/BacktraceSnifferTest.php new file mode 100644 index 0000000..6e84a78 --- /dev/null +++ b/tests/phpunit/Unit/BacktraceSnifferTest.php @@ -0,0 +1,74 @@ +assertInstanceOf( + '\Onoi\CallbackContainer\BacktraceSniffer', + new BacktraceSniffer() + ); + } + + public function testGetCaller() { + + $instance = new BacktraceSniffer(); + + $this->assertEquals( + __METHOD__, + $instance->getCaller() + ); + } + + public function testGetCallerWithDifferentDepth() { + + $instance = new BacktraceSniffer(); + + $this->assertEquals( + 'ReflectionMethod::invokeArgs', + $instance->getCaller( 2 ) + ); + + $this->assertEquals( + 'unknown', + $instance->getCaller( 100 ) + ); + } + + public function testGetCallers() { + + $instance = new BacktraceSniffer(); + + $this->assertEquals( + array( __METHOD__ ), + $instance->getCallers() + ); + } + + public function testGetCallersWithDifferentDepth() { + + $instance = new BacktraceSniffer(); + + $this->assertEquals( + array( + 'ReflectionMethod::invokeArgs', + __METHOD__ + ), + $instance->getCallers( 2 ) + ); + } + +} diff --git a/tests/phpunit/Unit/CallFuncMemorySnifferTest.php b/tests/phpunit/Unit/CallFuncMemorySnifferTest.php new file mode 100644 index 0000000..ac66f4f --- /dev/null +++ b/tests/phpunit/Unit/CallFuncMemorySnifferTest.php @@ -0,0 +1,74 @@ +assertInstanceOf( + '\Onoi\CallbackContainer\CallFuncMemorySniffer', + new CallFuncMemorySniffer() + ); + } + + public function testCallWithoutArguments() { + + $instance = new CallFuncMemorySniffer(); + + $this->assertEquals( + 'Foo', + $instance->call( function(){ return 'Foo'; } ) + ); + + $this->assertInternalType( + 'integer', + $instance->getMemoryUsed() + ); + + $this->assertInternalType( + 'float', + $instance->getTimeUsed() + ); + } + + public function testCallWithArguments() { + + $instance = new CallFuncMemorySniffer(); + + $this->assertEquals( + 'Foo-abc', + $instance->call( function( $arg ) { return 'Foo-'. $arg; }, array( 'abc' ) ) + ); + + $this->assertInternalType( + 'integer', + $instance->getMemoryUsed() + ); + + $this->assertInternalType( + 'float', + $instance->getTimeUsed() + ); + } + + public function testInvalidCallThrowsException() { + + $instance = new CallFuncMemorySniffer(); + + $this->setExpectedException( 'RuntimeException' ); + $instance->call( 'foo' ); + } + +} diff --git a/tests/phpunit/Unit/CallbackContainerBuilderTest.php b/tests/phpunit/Unit/CallbackContainerBuilderTest.php index 6fca13a..c967e83 100644 --- a/tests/phpunit/Unit/CallbackContainerBuilderTest.php +++ b/tests/phpunit/Unit/CallbackContainerBuilderTest.php @@ -10,7 +10,7 @@ * @group onoi-callback-container * * @license GNU GPL v2+ - * @since 1.0 + * @since 1.2 * * @author mwjames */ @@ -135,6 +135,37 @@ public function testRegisterFromFile() { ); } + public function testRegisterFromFileWithInterFactory() { + + $instance = new CallbackContainerBuilder(); + $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' ); + + $this->assertEquals( + new \stdClass, + $instance->create( 'AnotherStdClassFromFileWithInterFactory' ) + ); + } + + public function testRegisterFromFileWithInterFactoryAndArgument() { + + $instance = new CallbackContainerBuilder(); + $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' ); + + $this->assertEquals( + 123, + $instance->create( 'AnotherStdClassFromFileWithInterFactoryAndArgument', 123 )->argument + ); + } + + public function testRegisterFromFileWithCircularReferenceThrowsException() { + + $instance = new CallbackContainerBuilder(); + $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' ); + + $this->setExpectedException( 'Onoi\CallbackContainer\Exception\ServiceCircularReferenceException' ); + $instance->create( 'serviceFromFileWithForcedCircularReference' ); + } + public function testRegisterObject() { $expected = new \stdClass; @@ -325,7 +356,7 @@ public function testUnregisteredServiceOnSingletonThrowsException() { $instance->singleton( 'Foo' ); } - public function testTryToLoadCallbackHandlerWithTypeMismatchThrowsException() { + public function testCreateFromCallbackWithTypeMismatchThrowsException() { $instance = new CallbackContainerBuilder(); @@ -339,7 +370,7 @@ public function testTryToLoadCallbackHandlerWithTypeMismatchThrowsException() { $instance->create( 'Foo' ); } - public function testTryToUseInvalidNameForCallbackHandlerOnLoadThrowsException() { + public function testCreateWithInvalidNameForCallbackHandlerOnLoadThrowsException() { $instance = new CallbackContainerBuilder(); @@ -347,7 +378,7 @@ public function testTryToUseInvalidNameForCallbackHandlerOnLoadThrowsException() $instance->create( new \stdClass ); } - public function testTryToUseInvalidNameForCallbackHandlerOnSingletonThrowsException() { + public function testSingletonWithInvalidNameForCallbackHandlerOnSingletonThrowsException() { $instance = new CallbackContainerBuilder(); @@ -355,7 +386,7 @@ public function testTryToUseInvalidNameForCallbackHandlerOnSingletonThrowsExcept $instance->singleton( new \stdClass ); } - public function testTryToLoadCallbackHandlerWithCircularReferenceThrowsException() { + public function testCreateOnCallbackHandlerWithCircularReferenceThrowsException() { $instance = new CallbackContainerBuilder(); @@ -369,7 +400,7 @@ public function testTryToLoadCallbackHandlerWithCircularReferenceThrowsException $instance->create( 'Foo' ); } - public function testTryToLoadSingletonCallbackHandlerWithCircularReferenceThrowsException() { + public function testSingletonOnCallbackHandlerWithCircularReferenceThrowsException() { $instance = new CallbackContainerBuilder(); @@ -382,7 +413,7 @@ public function testTryToLoadSingletonCallbackHandlerWithCircularReferenceThrows $instance->singleton( 'Foo' ); } - public function testTryToUseInvalidNameOnCallbackHandlerRegistrationThrowsException() { + public function testRegisterCallbackWithInvalidNameThrowsException() { $instance = new CallbackContainerBuilder(); @@ -392,7 +423,7 @@ public function testTryToUseInvalidNameOnCallbackHandlerRegistrationThrowsExcept } ); } - public function testTryToUseInvalidNameOnObjectRegistrationThrowsException() { + public function testRegisterObjectWithInvalidNameThrowsException() { $instance = new CallbackContainerBuilder(); @@ -400,7 +431,7 @@ public function testTryToUseInvalidNameOnObjectRegistrationThrowsException() { $instance->registerObject( new \stdClass, new \stdClass ); } - public function testTryToUseInvalidNameOnTypeRegistrationThrowsException() { + public function testRegisterExpectedReturnTypeWithInvalidTypeThrowsException() { $instance = new CallbackContainerBuilder(); @@ -408,7 +439,7 @@ public function testTryToUseInvalidNameOnTypeRegistrationThrowsException() { $instance->registerExpectedReturnType( new \stdClass, 'Bar' ); } - public function testTryToRegisterFromInvalidFileThrowsException() { + public function testRegisterFromWithInvalidFileThrowsException() { $instance = new CallbackContainerBuilder(); diff --git a/tests/phpunit/Unit/CallbackContainerFactoryTest.php b/tests/phpunit/Unit/CallbackContainerFactoryTest.php index 67efa64..f52a47d 100644 --- a/tests/phpunit/Unit/CallbackContainerFactoryTest.php +++ b/tests/phpunit/Unit/CallbackContainerFactoryTest.php @@ -43,6 +43,24 @@ public function testCanConstructNullContainerBuilder() { ); } + public function testCanConstructLoggableContainerBuilder() { + + $instance = new CallbackContainerFactory(); + + $backtraceSniffer = $this->getMockBuilder( '\Onoi\CallbackContainer\BacktraceSniffer' ) + ->disableOriginalConstructor() + ->getMock(); + + $callFuncMemorySniffer = $this->getMockBuilder( '\Onoi\CallbackContainer\CallFuncMemorySniffer' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\LoggableContainerBuilder', + $instance->newLoggableContainerBuilder( null, $backtraceSniffer, $callFuncMemorySniffer ) + ); + } + public function testCanConstructServicesManager() { $instance = new CallbackContainerFactory(); @@ -62,4 +80,24 @@ public function testCanConstructServicesManager() { ); } + public function testCanConstructBacktraceSniffer() { + + $instance = new CallbackContainerFactory(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\BacktraceSniffer', + $instance->newBacktraceSniffer() + ); + } + + public function testCanConstructCallFuncMemorySniffer() { + + $instance = new CallbackContainerFactory(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\CallFuncMemorySniffer', + $instance->newCallFuncMemorySniffer() + ); + } + } diff --git a/tests/phpunit/Unit/LoggableContainerBuilderTest.php b/tests/phpunit/Unit/LoggableContainerBuilderTest.php new file mode 100644 index 0000000..2b4b75a --- /dev/null +++ b/tests/phpunit/Unit/LoggableContainerBuilderTest.php @@ -0,0 +1,108 @@ +spyLogger = new SpyLogger(); + $this->callbackContainerFactory = new CallbackContainerFactory(); + + $this->backtraceSniffer = $this->getMockBuilder( '\Onoi\CallbackContainer\BacktraceSniffer' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->callFuncMemorySniffer = $this->getMockBuilder( '\Onoi\CallbackContainer\CallFuncMemorySniffer' ) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testCanConstruct() { + + $containerBuilder = $this->getMockBuilder( '\Onoi\CallbackContainer\ContainerBuilder' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\LoggableContainerBuilder', + new LoggableContainerBuilder( $containerBuilder, $this->backtraceSniffer, $this->callFuncMemorySniffer ) + ); + } + + public function testLoadCallbackHandlerWithoutExpectedReturnType() { + + $instance = new LoggableContainerBuilder( + $this->callbackContainerFactory->newCallbackContainerBuilder() + ); + + $instance->setLogger( $this->spyLogger ); + $instance->registerCallback( 'Foo', function() { + return 'abc'; + } ); + + $this->assertEquals( + 'abc', + $instance->create( 'Foo' ) + ); + + $this->assertEquals( + 'abc', + $instance->singleton( 'Foo' ) + ); + + // Destruct + $instance = null; + + $this->assertNotEmpty( + $this->spyLogger->getLogs() + ); + } + + public function testRegisterFromFile() { + + $instance = new LoggableContainerBuilder( + $this->callbackContainerFactory->newCallbackContainerBuilder(), + $this->callbackContainerFactory->newBacktraceSniffer(), + $this->callbackContainerFactory->newCallFuncMemorySniffer() + ); + + $instance->setLogger( $this->spyLogger ); + $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' ); + + $this->assertEquals( + new \stdClass, + $instance->create( 'SomeStdClassFromFile' ) + ); + + $this->assertEquals( + new \stdClass, + $instance->singleton( 'SomeStdClassFromFile' ) + ); + + // Destruct + $instance = null; + + $this->assertNotEmpty( + $this->spyLogger->getLogs() + ); + } + +} diff --git a/tests/phpunit/Unit/NullContainerBuilderTest.php b/tests/phpunit/Unit/NullContainerBuilderTest.php index 1fd1686..1a6fb77 100644 --- a/tests/phpunit/Unit/NullContainerBuilderTest.php +++ b/tests/phpunit/Unit/NullContainerBuilderTest.php @@ -39,10 +39,18 @@ public function testInterfaceMethods() { $instance->registerExpectedReturnType( 'Foo', 'bar' ) ); + $this->assertNull( + $instance->registerObject( 'Foo', 'bar' ) + ); + $this->assertNull( $instance->registerCallback( 'Foo', function() {} ) ); + $this->assertNull( + $instance->registerFromFile( 'File' ) + ); + $callbackContainer = $this->getMockBuilder( '\Onoi\CallbackContainer\CallbackContainer' ) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/phpunit/Unit/ServicesManagerTest.php b/tests/phpunit/Unit/ServicesManagerTest.php index 2c60f5f..8717ab8 100644 --- a/tests/phpunit/Unit/ServicesManagerTest.php +++ b/tests/phpunit/Unit/ServicesManagerTest.php @@ -48,7 +48,7 @@ public function testAddServiceWithScalarType() { $this->assertSame( 123, - $instance->getBy( 'Foo' ) + $instance->get( 'Foo' ) ); } @@ -63,7 +63,7 @@ public function testAddServiceWithObjectType() { $this->assertSame( $this, - $instance->getBy( 'Foo' ) + $instance->get( 'Foo' ) ); } @@ -76,7 +76,7 @@ public function testRemoveService() { $instance->has( 'Foo' ) ); - $instance->removeBy( 'Foo' ); + $instance->remove( 'Foo' ); $this->assertFalse( $instance->has( 'Foo' ) @@ -96,7 +96,7 @@ public function testOverrideUntypedService() { $this->assertSame( 123, - $instance->getBy( 'Foo' ) + $instance->get( 'Foo' ) ); } @@ -112,7 +112,7 @@ public function testTryToOverrideTypedServiceWithIncompatibleTypeThrowsException $instance->overrideWith( 'Foo', 123 ); $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceTypeMismatchException' ); - $instance->getBy( 'Foo' ); + $instance->get( 'Foo' ); } public function testTryToAccessToUnknownServiceThrowsException() { @@ -120,7 +120,7 @@ public function testTryToAccessToUnknownServiceThrowsException() { $instance = $this->servicesManager; $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' ); - $instance->getBy( 'Foo' ); + $instance->get( 'Foo' ); } } diff --git a/tests/phpunit/Unit/SpyLogger.php b/tests/phpunit/Unit/SpyLogger.php new file mode 100644 index 0000000..9564e26 --- /dev/null +++ b/tests/phpunit/Unit/SpyLogger.php @@ -0,0 +1,40 @@ +logs[] = array( $level, $message, $context ); + } + + /** + * @since 2.0 + * + * @return array + */ + public function getLogs() { + return $this->logs; + } + +}