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 e545a51..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,51 +23,119 @@ 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 { - 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`. +### 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 @@ -84,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 @@ -93,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 95d7e2d..08c42eb 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+", @@ -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 d497028..15fb92d 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 ContainerLoader $containerLoader */ - 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..22f49e2 --- /dev/null +++ b/src/CallbackContainerBuilder.php @@ -0,0 +1,242 @@ +registerCallbackContainer( $callbackContainer ); + } + } + + /** + * @since 2.0 + * + * @param CallbackContainer $callbackContainer + */ + public function registerCallbackContainer( CallbackContainer $callbackContainer ) { + $callbackContainer->register( $this ); + } + + /** + * @since 2.0 + * + * @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 $file; + + foreach ( $defintions as $serviceName => $callback ) { + + if ( !is_callable( $callback ) ) { + continue; + } + + $this->registerCallback( $serviceName, $callback ); + } + } + + /** + * @since 2.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 2.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 2.0 + * + * {@inheritDoc} + */ + public function registerExpectedReturnType( $serviceName, $type ) { + + if ( !is_string( $serviceName ) || !is_string( $type ) ) { + throw new InvalidParameterTypeException( "Expected a string" ); + } + + $this->expectedReturnTypeByHandler[$serviceName] = $type; + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function isRegistered( $serviceName ) { + return isset( $this->registry[$serviceName] ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function create( $serviceName ) { + return $this->getReturnValueFromCallbackHandlerFor( $serviceName, func_get_args() ); + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function singleton( $serviceName ) { + return $this->getReturnValueFromSingletonFor( $serviceName, func_get_args() ); + } + + /** + * @since 2.0 + * + * @param string $serviceName + */ + public function deregister( $serviceName ) { + unset( $this->registry[$serviceName] ); + unset( $this->singletons[$serviceName] ); + unset( $this->expectedReturnTypeByHandler[$serviceName] ); + } + + private 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 ); + } + } + + private function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) { + + $instance = null; + $this->addRecursiveMarkerFor( $serviceName ); + + if ( !isset( $this->registry[$serviceName] ) ) { + 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]; + + $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, $parameters ) { + + $instance = null; + $fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#'; + + $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]--; + + 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 new file mode 100644 index 0000000..cd553d2 --- /dev/null +++ b/src/CallbackContainerFactory.php @@ -0,0 +1,87 @@ +newCallbackContainerBuilder(); + } + + return new LoggableContainerBuilder( $containerBuilder, $backtraceSniffer, $callFuncMemorySniffer ); + } + + /** + * @since 2.0 + * + * @return NullContainerBuilder + */ + public function newNullContainerBuilder() { + return new NullContainerBuilder(); + } + + /** + * @since 2.0 + * + * @param ContainerBuilder|null $callbackContainer + * + * @return ServicesManager + */ + public function newServicesManager( ContainerBuilder $containerBuilder = null ) { + + if ( $containerBuilder === null ) { + $containerBuilder = $this->newCallbackContainerBuilder(); + } + + 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/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 @@ +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/NullCallbackInstantiator.php b/src/NullCallbackInstantiator.php deleted file mode 100644 index 96e62c6..0000000 --- a/src/NullCallbackInstantiator.php +++ /dev/null @@ -1,55 +0,0 @@ -callbackInstantiator = $callbackInstantiator; - } - - /** - * @since 1.2 - * - * @return ServicesManager - */ - public static function newManager() { - return new self( new DeferredCallbackLoader() ); + public function __construct( ContainerBuilder $containerBuilder ) { + $this->containerBuilder = $containerBuilder; } /** @@ -43,9 +34,9 @@ public static function newManager() { * * @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 ) { @@ -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 ); + if ( $expectedReturnType !== null ) { + $this->containerBuilder->registerExpectedReturnType( $serviceName, $expectedReturnType ); } } @@ -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,18 @@ public function has( $serviceName ) { * @param string $serviceName * * @return mixed + * @throws ServiceNotFoundException */ - public function getBy( $serviceName ) { + public function get( $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 ); + $parameters = func_get_args(); + array_unshift( $parameters, $serviceName ); + + return call_user_func_array( array( $this->containerBuilder, 'create' ), $parameters ); } /** @@ -92,8 +87,8 @@ public function getBy( $serviceName ) { * * @param string $serviceName */ - public function removeBy( $serviceName ) { - $this->callbackInstantiator->deregister( $serviceName ); + public function remove( $serviceName ) { + $this->containerBuilder->deregister( $serviceName ); } /** @@ -103,7 +98,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..cc14f82 --- /dev/null +++ b/tests/phpunit/Fixtures/fakeCallbackFromFile.php @@ -0,0 +1,63 @@ + function( $containerBuilder ) { ... } + * ) + * + * @license GNU GPL v2+ + * @since 1.2 + * + * @author mwjames + */ +return array( + + /** + * @return Closure + */ + 'SomeStdClassFromFile' => function( $containerBuilder ) { + 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 + */ + 'InvalidDefinition' => 'Foo' +); \ No newline at end of file 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/DeferredCallbackLoaderTest.php b/tests/phpunit/Unit/CallbackContainerBuilderTest.php similarity index 58% rename from tests/phpunit/Unit/DeferredCallbackLoaderTest.php rename to tests/phpunit/Unit/CallbackContainerBuilderTest.php index eb880ef..c967e83 100644 --- a/tests/phpunit/Unit/DeferredCallbackLoaderTest.php +++ b/tests/phpunit/Unit/CallbackContainerBuilderTest.php @@ -2,24 +2,25 @@ 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+ - * @since 1.0 + * @since 1.2 * * @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,53 @@ public function testRegisterCallbackContainer() { ); } + public function testRegisterFromFile() { + + $instance = new CallbackContainerBuilder(); + $instance->registerFromFile( __DIR__ . '/../Fixtures/fakeCallbackFromFile.php' ); + + $this->assertEquals( + new \stdClass, + $instance->create( 'SomeStdClassFromFile' ) + ); + } + + 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; - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerExpectedReturnType( 'Foo', '\stdClass' ); $instance->registerObject( 'Foo', $expected ); @@ -158,7 +192,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 +214,8 @@ public function testOverrideSingletonInstanceOnRegisteredCallbackHandlerWithArgu ->disableOriginalConstructor() ->getMock(); - $instance = new DeferredCallbackLoader( - new FooCallbackContainer() + $instance = new CallbackContainerBuilder( + new FakeCallbackContainer() ); $instance->singleton( 'FooWithNullArgument', $argument ); @@ -207,9 +241,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 +270,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 +282,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 +299,7 @@ public function testRecursiveBuildToLoadParameterizedCallbackHandler() { public function testSingleton() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return new \stdClass; @@ -283,9 +317,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 +340,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() { + public function testCreateFromCallbackWithTypeMismatchThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $instance->registerCallback( 'Foo', function() { return new \stdClass; @@ -338,25 +370,25 @@ public function testTryToLoadCallbackHandlerWithTypeMismatchThrowsException() { $instance->create( 'Foo' ); } - public function testTryToUseInvalidNameForCallbackHandlerOnLoadThrowsException() { + public function testCreateWithInvalidNameForCallbackHandlerOnLoadThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->create( new \stdClass ); } - public function testTryToUseInvalidNameForCallbackHandlerOnSingletonThrowsException() { + public function testSingletonWithInvalidNameForCallbackHandlerOnSingletonThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->singleton( new \stdClass ); } - public function testTryToLoadCallbackHandlerWithCircularReferenceThrowsException() { + public function testCreateOnCallbackHandlerWithCircularReferenceThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'RuntimeException' ); @@ -368,23 +400,22 @@ public function testTryToLoadCallbackHandlerWithCircularReferenceThrowsException $instance->create( 'Foo' ); } - public function testTryToLoadSingletonCallbackHandlerWithCircularReferenceThrowsException() { + public function testSingletonOnCallbackHandlerWithCircularReferenceThrowsException() { - $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() { + public function testRegisterCallbackWithInvalidNameThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->registerCallback( new \stdClass, function() { @@ -392,20 +423,28 @@ public function testTryToUseInvalidNameOnCallbackHandlerRegistrationThrowsExcept } ); } - public function testTryToUseInvalidNameOnObjectRegistrationThrowsException() { + public function testRegisterObjectWithInvalidNameThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->registerObject( new \stdClass, new \stdClass ); } - public function testTryToUseInvalidNameOnTypeRegistrationThrowsException() { + public function testRegisterExpectedReturnTypeWithInvalidTypeThrowsException() { - $instance = new DeferredCallbackLoader(); + $instance = new CallbackContainerBuilder(); $this->setExpectedException( 'InvalidArgumentException' ); $instance->registerExpectedReturnType( new \stdClass, 'Bar' ); } + public function testRegisterFromWithInvalidFileThrowsException() { + + $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..f52a47d --- /dev/null +++ b/tests/phpunit/Unit/CallbackContainerFactoryTest.php @@ -0,0 +1,103 @@ +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 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(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\ServicesManager', + $instance->newServicesManager() + ); + + $containerBuilder = $this->getMockBuilder( '\Onoi\CallbackContainer\ContainerBuilder' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + '\Onoi\CallbackContainer\ServicesManager', + $instance->newServicesManager( $containerBuilder ) + ); + } + + 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/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/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/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 66% rename from tests/phpunit/Unit/NullCallbackLoaderTest.php rename to tests/phpunit/Unit/NullContainerBuilderTest.php index a8362ed..1a6fb77 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' ) @@ -43,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 8a0b8d2..8717ab8 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( @@ -43,13 +48,13 @@ public function testAddServiceWithScalarType() { $this->assertSame( 123, - $instance->getBy( 'Foo' ) + $instance->get( 'Foo' ) ); } public function testAddServiceWithObjectType() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this ); $this->assertTrue( @@ -58,20 +63,20 @@ public function testAddServiceWithObjectType() { $this->assertSame( $this, - $instance->getBy( 'Foo' ) + $instance->get( 'Foo' ) ); } public function testRemoveService() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this ); $this->assertTrue( $instance->has( 'Foo' ) ); - $instance->removeBy( 'Foo' ); + $instance->remove( 'Foo' ); $this->assertFalse( $instance->has( 'Foo' ) @@ -80,7 +85,7 @@ public function testRemoveService() { public function testOverrideUntypedService() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this ); $this->assertTrue( @@ -91,13 +96,13 @@ public function testOverrideUntypedService() { $this->assertSame( 123, - $instance->getBy( 'Foo' ) + $instance->get( 'Foo' ) ); } public function testTryToOverrideTypedServiceWithIncompatibleTypeThrowsException() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; $instance->add( 'Foo', $this, '\PHPUnit_Framework_TestCase' ); $this->assertTrue( @@ -106,16 +111,16 @@ public function testTryToOverrideTypedServiceWithIncompatibleTypeThrowsException $instance->overrideWith( 'Foo', 123 ); - $this->setExpectedException( 'RuntimeException' ); - $instance->getBy( 'Foo' ); + $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceTypeMismatchException' ); + $instance->get( 'Foo' ); } public function testTryToAccessToUnknownServiceThrowsException() { - $instance = ServicesManager::newManager(); + $instance = $this->servicesManager; - $this->setExpectedException( 'RuntimeException' ); - $instance->getBy( 'Foo' ); + $this->setExpectedException( '\Onoi\CallbackContainer\Exception\ServiceNotFoundException' ); + $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; + } + +}