diff --git a/.coveralls.yml b/.coveralls.yml index 53bda829..bc71b62f 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,3 +1,2 @@ coverage_clover: clover.xml json_path: coveralls-upload.json -src_dir: src diff --git a/.gitignore b/.gitignore index f146c861..673fe323 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ tmp/ zf-mkdoc-theme/ clover.xml -composer.lock coveralls-upload.json phpunit.xml vendor diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 8f4dd5aa..00000000 --- a/.php_cs +++ /dev/null @@ -1,45 +0,0 @@ -in('src') - ->in('test') - ->notPath('TestAsset') - ->notPath('_files') - ->filter(function (SplFileInfo $file) { - if (strstr($file->getPath(), 'compatibility')) { - return false; - } - }); -$config = Symfony\CS\Config\Config::create(); -$config->level(null); -$config->fixers( - array( - 'braces', - 'duplicate_semicolon', - 'elseif', - 'empty_return', - 'encoding', - 'eof_ending', - 'function_call_space', - 'function_declaration', - 'indentation', - 'join_function', - 'line_after_namespace', - 'linefeed', - 'lowercase_keywords', - 'parenthesis', - 'multiple_use', - 'method_argument_space', - 'object_operator', - 'php_closing_tag', - 'remove_lines_between_uses', - 'short_array_syntax', - 'short_tag', - 'standardize_not_equal', - 'trailing_spaces', - 'unused_use', - 'visibility', - 'whitespacy_lines', - ) -); -$config->finder($finder); -return $config; diff --git a/.travis.yml b/.travis.yml index 37e2a40d..b8a05c73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,62 +15,47 @@ cache: env: global: - - CODE_VERSION="^3.0" - - SITE_URL: https://zendframework.github.io/zend-di - - GH_USER_NAME: "Matthew Weier O'Phinney" - - GH_USER_EMAIL: matthew@weierophinney.net - - GH_REF: github.com/zendframework/zend-di.git + - COMPOSER_ARGS="--no-interaction" + - SITE_URL="https://zendframework.github.io/zend-di" + - GH_USER_NAME="Matthew Weier O'Phinney" + - GH_USER_EMAIL="matthew@weierophinney.net" + - GH_REF="github.com/zendframework/zend-di.git" - secure: "nhJ+hLBlGBu5P8udHwGk5eF6mUKubWF5ID+FXHcP86puQzprYIR7Trdv2u4N9M5AKTPKg3Me1QFqR2WgbNyyVWrtyGovCOAFMeZiMBExpNXONoVaAXGlsXCZe6a85Wn54HXeBjuJMKDNlt6LiJqteMf0axyk44h1V/rTbWK5ALKPy2kuILLZgVyEYtwVmrELBduvqR3uHx5PjuGCeJP8LOICuuu7m2JBhWqUBse89vYqhalvXsIButjSrxPmwgSpjOt7zksf//5HwoY1A83+cb6LfR/ZBe1bOgLtfjBBtYyjwL3DsnYPWecfd4qgO69oHT5ZhxOfzLxjSlvtPK0SrH8WEIcFjUnpcGeqyOnydq7HgEC9J6Fnq1VwKLI2VbM/+qQQqvlVDYAQuXvb/YG0sV0x0ad9zEtJ7QPOY8C6ygGm41+lERd39VzQqq5w8IkjXeUAhWzX+ts7w2aIZ3uPYCHoqmvV9DK44/fkmtFHsJU9guDcW1GU/QT91DGVSNMP0qZsZmw6H/kQtDH672GM4xQzv2FgIyqzSY0oggQwtG5y1XkPCr+X2VsSvtTXuihi+ru9g7iqEXU+2aUOyMfB3xnZQ5qiiQUpaatWO/KzRfp/h3b7InPne+nYTrCDfkjJ8N6bypkXv8D6EC/a4zK3XaaMyn0rQ4yPcd99BjHRIOs=" matrix: fast_finish: true include: - - php: 5.5 + - php: 7.1 env: - - EXECUTE_CS_CHECK=true - - php: 5.5 + - DEPS=lowest + - php: 7.1 env: - - CODE_VERSION="^2.6" - - php: 5.6 - env: - - EXECUTE_TEST_COVERALLS=true - - DEPLOY_DOCS="$(if [[ $TRAVIS_BRANCH == 'master' && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo -n 'true' ; else echo -n 'false' ; fi)" + - DEPS=locked + - CHECK_CS=true + - TEST_COVERAGE=true - PATH="$HOME/.local/bin:$PATH" - - php: 5.6 - env: - - CODE_VERSION="^2.6" - - php: 7 - - php: 7 + - php: 7.1 env: - - CODE_VERSION="^2.6" - - php: hhvm - - php: hhvm - env: - - CODE_VERSION="^2.6" - allow_failures: - - php: hhvm + - DEPS=latest notifications: irc: "irc.freenode.org#zftalk.dev" email: false before_install: - - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi - - composer self-update - - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls ; fi - - composer require --no-update "zendframework/zend-code:$CODE_VERSION" + - travis_retry composer self-update + - if [[ $TRAVIS_PHP_VERSION != "hhvm" && $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini ; fi install: - - travis_retry composer install --no-interaction --ignore-platform-reqs + - if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi + - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi + - if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer require --dev $COMPOSER_ARGS satooshi/php-coveralls:^1.0 ; fi + - travis_retry composer install $COMPOSER_ARGS + - composer show script: - - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit --coverage-clover clover.xml ; fi - - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then ./vendor/bin/phpunit ; fi - - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run ; fi - - if [[ $DEPLOY_DOCS == "true" && "$TRAVIS_TEST_RESULT" == "0" ]]; then wget -O theme-installer.sh "https://raw.githubusercontent.com/zendframework/zf-mkdoc-theme/master/theme-installer.sh" ; chmod 755 theme-installer.sh ; ./theme-installer.sh ; fi - -after_success: - - if [[ $DEPLOY_DOCS == "true" ]]; then echo "Preparing to build and deploy documentation" ; ./zf-mkdoc-theme/deploy.sh ; echo "Completed deploying documentation" ; fi + - if [[ $TEST_COVERAGE == 'true' ]]; then composer test-coverage ; else composer test ; fi + - if [[ $CHECK_CS == 'true' ]]; then composer cs-check ; fi after_script: - - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/coveralls ; fi + - if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer upload-coverage ; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb7c41f..8555122d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,54 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 3.0.0 - WIP + +### Added + +- `Zend\Di\DefaultContainer` that implements `Psr\Container\ContainerInterface` + * Can act as a standalone IoC container + * Provides `build()` to be signature compatible with `Zend\ServiceManager\ServiceManager` +- Renamed `Zend\Di\DependencyInjectionInterface` to `Zend\Di\InjectorInterface`. It defines + the injector to create new instances based on a class or alias name. + * `newInstance()` Changed to `create()` + * `has()` Changed to `canCreate()` + * Removed `get()` +- `Zend\Di\Injector` as implementation of `Zend\Di\InjectorInterface` + * Is designed to incorporate with `Psr\Container\ContainerInterface` + * Utilizes `Zend\Di\Resolver\DependencyResolverInterface` +- Moved strategies to resolve method parameters to `Zend\Di\Resolver` +- PHP 7.1 Typesafety +- Classes to wrap value and type injections +- Support for zend-component-installer +- `Zend\Di\ConfigInterface` to implement custom configurations +- Code generator for generating a pre-resolved injector and factories + +### Deprecated + +- Nothing + +### Removed + +- Support for PHP < 7.1 +- `Zend\Di\Defintion\CompilerDefinition` in favour of `Zend\Di\CodeGenerator`. +- `Zend\Di\InstanceManager`, `Zend\Di\ServiceLocator`, `Zend\Di\ServiceLocatorInterface` + and `Zend\Di\LocatorInterface` in favour of `Psr\Container\ContainerInterface` +- `Zend\Di\Di` is removed in favour of `Zend\Di\DefaultContainer` +- `Zend\Di\DefintionList` +- `Zend\Di\Definition\BuilderDefinition` +- `Zend\Di\Definition\ArrayDefinition` +- Parameters passed to `newInstance()` will only be used for constructing the requested class and no longer be forwarded to nested instanciations. +- `get()` does no longer support a `$parameters` array, `newInstance()` still does +- Removed setter/method injections +- Generators in `Zend\Di\ServiceLocator` in favor of `Zend\Di\CodeGenerator` + +### Fixed + +- [#6](https://github.com/zendframework/zend-di/pull/6) Full ZF3 Compatibility +- [#20](https://github.com/zendframework/zend-di/pull/20) Update composer deps and travis config +- [#17](https://github.com/zendframework/zend-di/pull/17) Fix mkdocs config (src_dir is deprecated) +- [#18](https://github.com/zendframework/zend-di/issues/18) Di Runtime Compiler Definition + ## 2.7.0 - TBD ### Added diff --git a/README.md b/README.md index fe27f6a3..44a59ff7 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,34 @@ [![Build Status](https://secure.travis-ci.org/zendframework/zend-di.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-di) [![Coverage Status](https://coveralls.io/repos/zendframework/zend-di/badge.svg?branch=master)](https://coveralls.io/r/zendframework/zend-di?branch=master) -`Zend\Di` is an example of an Inversion of Control (IoC) container. IoC containers +`Zend\Di` provides auto wiring to implement Inversion of Control (IoC) containers. IoC containers are widely used to create object instances that have all dependencies resolved and injected. Dependency Injection containers are one form of IoC – but not the only form. +`Zend\Di` is designed to be simple, fast and reusable. It provides the following features: + +* Constructor injection +* Autowiring: + - Recursively through all dependencies + - With configured type preferences + - with configured injections + - With injections passed in the create() call +* Code generators to create factories usable by other IoC containers like Zend\ServiceManager + +It does __not__ provide: + +* Setter, interface, property or any other injection method than constructor injection +* Support for factories +* Declaring shared/unshared instances + - the injector always creates new instances + - the default container always shares instances +* Support for variadic arguments in __construct + +If you need these features combine it with another IoC container like [`Zend\ServiceManager`](https://docs.zendframework.com/zend-servicemanager/). + + - File issues at https://github.com/zendframework/zend-di/issues - Documentation is at https://zendframework.github.io/zend-di/ + + diff --git a/composer.json b/composer.json index 9f15661d..1a215959 100644 --- a/composer.json +++ b/composer.json @@ -1,38 +1,61 @@ { - "name": "zendframework/zend-di", - "description": " ", - "license": "BSD-3-Clause", - "keywords": [ + "name" : "zendframework/zend-di", + "description" : " ", + "require" : { + "php" : "^7.1", + "psr/container" : "^1.0", + "zendframework/zend-stdlib" : "^2.7 || ^3.0" + }, + "require-dev" : { + "phpunit/PHPUnit" : "^6.0", + "zendframework/zend-code" : "^2.6 || ^3.0", + "zendframework/zend-servicemanager" : "^3.0", + "zendframework/zend-coding-standard": "^1.0" + }, + "conflict": { + "zendframework/zend-servicemanager-di": "*" + }, + "license" : "BSD-3-Clause", + "keywords" : [ "zf2", "di" ], - "homepage": "https://github.com/zendframework/zend-di", - "autoload": { - "psr-4": { - "Zend\\Di\\": "src/" + "autoload-dev" : { + "psr-4" : { + "ZendTest\\Di\\" : "test/" } }, - "require": { - "php": "^5.5 || ^7.0", - "container-interop/container-interop": "^1.1", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/PHPUnit": "~4.0" - }, - "minimum-stability": "dev", - "prefer-stable": true, - "extra": { - "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "extra" : { + "branch-alias" : { + "dev-master" : "2.6-dev", + "dev-develop" : "2.7-dev" + }, + "zf" : { + "component" : "Zend\\Di", + "config-provider" : "Zend\\Di\\ConfigProvider" } }, - "autoload-dev": { - "psr-4": { - "ZendTest\\Di\\": "test/" + "minimum-stability" : "dev", + "autoload" : { + "psr-4" : { + "Zend\\Di\\" : "src/" } + }, + "suggest" : { + "zendframework/zend-servicemanager" : "An IoC container without auto wiring capabilities", + "zendframework/zend-code" : "Required if you want to generate code" + }, + "homepage" : "https://github.com/zendframework/zend-di", + "prefer-stable" : true, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "upload-coverage": "coveralls -v", + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..2c7bc399 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1875 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "ab7d71ab2121a82fa23b5e2c8559a423", + "packages": [ + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "zendframework/zend-stdlib", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/debedcfc373a293f9250cc9aa03cf121428c8e78", + "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "^2.6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev", + "dev-develop": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-stdlib", + "keywords": [ + "stdlib", + "zf2" + ], + "time": "2016-09-13T14:38:50+00:00" + } + ], + "packages-dev": [ + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-08-30T18:51:59+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2017-09-04T11:05:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "ext-xdebug": "^2.5", + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-11-03T13:47:33+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03T07:40:28+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-08-20T05:47:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/562f7dc75d46510a4ed5d16189ae57fbe45a9932", + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.2.2", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^4.0.3", + "sebastian/comparator": "^2.0.2", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-11-08T11:26:09+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/2f789b59ab89669015ad984afa350c4ec577ade0", + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.0" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2017-08-03T14:08:16+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1174d9018191e93cb9d719edec01257fc05f8158" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158", + "reference": "1174d9018191e93cb9d719edec01257fc05f8158", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-11-03T07:16:52+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62", + "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-05-22T02:43:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23T20:04:58+00:00" + }, + { + "name": "zendframework/zend-code", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/6b1059db5b368db769e4392c6cb6cc139e56640d", + "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d", + "shasum": "" + }, + "require": { + "php": "^7.1", + "zendframework/zend-eventmanager": "^2.6 || ^3.0" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "zendframework/zend-coding-standard": "^1.0.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2017-10-20T15:21:32+00:00" + }, + { + "name": "zendframework/zend-coding-standard", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-coding-standard.git", + "reference": "893316d2904e93f1c74c1384b6d7d57778299cb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-coding-standard/zipball/893316d2904e93f1c74c1384b6d7d57778299cb6", + "reference": "893316d2904e93f1c74c1384b6d7d57778299cb6", + "shasum": "" + }, + "require": { + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Zend Framework coding standard", + "keywords": [ + "Coding Standard", + "zf" + ], + "time": "2016-11-09T21:30:43+00:00" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "9d72db10ceb6e42fb92350c0cb54460da61bd79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/9d72db10ceb6e42fb92350c0cb54460da61bd79c", + "reference": "9d72db10ceb6e42fb92350c0cb54460da61bd79c", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "phpunit/phpunit": "^6.0.7 || ^5.7.14", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + }, + "suggest": { + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "event", + "eventmanager", + "events", + "zf2" + ], + "time": "2017-07-11T19:17:22+00:00" + }, + { + "name": "zendframework/zend-servicemanager", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-servicemanager.git", + "reference": "c3036efb81f71bfa36cc9962ee5d4474f36581d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/c3036efb81f71bfa36cc9962ee5d4474f36581d0", + "reference": "c3036efb81f71bfa36cc9962ee5d4474f36581d0", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "php": "^5.6 || ^7.0", + "psr/container": "^1.0", + "zendframework/zend-stdlib": "^3.1" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "ocramius/proxy-manager": "^1.0 || ^2.0", + "phpbench/phpbench": "^0.10.0", + "phpunit/phpunit": "^5.7 || ^6.0.6", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "suggest": { + "ocramius/proxy-manager": "ProxyManager 1.* to handle lazy initialization of services", + "zendframework/zend-stdlib": "zend-stdlib ^2.5 if you wish to use the MergeReplaceKey or MergeRemoveKey features in Config instances" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev", + "dev-develop": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-servicemanager", + "keywords": [ + "service-manager", + "servicemanager", + "zf" + ], + "time": "2017-03-01T22:08:02+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^7.1" + }, + "platform-dev": [] +} diff --git a/doc/book/codegen.md b/doc/book/codegen.md new file mode 100644 index 00000000..6b420f04 --- /dev/null +++ b/doc/book/codegen.md @@ -0,0 +1,40 @@ +# Code Generator + +`Zend\Di` comes with [AoT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) +generators to create optimized code for production. +These generators will inspect the provided classes, resolve its dependencies and generate +factories based on these results. + +__NOTE:__ This feature requires [zend-code](https://docs.zendframework.com/zend-code/). + +## Generating an optimized injector + +The `Zend\Di\CodeGenerator\InjectorGenerator` offers an implementation to generate +an optimized Injector based on the runtime configuration and a resolver instance. + +```php +use Zend\Di\Config; +use Zend\Di\Definition\RuntimeDefinition; +use Zend\Di\Resolver\DependencyResolver; +use Zend\Di\CodeGenerator\InjectorGenerator; + +$config = new Config(); +$resolver = new DependencyResolver(new RuntimeDefinition(), $config) +$generator = new InjectorGenerator($config, $resolver); + +// It is highly recommended to set the container that is used at runtime +$resolver->setContainer($container); +$generator->setOutputDirectory('/path/to/generated/files'); +$generator->generate([ + MyClassA::class, + MyClassB::class, + // ... +]); +``` + +You can also utilize `Zend\Code\Scanner` to scan your code for classes: + +```php +$scanner = new DirectoryScanner(__DIR__); +$generator->generate($scanner->getClassNames()); +``` diff --git a/doc/book/config.md b/doc/book/config.md index 354e5a9f..91fcd266 100644 --- a/doc/book/config.md +++ b/doc/book/config.md @@ -1,33 +1,208 @@ # Configuration -Most of the configuration for both the setup of `Definition`s as well as the -setup of the `InstanceManager` can be attained by a configuration file. This -file will produce an array (typically) and have an iterable structure. +The configuration can be provided as an associative array by constructing `Zend\\Di\\Config`. +The configuration defines how types are constructed and dependencies should be +resolved. A type may be an actual class name or an alias to a class name. -The top two keys are 'definition' and 'instance', each specifying values for -the definition setup and instance manager setup, respectively. +The configuration array respects the following keys (unknown keys are simply ignored): -The definition section expects the following information expressed as a PHP -array: +* `preferences`: Associative nested array that maps class or interface names to a service name + that should be used to provied a dependency. See the Type Preferences section below for details. +* `types`: Associative array defining how classes or aliases should be constructed. Each key + in this array is a class or alias name and its value is another associative array with the + following keys: + - `preferences`: The same as `preferences` above, but only for the associated class. + - `parameters`: Associative array declaring the values to inject for the declared construction parameters. + Each key is the parameter name as declared in the constructor method of the associated class name. + See the parameters section below for details. + - `aliasOf`: String that contains a class name. It declares that the associated key is an alias + of the given class name. This class must exist. It cannot not be another alias. + +Here is an example of how the injector config can be created: + +```php +$config = new \Zend\Di\Config([ + // Declares global preferences to use when resolving + // dependencies of the specified type + 'preferences' => [ + // A map of classname => preferred type + MyInterface::class => MyImplementation::class + ], + + // Declares how types should be constructed. + // This also allows declaring aliases of a specific class + 'types' => [ + ClassName::class => [ + // Declaration in the same way as global preferences + // but these will aply when the type of the associated key + // should be instanciated + 'preferences' => [], + + // Constructor parameters to inject. This option will define + // the injections directly by the parameter name of the constructor + // used as key. + // If the parameter is Typehinted by a class/interface name, you can + // provide the injection by string. The injector will use the ioc + // container to obtain it. + 'parameters' => [ + 'foo' => 'bar' + ] + ], + + // Define an alias + 'Alias.Name' => [ + 'aliasOf' => ClassName::class, + + 'preferences' => [], + 'parameters' => [] + ] + ] +]); +``` + +## Type Preferences + +In many cases, you might be using interfaces as type hints as opposed to +concrete types. Even though type preferences are not limited to interfaces or abstract +class names, they provide hints to the injector on how such types should be resolved. + +The resolver will look up the name finally passed to the container in the following way +(the first match will be used): + +1. The preference defined in the type configuration of the class if it satifies + the typehint (implements, extends or aliasOf) +2. If there is a global preference defined and it satifies the typehint +3. Use the typehintet name directly ```php -$config = [ - 'definition' => [ - 'compiler' => [/* @todo compiler information */], - 'runtime' => [/* @todo runtime information */], - 'class' => [ - 'instantiator' => '', // the name of the instantiator, by default this is __construct - 'supertypes' => [], // an array of supertypes the class implements - 'methods' => [ - 'setSomeParameter' => [ // a method name - 'parameterName' => [ - 'name', // string parameter name - 'type', // type or null - 'is-required', // bool - ), - ), - ), - ), - ), -); + +// Assume the following classes are declared: + +interface FooInterface +{} + +class Foo implements FooInterface +{} + +class SpecialFoo implements FooInterface +{} + +class Bar +{} + +class MyClass +{ + public function __construct(FooInterface $foo) + { + // ... + } +} + +// With the following configuration: + +use Zend\Di\Injector; +use Zend\Di\Config; + +$injector = new Injector(new Config([ + 'preferences' => [ + FooInterface::class => Foo::class + ] + 'types' => [ + 'MyClass.A' => [ + 'typeOf' => MyClass::class + 'preferences' => [ + FooInterface::class => SpecialFoo::class, + ] + ], + 'MyClass.B' => [ + 'typeOf' => MyClass::class + 'preferences' => [ + FooInterface::class => Bar::class, + ] + ], + ] +]); + + +// The results are: +$a = $injector->create(MyClass::class); // Constructed with Foo +$b = $injector->create('MyClass.A'); // Constructed with SpecialFoo +$c = $injector->create('MyClass.B'); // Constructed with Foo (since Bar does not satisfy FooInterface) + +``` + + +## Parameters + +In contrast of type preferences, the resolver will not perform checks if the provided value +satisfies the required type. It will be used directly to inject the value. + +There are a couple of ways to define injections. + +* An IoC container service name as string: This is only possible if the required type is a + class or interface. For other types (scalar, iterable, callable, etc) or typeless parameters + the string value is passed __as is__. +* An instance of `Zend\Di\Resolver\ValueInjection`: Injects the value returned by `getValue()` + as is. +* An instance of `Zend\Di\Resolver\TypeInjection`: Obtains the injected value from the IoC + container by passing the return value of `getType()` to the container's `get()` method. +* The string literal `'*'`: This requests the injector to ignore any previously defined parameter + and use the type preference resolution as described in Type Preferences. +* Any other value will be used as is and encapsulated in a `Zend\Di\Resolver\ValueInjection`. + If the provided value's type does not fit the required parameter type, an exception is thrown. + +## Aliases + +Aliases allow you to configure the same class with different construction options. Aliases can +directly be created with the injector or declared as type preferences. + +An alias must refer to an actual class or an interface, therefore you cannot declare aliases for another alias. + +For example the following the following class should be instanciated in two different ways: + +```php + +// Assume the following classes are declared + +class Foo +{} + +class SpecialFoo extends Foo +{} + +class MyClass +{ + public function __construct(Foo $foo, string $bar) + { + // ... + } +} + +// With the following injection config: + +use Zend\Di\Injector; +use Zend\Di\Config; + +$injector = new Injector(new Config([ + 'types' => [ + MyClass::class => [ + 'parameters' => [ + 'foo' => SpecialFoo::class, + 'bar' => 'Stringvalue' + ] + ], + 'MyClass.Alias' => [ + 'typeOf' => MyClass::class, + 'parameters' => [ + 'foo' => '*', + 'bar' => 'Stringvalue' + ] + ] + ] +]); + + +// The results are: +$a = $injector->create(MyClass::class); // Constructed with SpecialFoo +$b = $injector->create('MyClass.Alias'); // Constructed with Foo (since there are no type preferences for Foo) ``` diff --git a/doc/book/cookbook/use-with-psr-containers.md b/doc/book/cookbook/use-with-psr-containers.md new file mode 100644 index 00000000..5ae3f3df --- /dev/null +++ b/doc/book/cookbook/use-with-psr-containers.md @@ -0,0 +1,63 @@ +# Usage with PSR-11 containers + +zend-di is designed to utilize and work with any IoC container thet implements the PSR-11 interface. +To achieve this you can pass the container instance as second parameter to the injector: + +```php +use Zend\Di\Injector; + +$injector = new Injector(null, $container); +``` + +From there on the injector will use the provided `$container` to obtain the dependencies. + +## Decorating the container + +In the example above the provided container may not utilize the injector to create unknown +instances, even when the classes are known to zend-di. It may fail with an exception that +dependencies could not be resolved. + +If you want to pair the container with the injector and use the injector for dependencies +the container it is not aware of, you may decorate the original container into a di aware implementation +as in the following example: + +```php + +namespace MyApp; + +use Zend\Di\Injector; +use Psr\Container\ContainerInterface; + +class MyContainer implements ContainerInterface +{ + private $wrapped; + + private $injector; + + public function __construct(ContainerInterface $wrappedContainer) + { + $this->wrapped = $wrappedContainer; + $this->injector = new Injector(null, $this); + } + + public function has($name) + { + retrun $this->wrapped->has($name) || $this->injector->canCreate($name); + } + + public function get($name) + { + if ($this->wrapped->has($name)) { + return $this->wrapped->get($name); + } + + $service = $this->injector->create($name); + + // You can make the service shared via the wrapped container + // or anyhow else ... + // $this->container->set($name, $service); + + return $service; + } +} +``` diff --git a/doc/book/cookbook/use-with-servicemanager.md b/doc/book/cookbook/use-with-servicemanager.md new file mode 100644 index 00000000..1f7df4c3 --- /dev/null +++ b/doc/book/cookbook/use-with-servicemanager.md @@ -0,0 +1,35 @@ +# Usage With Zend ServiceManager + +zend-di is designed to play and integrate well with zend-servicemanager. +When you are using [zend-component-installer](https://docs.zendframework.com/zend-component-installer/), +you just need to install zend-di via composer and you're done. + +## Service Factories For DI instances + +zend-di ships with two service factories to provide the `Zend\Di\InjectorInterface` implementation. + +* `Zend\Di\Container\ConfigFactory`: Creates a config instance by using the `"config"` service. +* `Zend\Di\Container\InjectorFactory`: Creates the injector instance that uses a + `Zend\Di\ConfigInterface` service, if available. + +```php + +use Zend\Di; +use Zend\Di\Container; + +$serviceManager->setFactory(Di\ConfigInterface::class, Container\ConfigFactory::class); +$serviceManager->setFactory(Di\InjectorInterface::class, Container\InjectorFactory::class); +``` + +## Abstract/Generic Service Factory + +This component ships with an generic factory `Zend\Di\Container\AutowireFactory`. This factory +is suitable as abstract service factory for zend-servicemanager. + +You can also use it to create instances with di using an IoC container (e.g. inside a service factory): + +```php +use Zend\Di\Container\AutowireFactory; +(new AutowireFactory())->__invoke($container, MyClassname::class); +``` + diff --git a/doc/book/index.html b/doc/book/index.html index 162fdcb9..5f0eaac3 100644 --- a/doc/book/index.html +++ b/doc/book/index.html @@ -1,8 +1,8 @@

zend-di

- -

Automated dependency injection and instance manager.

+ +

Automated dependency injection to implement IoC.

$ composer require zendframework/zend-di
diff --git a/doc/book/injector.md b/doc/book/injector.md new file mode 100644 index 00000000..ab8fccc7 --- /dev/null +++ b/doc/book/injector.md @@ -0,0 +1,122 @@ +# Injector + +The `Zend\Di\Injector` is responsible for creating instances by providing the +dependencies required by the class. + +The dependencies are resolved by analyzing the constructor parameters +of the requested class via reflection. For parameters defined with a +class or interface typehint, the configured preferences are taken into +account. + +A `Zend\Di\ConfigInterface` can be provided to configure the injector. +See the [Configuration](config.md) for details. + +## Create instances + +Instances can simply be created by calling `create()`: + +```php +use Zend\Di\Injector; + +$injector = new Injector() +$injector->create(MyClass::class); +``` + +## Create instances with parameters + +You can also pass construction parameters when calling create: + +```php + +$injector->create(MyDbAdapter::class, [ + 'username' => 'johndoe' +]); +``` +Parameters passed to `create()` will overwrite any configured injection for the +requested class. + +Generally the following behavior applies for parameter values that are no `ValueInjection` +or `TypeInjection` instance: + +* If the paramter has a class/interface typehint: + - string values will be wrapped into a `TypeInjection` + - objects are wrapped into a `ValueInjection` + - everything else will fail with an exception. +* If the paramter has a builtin typehint (e.g. string, int, callable, etc ...), the value will be + wrapped in a `ValueInjection`. +* If the parameter has no typehint at all the Value will be wrapped into a `ValueInjection` + + +Examples: + +```php + +// Assume the following classes +class Foo +{} + +class SpecialFoo extends Foo +{} + +class Bar +{ + public function __construct(Foo $foo, $type = null) + {} +} + +// Usage +use Zend\Di\Resolver\ValueInjection; +use Zend\Di\Resolver\TypeInjection; + +// Creates Bar with an instance of SpecialFoo form the ioc container: +$injector->create(Bar::class, [ + 'foo' => SpecialFoo::class, +]); + +// Creates Bar with the given instance of SpecialFoo bypassing the ioc container: +$injector->create(Bar::class, [ + 'foo' => ValueInjection(new SpecialFoo()) +]); + +// Creates Bar with an instance of Foo and the string literal 'SpecialFoo' for $type: +$injector->create(Bar::class, [ + 'type' => SpecialFoo::class +]); + +// Creates Bar with an instance of Foo and an instance of SpecialFoo from the ioc container for $type: +$injector->create(Bar::class, [ + 'type' => new TypeInjection(SpecialFoo::class) +]); +``` + +Refer to the Parameters section in the [Configuration](config.md) section for all +possibilities of how parameters can be declared. + + +## Check if a type is creatable + +When you use the injector in a factory or where ever you cannot be certain that +a provided type is potentially creatable by the injector (e.g. an alias), you can test it with +the `canCreate()` method. + +For example you consume the class name in a generic service factory for zend servicemanager: + +```php + +use Zend\Di\Injector; + +/** @var \Zend\ServiceManager\ServiceManager $serviceManager */ +$factory = function($container, $requestedName, array $options = null) { + $injector = $container->get(Injector::class); + + if (!$injector->canCreate($requestedName)) { + throw new \RuntimeException('Bad service name'); + } + + return $injector->create($requestedName, $options? : []); +}; + +$serviceManager->setFactory('Foo', $factory); +$serviceManager->setFactory('Bar', $factory); +$serviceManager->setFactory(stdClass::class, $factory); +``` diff --git a/doc/book/intro.md b/doc/book/intro.md index c4ce4add..db7c7dd0 100644 --- a/doc/book/intro.md +++ b/doc/book/intro.md @@ -13,34 +13,37 @@ $b = new MovieLister(new MovieFinder()); Above, `MovieFinder` is a dependency of `MovieLister`, and `MovieFinder` was injected into `MovieLister`. -If -you are not familiar with the concept of DI, here are a couple of great reads: +There are several forms of dependency injection: + +- Constructor injection +- Setter injection +- Property (or field) injection + +The zend-di component only implements constructor injection. At first to keep the implementation simple, +at second constructor injection has a non-obvious benefit: It enforces the order of initialization +(which helps to prevent circular dependencies) and the completeness of the instaciated object. + +If you are not familiar with the concept of DI, here are a couple of great reads: - [Matthew Weier O'Phinney's Analogy](http://weierophinney.net/matthew/archives/260-Dependency-Injection-An-analogy.html) - [Ralph Schindler's Learning DI](http://ralphschindler.com/2011/05/18/learning-about-dependency-injection-and-php) - [Fabien Potencier's Series](http://fabien.potencier.org/article/11/what-is-dependency-injection) on DI + > ### zend-servicemanager > -> `Zend\Di` is an example of an Inversion of Control (IoC) container. IoC containers are widely used -> to create object instances that have all dependencies resolved and injected. Dependency Injection -> containers are one form of IoC, but not the only form. -> -> Zend Framework ships with another form of IoC as well, -> [zend-servicemanager](https://zendframework.github.io/zend-servicemanager/). -> Unlike zend-di, zend-servicemanager is code-driven, meaning that you tell it -> what class to instantiate, or provide a factory for the given class. This -> approach offers several benefits: +> Since zend-di purely provides automatic DI (aka. auto wiring), it does not provide code-driven +> [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control) (IoC). > -> - Easier to debug (error stacks take you into your factories, not the -> dependency injection container). -> - Easier to setup (write code to instantiate objects, instead of -> configuration). -> - Faster (zend-di has known performance issues due to the approaches used). +> However, Zend Framework does ship with another IoC component as well: [zend-servicemanager](https://zendframework.github.io/zend-servicemanager/). +> Unlike zend-di, zend-servicemanager is code-driven, meaning that you tell it what class to instantiate, +> or provide a factory for the given class. This allows you a more detailed control on how your objects +> will be instanciated. > -> Unless you have specific needs for a dependency injection container versus -> more general Inversion of Control, we recommend using zend-servicemanager for -> the above reasons. +> In fact zend-di is designed to play nicely with other IoC containers that implement PSR-11, +> especially with zend-servicemanager. +> You can even use factories generated by zend-di for zend-servicemanager. + # Dependency Injection Containers @@ -48,7 +51,8 @@ When your code is written in such a way that all your dependencies are injected into consuming objects, you might find that the simple act of wiring an object has gotten more complex. When this becomes the case, and you find that this wiring is creating more boilerplate code, this makes for an excellent -opportunity to utilize a Dependency Injection Container. +opportunity to utilize a Dependency Injection Container. These containers are also +often referred to as IoC Containers. In it's simplest form, a Dependency Injection Container (here-in called a DiC for brevity), is an object that is capable of creating objects on request and @@ -57,6 +61,11 @@ requested objects. Since the patterns that developers employ in writing DI capable code vary, DiC's are generally either in the form of smallish objects that suit a very specific pattern, or larger DiC frameworks. -zend-di is a DiC framework. While for the simplest code there is no -configuration needed, and the use cases are quite simple, zend-di is capable of -being configured to wire these complex use cases +The PHP FIG defined a Standard-Interface for such DiCs: The [PSR-11](http://www.php-fig.org/psr/psr-11/) + +zend-di is a DiC framework which provides an injector performing the wiring and a +simple imlementation of a DiC. The injector is able to consume any PSR-11 Container, +such as [zend-servicemanager](https://zendframework.github.io/zend-servicemanager/), to obtain the instances of the dependencies. + +While for the simplest use cases, no configuration is needed, zend-di allows to +configure how to resolve dependencies for more complex use cases. diff --git a/doc/book/legacy/config.md b/doc/book/legacy/config.md new file mode 100644 index 00000000..89b76d9c --- /dev/null +++ b/doc/book/legacy/config.md @@ -0,0 +1,35 @@ +# Configuration + +> __NOTE:__ This is the documentation for the legacy version (2.x) of `Zend\Di` + +Most of the configuration for both the setup of `Definition`s as well as the +setup of the `InstanceManager` can be attained by a configuration file. This +file will produce an array (typically) and have an iterable structure. + +The top two keys are 'definition' and 'instance', each specifying values for +the definition setup and instance manager setup, respectively. + +The definition section expects the following information expressed as a PHP +array: + +```php +$config = [ + 'definition' => [ + 'compiler' => [/* @todo compiler information */], + 'runtime' => [/* @todo runtime information */], + 'class' => [ + 'instantiator' => '', // the name of the instantiator, by default this is __construct + 'supertypes' => [], // an array of supertypes the class implements + 'methods' => [ + 'setSomeParameter' => [ // a method name + 'parameterName' => [ + 'name', // string parameter name + 'type', // type or null + 'is-required', // bool + ), + ), + ), + ), + ), +); +``` diff --git a/doc/book/debugging-and-complex-use-cases.md b/doc/book/legacy/debugging-and-complex-use-cases.md similarity index 97% rename from doc/book/debugging-and-complex-use-cases.md rename to doc/book/legacy/debugging-and-complex-use-cases.md index 2bd52bf1..36cb93d9 100644 --- a/doc/book/debugging-and-complex-use-cases.md +++ b/doc/book/legacy/debugging-and-complex-use-cases.md @@ -1,5 +1,7 @@ # Debugging & Complex Use Cases +> __NOTE:__ This is the documentation for the legacy version (2.x) of `Zend\Di` + ## Debugging a DiC It is possible to dump the information contained within both the `Definition` diff --git a/doc/book/definitions.md b/doc/book/legacy/definitions.md similarity index 98% rename from doc/book/definitions.md rename to doc/book/legacy/definitions.md index a4f047e1..efeff95b 100644 --- a/doc/book/definitions.md +++ b/doc/book/legacy/definitions.md @@ -1,5 +1,7 @@ # Dependency Definitions +> __NOTE:__ This is the documentation for the legacy version (2.x) of `Zend\Di` + Definitions are what zend-di uses to understand the structure of the code it is attempting to wire. This means that if you've written non-ambiguous, clear and concise code, zend-di has a very good chance of understanding how to wire things diff --git a/doc/book/instance-manager.md b/doc/book/legacy/instance-manager.md similarity index 98% rename from doc/book/instance-manager.md rename to doc/book/legacy/instance-manager.md index 98b6af17..26532f1e 100644 --- a/doc/book/instance-manager.md +++ b/doc/book/legacy/instance-manager.md @@ -1,5 +1,7 @@ # Instance Manager +> __NOTE:__ This is the documentation for the legacy version (2.x) of `Zend\Di` + The `InstanceManager` is responsible for any runtime information associated with the zend-di DiC. This means that the information that goes into the instance manager is specific to both how the particular consuming application's needs, diff --git a/doc/book/migration.md b/doc/book/migration.md new file mode 100644 index 00000000..ecddb144 --- /dev/null +++ b/doc/book/migration.md @@ -0,0 +1,57 @@ +# Migration Guide + +Version 3 is the first new major release of zend-di, and contains a number of +backward incompatible changes. These were introduced to provide better performance +and stability. + +This guide describes how to migrate from Version 2 to 3. + +# What has changed? + +This lists the most impacting changes and potential pitfalls when +upgrading to `zend-di` version 3. + +* The injector now only supports constructor injections. If you require injections + based on Aware-Interfaces or Method-Injections, you need to provide it on your + own. You could do this by decorating the injector instance or using initializers + in zend-servicemanager. +* `\Zend\Di\Di` is renamed to `\Zend\Di\InjectorInterface`. It also is no longer + an IoC container which offers get/has. This is now offered by + `Zend\Di\DefaultContainer` which implements `Psr\Container\ContainerInterface`. + If you were using `\Zend\Di\Di` as IoC container please switch to + `Zend\Di\DefaultContainer` or use it with [zend-servicemanager](cookbook/use-with-servicemanager.md). +* All programmatic and array-based Definitions were dropped. If you need custom + definitions, you have to implement `\Zend\Di\Definition\DefinitionInterface`. +* The definition compiler was removed in favor of a [code generator](codegen.md), + which offers better performance. +* Added PHP 7.1 Typesafety. All Interfaces and Classes are strongly typed. +* `Generator` and `GeneratorInstance` in `Zend\Di\ServiceLocator` were removed + in favor of the [code generator](codegen.md), which creates zend-servicemanager + compatible factories. + +# Migrating from v2 to v3 with zend-mvc + +When you are using zend-mvc you can follow these steps to upgrade: + +1. Remove `zendframework/zend-servicemanager-di`from your composer.json +2. Change the version constraint for `zendframework/zend-di` to `^3.0` +3. Change the `Zend\ServiceManager\Di\Module` modules entry to `Zend\Di\Module` +4. If you are using any factory from zend-servicemanager-di, you may have to + replace it with `Zend\Di\Container\AutowireFactory` +5. Migrate your di config to the new [configuration format](config.md). + +# Migrating the configuration + +The configuration is now expected in `$config['dependencies']['auto']`, where +`$config` is your `config` service. + +The config service factory will automatically attempt to migrate legacy +configurations at runtime, which gives you some time to migrate your configs. +You can use `Zend\Di\LegacyConfig` to help migrating existing configs: + +```php +use Zend\Di\LegacyConfig; + +$migrated = new LegacyConfig($diConfigArray); +$code = var_export($migrated->toArray(), true); +``` diff --git a/doc/book/psr-11.md b/doc/book/psr-11.md new file mode 100644 index 00000000..c7248b8f --- /dev/null +++ b/doc/book/psr-11.md @@ -0,0 +1,8 @@ +# PSR-11 Support + +zend-di stating from version 3.0 and up utilizes the [psr/container](https://github.com/php-fig/container) +interfaces. It supports any implementation to obtain the instances of the resolved dependencies. + +zend-di ships with a very basic implementation of the container interface which only uses the +injector to creates instances and always shares once created services. We suggest you replace it +with another implementation like [zend-servicemanager](https://docs.zendframework.com/zend-servicemanager/) for more flexibility. diff --git a/doc/book/quick-start.md b/doc/book/quick-start.md index 671be2b2..7e8d3d2b 100644 --- a/doc/book/quick-start.md +++ b/doc/book/quick-start.md @@ -1,138 +1,58 @@ # Quick Start -This quick start is intended to get developers familiar with the concepts of the -zend-di DiC. Generally speaking, code is never as simple as it is inside this -example, so working knowledge of the other sections of the manual is suggested. +The DI component provides a simple and easy-to-use auto wiring strategy which implements +[constructor injection](https://en.wikipedia.org/wiki/Dependency_injection#Constructor_injection). -Assume, for a moment, the following application code. It already assumes that -dependencies are injected, and so becomes a good candiate for a DiC. +It utilizes PSR-11 Containers to obtain required services, so it can be paired with any IoC container +that implements this interface such as [zend-servicemanager](https://docs.zendframework.com/zend-servicemanager/). -```php -namespace MyLibrary -{ - class DbAdapter - { - protected $username = null; - protected $password = null; - - public function __construct($username, $password) - { - $this->username = $username; - $this->password = $password; - } - } -} - -namespace MyMovieApp -{ - class MovieFinder - { - protected $dbAdapter = null; - - public function __construct(\MyLibrary\DbAdapter $dbAdapter) - { - $this->dbAdapter = $dbAdapter; - } - } - - class MovieLister - { - protected $movieFinder = null; - - public function __construct(MovieFinder $movieFinder) - { - $this->movieFinder = $movieFinder; - } - } -} -``` +## 1. Installation -With the above code, you find yourself writing the following to wire and utilize -it: +If you haven't already, [install Composer](https://getcomposer.org/). +Once you have, you can install the service manager: -```php -// $config object is assumed - -$dbAdapter = new MyLibrary\DbAdapter($config->username, $config->password); -$movieFinder = new MyMovieApp\MovieFinder($dbAdapter); -$movieLister = new MyMovieApp\MovieLister($movieFinder); -foreach ($movieLister as $movie) { - // iterate and display $movie -} +```bash +$ composer install zendframework/zend-di ``` -If you are doing this above wiring in each controller or view that wants to list -movies, not only can this become repetitive and boring to write, but also -unmaintainable if you want to swap out one of these dependencies on a wholesale -scale. - -Since this example of code already practices good dependency injection using -constructor injection, it is a great candidate for using zend-di. +## 2. Configuring the injector -The following demonstrates how to wire the above into a zend-di container: +Cou can now create and configure an injector instance. The injector accepts an instance of +`Zend\Di\ConfigInterface`. This can be provided by passing `Zend\Di\Config`, which accepts a simple array: ```php -// Inside a bootstrap somewhere -$di = new Zend\Di\Di(); -$di->instanceManager()->setParameters('MyLibrary\DbAdapter', [ - 'username' => $config->username, - 'password' => $config->password, -]); - -// Elsewhere: -$movieLister = $di->get('MyMovieApp\MovieLister'); -foreach ($movieLister as $movie) { - // iterate and display $movie -} +use Zend\Di\Injector; +use Zend\Di\Config; + +$injector = new Injector(new Config([ + 'preferences' => [ + MyInterface::class => MyImplementation::class + ] +])); ``` -In the above example, we are obtaining a *default instance* of `Zend\Di\Di`. By -'default', we mean that `Zend\\Di\\Di` is constructed with a `DefinitionList` -seeded with a `RuntimeDefinition` (which uses PHP's Reflection API) and an empty -instance manager and no configuration: +This config implementation accepts a veriety of options. Refer to the [Configuration](config.md) section for +full details. + +## 3. Creating instances + +Finally you can create new instances of a specific class or alias by using the `create()` method: ```php -public function __construct( - DefinitionList $definitions = null, - InstanceManager $instanceManager = null, - Configuration $config = null -) { - $this->definitions = ($definitions) ?: new DefinitionList(new Definition\RuntimeDefinition()); - $this->instanceManager = ($instanceManager) ?: new InstanceManager(); - - if ($config) { - $this->configure($config); - } -} +$instance = $injector->create(MyClass::class); ``` -This means that when `$di->get()` is called, it will be consulting the -`RuntimeDefinition`, which uses Reflection to understand the structure of the -code. Once it knows the structure of the code, it can then know how the -dependencies fit together and how to go about wiring your objects for you. -`Zend\Di\Definition\RuntimeDefinition` will utilize the names of the parameters -in the methods as the class parameter names. This is how both the `username` and -`password` keys are mapped to the first and second parameters, respectively, of -the constructor consuming these named parameters. +The only precondition is: The class you are passing to create must exist (or be autoloadable). +If this is not the case, the injector will fail with an exception. -If you were to want to pass in the username and password at call time, this is -achieved by passing them as the second argument to `get()`: +The `create()` call will _always_ create a new instance of the given class. If you +need a shared instance, you can utilize the associated IoC container, which implements the PSR-11 interface: ```php -$di = new Zend\Di\Di(); -$movieLister = $di->get('MyMovieApp\MovieLister', [ - 'username' => $config->username, - 'password' => $config->password -]); -foreach ($movieLister as $movie) { - // iterate and display $movie -} +$sharedInstance = $di->getContainer()->get(MyClass::class); ``` -It is important to note that when using call time parameters, these parameter -names will be applied to any class that accepts a parameter of such name. - -By calling `$di->get()`, this instance of `MovieLister` will be automatically -shared. This means subsequent calls to `get()` will return the same instance as -previous calls. If you wish to have completely new instances of `MovieLister`, -you can utilize `$di->newInstance()`. +The default container implementation is very limited and you should use one that provides more features like +[Zend ServiceManager](https://docs.zendframework.com/zend-servicemanager/). Refer to the +[Usage with PSR-Containers](cookbook/use-with-psr-containers.md) and +[Usage with Zend ServiceManager](cookbook/use-with-servicemanager.md) sections for details. diff --git a/mkdocs.yml b/mkdocs.yml index d90ee8c0..0cefcf51 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,10 +5,19 @@ pages: - Intro: intro.md - "Quick Start": quick-start.md - Reference: - - Definitions: definitions.md - - "Instance Manager": instance-manager.md + - "PSR-11 Support": psr-11.md - Configuration: config.md - - "Debugging and Complex Use Cases": debugging-and-complex-use-cases.md + - "Injector": injector.md + - "Code Generator": codegen.md + - "Legacy (v2)": + - Definitions: legacy/definitions.md + - "Instance Manager": legacy/instance-manager.md + - Configuration: legacy/config.md + - "Debugging And Complex Use Cases": legacy/debugging-and-complex-use-cases.md + - Cookbook: + - "Usage With PSR-11 Containers": cookbook/use-with-psr-containers.md + - "Usage With Zend ServiceManager": cookbook/use-with-servicemanager.md + - "Migration Guide": migration.md site_name: zend-di site_description: zend-di repo_url: 'https://github.com/zendframework/zend-di' diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..9843b449 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,10 @@ + + + + + + src + test + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9851a1f4..ee0ce17d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ @@ -23,13 +23,5 @@ - - - - - diff --git a/src/CodeGenerator/AbstractInjector.php b/src/CodeGenerator/AbstractInjector.php new file mode 100644 index 00000000..a7bc3e34 --- /dev/null +++ b/src/CodeGenerator/AbstractInjector.php @@ -0,0 +1,88 @@ +injector = $injector; + $this->container = $container ? : new DefaultContainer($this); + + $this->loadFactoryList(); + } + + /** + * Init factory list + */ + abstract protected function loadFactoryList(); + + /** + * @param string $type + * @return \Zend\Di\CodeGenerator\FactoryInterface + */ + private function getFactory($type): FactoryInterface + { + if (\is_string($this->factories[$type])) { + $factory = $this->factories[$type]; + $this->factories[$type] = new $factory(); + } + + return $this->factories[$type]; + } + + /** + * {@inheritDoc} + * @see \Zend\Di\InjectorInterface::canCreate() + */ + public function canCreate(string $name): bool + { + return (isset($this->factories[$name]) || $this->injector->canCreate($name)); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\InjectorInterface::create() + */ + public function create(string $name, array $options = []) + { + if (isset($this->factories[$name])) { + return $this->getFactory($name)->create($this->container, $options); + } + + return $this->injector->create($name, $options); + } +} diff --git a/src/CodeGenerator/AutoloadGenerator.php b/src/CodeGenerator/AutoloadGenerator.php new file mode 100644 index 00000000..8a68ee50 --- /dev/null +++ b/src/CodeGenerator/AutoloadGenerator.php @@ -0,0 +1,92 @@ +namespace = $namespace; + } + + private function generateAutoloaderClass(array &$classmap) + { + $class = new ClassGenerator('Autoloader'); + $classmapValue = new PropertyValueGenerator( + $classmap, + PropertyValueGenerator::TYPE_ARRAY_SHORT, + PropertyValueGenerator::OUTPUT_MULTIPLE_LINE + ); + + $registerCode = 'if (!$this->registered) {'.PHP_EOL + . ' spl_autoload_register($this);'.PHP_EOL + . ' $this->registered = true;'.PHP_EOL + . '}'.PHP_EOL + . 'return $this;'; + + $unregisterCode = 'if ($this->registered) {'.PHP_EOL + . ' spl_autoload_unregister($this);'.PHP_EOL + . ' $this->registered = false;'.PHP_EOL + . '}'.PHP_EOL + . 'return $this;'; + + $loadCode = 'if (isset($this->classmap[$class])) {'.PHP_EOL + . ' include __DIR__ . \'/\' . $this->classmap[$class];'.PHP_EOL + . '}'; + + $class->addProperty('registered', false, PropertyGenerator::FLAG_PRIVATE) + ->addProperty('classmap', $classmapValue, PropertyGenerator::FLAG_PRIVATE) + ->addMethod('register', [], MethodGenerator::FLAG_PUBLIC, $registerCode) + ->addMethod('unregister', [], MethodGenerator::FLAG_PUBLIC, $unregisterCode) + ->addMethod('load', ['class'], MethodGenerator::FLAG_PUBLIC, $loadCode) + ->addMethod('__invoke', ['class'], MethodGenerator::FLAG_PUBLIC, '$this->load($class);'); + + $file = new FileGenerator(); + $file->setDocBlock(new DocBlockGenerator('Generated autoloader for Zend\Di')) + ->setNamespace($this->namespace) + ->setClass($class) + ->setFilename($this->outputDirectory . '/Autoloader.php'); + + $file->write(); + } + + /** + * @param array $classmap + */ + public function generate(array &$classmap) + { + $this->ensureOutputDirectory(); + $this->generateAutoloaderClass($classmap); + + $code = "require_once __DIR__ . '/Autoloader.php';\n" + . 'return (new Autoloader())->register();'; + + $file = new FileGenerator(); + $file->setDocBlock(new DocBlockGenerator('Generated autoload file for Zend\Di')) + ->setNamespace($this->namespace) + ->setBody($code) + ->setFilename($this->outputDirectory.'/autoload.php') + ->write(); + } +} diff --git a/src/CodeGenerator/FactoryGenerator.php b/src/CodeGenerator/FactoryGenerator.php new file mode 100644 index 00000000..e0ec236c --- /dev/null +++ b/src/CodeGenerator/FactoryGenerator.php @@ -0,0 +1,242 @@ +resolver = $resolver; + $this->config = $config; + $this->namespace = $namespace ? : 'ZendDiGenerated'; + } + + /** + * @param string $name + * @return string + */ + protected function buildClassName(string $name) + { + return preg_replace('~[^a-z0-9\\\\]+~i', '_', $name) . 'Factory'; + } + + /** + * @param string $name + * @return string + */ + protected function buildFileName(string $name) + { + $name = $this->buildClassName($name); + return str_replace('\\', '/', $name) . '.php'; + } + + /** + * @param string $type + * @return string|unknown + */ + private function getClassName(string $type): string + { + if ($this->config->isAlias($type)) { + return $this->config->getClassForAlias($type); + } + + return $type; + } + + /** + * Builds the code for constructor parameters + * + * @param string $type The type name to build for + */ + private function buildParametersCode(string $type) + { + $params = $this->resolver->resolveParameters($type); + $names = []; + + $withOptions = []; + $withoutOptions = []; + + /** @var AbstractInjection $injection */ + foreach ($params as $injection) { + if (! $injection->isExportable()) { + return false; + } + + $name = $injection->getParameterName(); + $variable = '$p_' . $name; + $code = $injection->export(); + + if ($injection instanceof TypeInjection) { + $code = '$container->get(' . $code . ')'; + } + + // build for two cases: + // 1. Parameters are passed at call time + // 2. No Parameters were passed at call time (might be slightly faster) + $names[] = $variable; + $withoutOptions[] = sprintf('%s = %s;', $variable, $code); + $withOptions[] = sprintf( + '%1$s = array_key_exists(%3$s, $options)? $options[%3$s] : %2$s;', + $variable, + $code, + var_export($name, true) + ); + } + + $intention = 4; + $tab = str_repeat(' ', $intention); + $code = ''; + + if (count($withOptions)) { + // Build conditional initializer code: + // If no $params were provided ignore it completely + // otherwise check if there is a value for each dependency in $params. + $code = 'if (empty($options)) {' . "\n" + . $tab . implode("\n$tab", $withoutOptions) . "\n" + . '} else {' . "\n" + . $tab . implode("\n$tab", $withOptions) + . "\n}\n\n"; + } + + return [$names, $code]; + } + + /** + * @param string $type + * @return string|false + */ + private function buildCreateMethodBody(string $type) + { + $class = $this->getClassName($type); + $result = $this->buildParametersCode($type); + + // The resolver was unable to deliver and somehow the instantiator + // was not considered a requirement. Whatever caused this, it's not acceptable here + if (! $result) { + return false; + } + + list($paramNames, $paramsCode) = $result; + + // Decide if new or static method call should be used + $absoluteClassName = '\\' . $class; + $invokeCode = sprintf('new %s(%s)', $absoluteClassName, implode(', ', $paramNames)); + + return $paramsCode . "return $invokeCode;\n"; + } + + private function buildInvokeMethod(ClassGenerator $generator) + { + $code = 'if (is_string($options)) {' . PHP_EOL + . ' $options = $zfCompatibleOptions;' . PHP_EOL + . '}' . PHP_EOL.PHP_EOL + . 'return $this->create($container, $options);'; + + $args = [ + new ParameterGenerator('container', ContainerInterface::class), + new ParameterGenerator('options', null, []), + new ParameterGenerator('zfCompatibleOptions', 'array', []) + ]; + + $generator->addMethod('__invoke', $args, MethodGenerator::FLAG_PUBLIC, $code); + } + + /** + * @param string $class + * @return bool + */ + public function generate(string $class) + { + $createBody = $this->buildCreateMethodBody($class); + + if (! $createBody || ! $this->outputDirectory) { + return false; + } + + $factoryClassName = $this->namespace . '\\' . $this->buildClassName($class); + $generator = new ClassGenerator($factoryClassName); + $comment = 'Generated factory for ' . $class; + + $generator->setImplementedInterfaces(['\\' . FactoryInterface::class]); + $generator->setDocBlock(new DocBlockGenerator($comment)); + $generator->setFinal(true); + $generator->addMethod('create', [ + new ParameterGenerator('container', ContainerInterface::class), + new ParameterGenerator('options', 'array', []) + ], MethodGenerator::FLAG_PUBLIC, $createBody); + + $this->buildInvokeMethod($generator); + + $filename = $this->buildFileName($class); + $filepath = $this->outputDirectory . '/' . $filename; + $file = new FileGenerator(); + + $this->ensureDirectory(dirname($filepath)); + + $file->setFilename($filepath) + ->setDocBlock(new DocBlockGenerator($comment)) + ->setNamespace($generator->getNamespaceName()) + ->setClass($generator) + ->write(); + + $this->classmap[$factoryClassName] = $filename; + return $factoryClassName; + } + + /** + * @return array + */ + public function getClassmap(): array + { + return $this->classmap; + } +} diff --git a/src/LocatorInterface.php b/src/CodeGenerator/FactoryInterface.php similarity index 53% rename from src/LocatorInterface.php rename to src/CodeGenerator/FactoryInterface.php index 3dfa2ccc..5590c950 100644 --- a/src/LocatorInterface.php +++ b/src/CodeGenerator/FactoryInterface.php @@ -7,10 +7,17 @@ * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace Zend\Di; +namespace Zend\Di\CodeGenerator; -use Interop\Container\ContainerInterface; +use Psr\Container\ContainerInterface; -interface LocatorInterface extends ContainerInterface +interface FactoryInterface { + /** + * Create an instance + * + * @param array $options + * @return object + */ + public function create(ContainerInterface $container, array $options); } diff --git a/src/CodeGenerator/GeneratorTrait.php b/src/CodeGenerator/GeneratorTrait.php new file mode 100644 index 00000000..c03f6a83 --- /dev/null +++ b/src/CodeGenerator/GeneratorTrait.php @@ -0,0 +1,82 @@ +mode, true)) { + throw new GenerateCodeException('Could not create output directory: ' . $this->outputDirectory); + } + } + + /** + * Ensures the existence of the output directory + * + * @throws LogicException + * @throws GenerateCodeException + */ + protected function ensureOutputDirectory() + { + if (! $this->outputDirectory) { + throw new LogicException('Cannot generate code without output directory'); + } + + $this->ensureDirectory($this->outputDirectory); + } + + /** + * Set the output directory + * + * You should configure a psr-4 autoloader with the namespace `Zend\Di\Generated` + * to src/ in this directory. + * + * The compiler will attempt to create this directory if it does not exist + * + * @param string $dir The path to the output directory + * @param int $mode The creation mode for the directory + * @return self Provides a fluent interface + */ + public function setOutputDirectory(string $dir, ?int $mode = null): self + { + $this->outputDirectory = $dir; + + if ($mode !== null) { + $this->mode = $mode; + } + + return $this; + } +} diff --git a/src/CodeGenerator/InjectorGenerator.php b/src/CodeGenerator/InjectorGenerator.php new file mode 100644 index 00000000..59cecc9a --- /dev/null +++ b/src/CodeGenerator/InjectorGenerator.php @@ -0,0 +1,175 @@ +config = $config; + $this->resolver = $resolver; + $this->namespace = $namespace ? : 'Zend\Di\Generated'; + $this->factoryGenerator = new FactoryGenerator($config, $resolver, $this->namespace . '\Factory'); + $this->autoloadGenerator = new AutoloadGenerator($this->namespace); + } + + /** + * Generate injector + * + * @param array $factories + */ + private function generateInjector(array $factories) + { + $listFile = new FileGenerator(); + $listFile->setFilename($this->outputDirectory . '/factories.php') + ->setDocBlock(new DocBlockGenerator('AUTO GENERATED FACTORY LIST')) + ->setBody('return ' . var_export($factories, true) . ';'); + + $class = new ClassGenerator('GeneratedInjector', $this->namespace); + $classFile = new FileGenerator(); + + $loadFactoryCode = '$this->factories = require __DIR__ . \'/factories.php\';'; + $class->setExtendedClass('\\' . AbstractInjector::class) + ->addMethod('loadFactoryList', [], MethodGenerator::FLAG_PUBLIC, $loadFactoryCode); + + $classFile->setFilename($this->outputDirectory . '/GeneratedInjector.php') + ->setDocBlock(new DocBlockGenerator('AUTO GENERATED DEPENDENCY INJECTOR')) + ->setNamespace($class->getNamespaceName()) + ->setClass($class); + + $listFile->write(); + $classFile->write(); + } + + /** + * @param string $class + * @param array $factories + */ + private function generateTypeFatory(string $class, array &$factories) + { + if (isset($factories[$class])) { + continue; + } + + try { + $factory = $this->factoryGenerator->generate($class); + + if ($factory) { + $factories[$class] = $factory; + } + } catch (\Exception $e) { + // TODO: logging/notifying ... + } + } + + /** + * @return void + */ + private function generateAutoload() + { + $addFactoryPrefix = function ($value) { + return 'Factory/' . $value; + }; + + $classmap = array_map($addFactoryPrefix, $this->factoryGenerator->getClassmap()); + $classmap[$this->namespace . '\\GeneratedInjector'] = 'GeneratedInjector.php'; + + $this->autoloadGenerator->generate($classmap); + } + + /** + * Generate the injector + * + * This will generate the injector and its factories into the output directory + * + * @param string[] $classes + */ + public function generate($classes = []) + { + $this->ensureOutputDirectory(); + $this->factoryGenerator->setOutputDirectory($this->outputDirectory . '/Factory'); + $this->autoloadGenerator->setOutputDirectory($this->outputDirectory); + $factories = []; + + foreach ($classes as $class) { + $this->generateTypeFatory((string)$class, $factories); + } + + foreach ($this->config->getConfiguredTypeNames() as $type) { + $this->generateTypeFatory($type, $factories); + } + + $this->generateAutoload(); + $this->generateInjector($factories); + } +} diff --git a/src/Config.php b/src/Config.php index a9a7f9d4..0fe91b2b 100644 --- a/src/Config.php +++ b/src/Config.php @@ -9,188 +9,230 @@ namespace Zend\Di; -use Traversable; -use Zend\Di\Definition\ArrayDefinition; -use Zend\Di\Definition\RuntimeDefinition; -use Zend\Stdlib\ArrayUtils; - /** - * Configures Di instances + * Provides a DI configuration from an array + * + * This configures the instanciation process of the dependency injector + * + * **Example:** + * ```php + * return [ + * // This section provides global type preferences + * // Those are visited if a specific instance has no preference definions + * 'preferences' => [ + * // The key is the requested class or interface name, the values are + * // the types the dependency injector should prefer + * Some\Interface::class => Some\Preference::class + * ], + * // This configures the instanciation of specific types + * // Types may also be purely virtual by defining the aliasOf key. + * 'types' => [ + * My\Class::class => [ + * 'preferences' => [ + * // this superseds the global type preferences + * // when My\Class is instanciated + * Some\Interface::class => 'My.SpecificAlias' + * ], + * + * // Instanciation paramters. These will only be used for + * // the instantiator (i.e. the constructor) + * 'parameters' => [ + * 'foo' => My\FooImpl::class, // Use the given type to provide the injection (depends on definition) + * 'bar' => '*' // Use the type preferences + * ], + * ], + * + * 'My.Alias' => [ + * // typeOf defines virtual classes which can be used as type perferences or for + * // newInstance calls. They allow providing a different configs for a class + * 'typeOf' => Some\Class::class, + * 'preferences' => [ + * Foo::class => Bar::class + * ] + * ] + * ] + * ]; + * ``` + * + * ## Notes on Injections + * + * Named arguments and Automatic type lookups will only work for Methods that are known to the dependency injector + * through its definitions. Injections for unknown methods do not perform type lookups on its own. + * + * A value injection without any lookups can be forced by providing a Resolver\ValueInjection instance. + * + * To force a service/class instance provide a Resolver\TypeInjection instance. For classes known from + * the definitions, a type preference might be the better approach + * + * @see Zend\Di\Resolver\ValueInjection A container to force injection of a value + * @see Zend\Di\Resolver\TypeInjection A container to force looking up a specific type instance for injection */ -class Config +class Config implements ConfigInterface { /** * @var array */ - protected $data = []; + protected $preferences = []; + + /** + * @var array + */ + protected $types = []; /** - * Constructor + * Construct from option array + * + * Utilizes the given options array or traversable. * - * @param array|Traversable $options + * @param array|\ArrayAccess $options The options array. Traversables + * will be converted to an array + * internally * @throws Exception\InvalidArgumentException */ - public function __construct($options) + public function __construct($options = []) { - if ($options instanceof Traversable) { - $options = ArrayUtils::iteratorToArray($options); - } - - if (!is_array($options)) { + if (! is_array($options) && ! ($options instanceof \ArrayAccess)) { throw new Exception\InvalidArgumentException( - 'Config data must be of type Traversable or an array' + 'Config data must be of type array or array access' ); } - $this->data = $options; + + $this->preferences = $this->getDataFromArray($options, 'preferences')?: []; + $this->types = $this->getDataFromArray($options, 'types')?: []; + } + + /** + * @param array $data + * @param string $key + * @return array|\ArrayAccess|null + */ + private function getDataFromArray($data, $key) + { + if (! isset($data[$key]) || (! is_array($data[$key]) && ! ($data[$key] instanceof \ArrayAccess))) { + return null; + } + + return $data[$key]; } /** - * Configure + * {@inheritDoc} + * @see \Zend\Di\ConfigInterface::getClassForAlias() + */ + public function getClassForAlias(string $name): ?string + { + if (isset($this->types[$name]['typeOf'])) { + return $this->types[$name]['typeOf']; + } + + return null; + } + + /** + * Returns the instanciation paramters for the given type * - * @param Di $di - * @return void + * @param string $type The alias or class name + * @return array The configured parameters + */ + public function getParameters(string $type): array + { + if (! isset($this->types[$type]['parameters']) || ! is_array($this->types[$type]['parameters'])) { + return []; + } + + return $this->types[$type]['parameters']; + } + + /** + * {@inheritDoc} + * @see \Zend\Di\ConfigInterface::setParameters() */ - public function configure(Di $di) + public function setParameters(string $type, array $params) { - if (isset($this->data['definition'])) { - $this->configureDefinition($di, $this->data['definition']); + $this->types[$type]['parameters'] = $params; + return $this; + } + + /** + * @param string $type + * @param string $context + * @return string|null + */ + public function getTypePreference(string $type, ?string $context = null): ?string + { + if ($context) { + return $this->getTypePreferenceForClass($type, $context); } - if (isset($this->data['instance'])) { - $this->configureInstance($di, $this->data['instance']); + + if (! isset($this->preferences[$type])) { + return null; } + + $preference = $this->preferences[$type]; + return ($preference != '') ? (string)$preference : null; } /** - * @param Di $di - * @param array $definition + * {@inheritDoc} + * @see \Zend\Di\ConfigInterface::getTypePreferencesForClass() */ - public function configureDefinition(Di $di, $definition) + private function getTypePreferenceForClass(string $type, ?string $context): ?string { - foreach ($definition as $definitionType => $definitionData) { - switch ($definitionType) { - case 'compiler': - foreach ($definitionData as $filename) { - if (is_readable($filename)) { - $di->definitions()->addDefinition(new ArrayDefinition(include $filename), false); - } - } - break; - case 'runtime': - if (isset($definitionData['enabled']) && !$definitionData['enabled']) { - // Remove runtime from definition list if not enabled - $definitions = []; - foreach ($di->definitions() as $definition) { - if (!$definition instanceof RuntimeDefinition) { - $definitions[] = $definition; - } - } - $definitionList = new DefinitionList($definitions); - $di->setDefinitionList($definitionList); - } elseif (isset($definitionData['use_annotations']) && $definitionData['use_annotations']) { - /* @var $runtimeDefinition Definition\RuntimeDefinition */ - $runtimeDefinition = $di - ->definitions() - ->getDefinitionByType('\Zend\Di\Definition\RuntimeDefinition'); - $runtimeDefinition->getIntrospectionStrategy()->setUseAnnotations(true); - } - break; - case 'class': - foreach ($definitionData as $className => $classData) { - $classDefinitions = $di->definitions()->getDefinitionsByType('Zend\Di\Definition\ClassDefinition'); - foreach ($classDefinitions as $classDefinition) { - if (!$classDefinition->hasClass($className)) { - unset($classDefinition); - } - } - if (!isset($classDefinition)) { - $classDefinition = new Definition\ClassDefinition($className); - $di->definitions()->addDefinition($classDefinition, false); - } - foreach ($classData as $classDefKey => $classDefData) { - switch ($classDefKey) { - case 'instantiator': - $classDefinition->setInstantiator($classDefData); - break; - case 'supertypes': - $classDefinition->setSupertypes($classDefData); - break; - case 'methods': - case 'method': - foreach ($classDefData as $methodName => $methodInfo) { - if (isset($methodInfo['required'])) { - $classDefinition->addMethod($methodName, $methodInfo['required']); - unset($methodInfo['required']); - } - foreach ($methodInfo as $paramName => $paramInfo) { - $classDefinition->addMethodParameter($methodName, $paramName, $paramInfo); - } - } - break; - default: - $methodName = $classDefKey; - $methodInfo = $classDefData; - if (isset($classDefData['required'])) { - $classDefinition->addMethod($methodName, $methodInfo['required']); - unset($methodInfo['required']); - } - foreach ($methodInfo as $paramName => $paramInfo) { - $classDefinition->addMethodParameter($methodName, $paramName, $paramInfo); - } - } - } - } - } + if (! isset($this->types[$context]['preferences'][$type])) { + return null; } + + $preference = $this->types[$context]['preferences'][$type]; + return ($preference != '') ? (string)$preference : null; } /** - * Configures a given Di instance - * - * @param Di $di - * @param $instanceData + * {@inheritDoc} + * @see \Zend\Di\ConfigInterface::isAlias() */ - public function configureInstance(Di $di, $instanceData) + public function isAlias(string $name): bool { - $im = $di->instanceManager(); - - foreach ($instanceData as $target => $data) { - switch (strtolower($target)) { - case 'aliases': - case 'alias': - foreach ($data as $n => $v) { - $im->addAlias($n, $v); - } - break; - case 'preferences': - case 'preference': - foreach ($data as $n => $v) { - if (is_array($v)) { - foreach ($v as $v2) { - $im->addTypePreference($n, $v2); - } - } else { - $im->addTypePreference($n, $v); - } - } - break; - default: - foreach ($data as $n => $v) { - switch ($n) { - case 'parameters': - case 'parameter': - $im->setParameters($target, $v); - break; - case 'injections': - case 'injection': - $im->setInjections($target, $v); - break; - case 'shared': - case 'share': - $im->setShared($target, $v); - break; - } - } - } + return isset($this->types[$name]['typeOf']); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\ConfigInterface::getConfiguredTypeNames() + */ + public function getConfiguredTypeNames(): array + { + return array_keys($this->types); + } + + /** + * @param string $type + * @param string $preference + * @param string $context + */ + public function setTypePreference(string $type, string $preference, ?string $context = null): self + { + if ($context) { + $this->types[$context]['preferences'][$type] = $preference; + } else { + $this->preferences[$type] = $preference; } + + return $this; + } + + /** + * @param string $name The name of the alias + * @param string $class The class name this alias points to + * @throws Exception\ClassNotFoundException When `$class` does not exist + * @return self + */ + public function setAlias(string $name, string $class): self + { + if (! class_exists($class) && ! interface_exists($class)) { + throw new Exception\ClassNotFoundException('Could not find class "' . $class . '"'); + } + + $this->types[$name]['typeOf'] = $class; + return $this; } } diff --git a/src/ConfigInterface.php b/src/ConfigInterface.php new file mode 100644 index 00000000..631fb8e0 --- /dev/null +++ b/src/ConfigInterface.php @@ -0,0 +1,64 @@ + $this->getDependencyConfig() + ]; + } + + /** + * Returns the dependency (service manager) configuration + * + * @return array + */ + public function getDependencyConfig(): array + { + return [ + 'factories' => [ + InjectorInterface::class => Container\InjectorFactory::class, + ConfigInterface::class => Container\ConfigFactory::class + ], + 'abstract_factories' => [ + Container\AutowireFactory::class + ] + ]; + } +} diff --git a/src/Container/AutowireFactory.php b/src/Container/AutowireFactory.php new file mode 100644 index 00000000..e304a989 --- /dev/null +++ b/src/Container/AutowireFactory.php @@ -0,0 +1,69 @@ +get(InjectorInterface::class); + + if (! $injector instanceof InjectorInterface) { + throw new Exception\RuntimeException( + 'Could not get a dependency injector form the container implementation' + ); + } + + return $injector; + } + + /** + * Check creatability of the requested name + */ + public function canCreate(ContainerInterface $container, $requestedName) + { + if (! $container->has(InjectorInterface::class)) { + return false; + } + + return $this->getInjector($container)->canCreate((string)$requestedName); + } + + /** + * Create an instance + */ + public function create(ContainerInterface $container, string $requestedName, ?array $options = null) + { + return $this->getInjector($container)->create($requestedName, $options ? : []); + } + + /** + * Make invokable and implement the zend-service factory pattern + */ + public function __invoke(ContainerInterface $container, $requestedName, array $options = null) + { + return $this->create($container, (string)$requestedName, $options); + } +} diff --git a/src/Container/ConfigFactory.php b/src/Container/ConfigFactory.php new file mode 100644 index 00000000..22dd7721 --- /dev/null +++ b/src/Container/ConfigFactory.php @@ -0,0 +1,48 @@ +has('config') ? $container->get('config') : []; + $data = (isset($config['dependencies']['auto'])) ? $config['dependencies']['auto'] : []; + + if (isset($config['di'])) { + trigger_error('Detected legacy DI configuration, please upgrade to v3.', E_USER_DEPRECATED); + + $legacyConfig = new LegacyConfig($config['di']); + $data = array_merge_recursive($legacyConfig->toArray(), $data); + } + + return new Config($data); + } + + /** + * Make the instance invokable + */ + public function __invoke(ContainerInterface $container): ConfigInterface + { + return $this->create($container); + } +} diff --git a/src/Container/InjectorFactory.php b/src/Container/InjectorFactory.php new file mode 100644 index 00000000..c453b046 --- /dev/null +++ b/src/Container/InjectorFactory.php @@ -0,0 +1,52 @@ +has(ConfigInterface::class)) { + return $container->get(ConfigInterface::class); + } + + return (new ConfigFactory())->create($container); + } + + /** + * {@inheritDoc} + * @see \Zend\ServiceManager\Factory\FactoryInterface::__invoke() + */ + public function create(ContainerInterface $container): InjectorInterface + { + $config = $this->createConfig($container); + return new Injector($config, null, null, $this); + } + + /** + * Make the instance invokable + */ + public function __invoke(ContainerInterface $container): InjectorInterface + { + return $this->create($container); + } +} diff --git a/src/DefaultContainer.php b/src/DefaultContainer.php new file mode 100644 index 00000000..c77897dd --- /dev/null +++ b/src/DefaultContainer.php @@ -0,0 +1,98 @@ +injector = $injector; + + $this->services[InjectorInterface::class] = $injector; + $this->services[ContainerInterface::class] = $this; + $this->services[get_class($injector)] = $injector; + $this->services[get_class($this)] = $this; + } + + /** + * Explicitly set a service + * + * @param string $name The name of the service retrievable by get() + * @param object $service The service instance + * @return self + */ + public function setInstance(string $name, $service): self + { + $this->services[$name] = $service; + return $this; + } + + /** + * Check if a service is available + * + * @see \Psr\Container\ContainerInterface::has() + * @param string $name + * @return mixed + */ + public function has($name) + { + if (isset($this->services[$name])) { + return true; + } + + return $this->injector->canCreate($name); + } + + /** + * Retrieve a service + * + * Tests first if a service is registered, and, if so, + * returns it. + * + * If the service is not yet registered, it is attempted to be created via + * the dependency injector and then it is stored for further use. + * + * @see \Psr\Container\ContainerInterface::get() + * @param string $name + * @return mixed + */ + public function get($name) + { + if (! isset($this->services[$name])) { + $this->services[$name] = $this->injector->create($name); + } + + return $this->services[$name]; + } +} diff --git a/src/Definition/Annotation/Inject.php b/src/Definition/Annotation/Inject.php deleted file mode 100644 index 9ff5ca04..00000000 --- a/src/Definition/Annotation/Inject.php +++ /dev/null @@ -1,31 +0,0 @@ -content = $content; - } -} diff --git a/src/Definition/Annotation/Instantiator.php b/src/Definition/Annotation/Instantiator.php deleted file mode 100644 index 346ae78a..00000000 --- a/src/Definition/Annotation/Instantiator.php +++ /dev/null @@ -1,31 +0,0 @@ -content = $content; - } -} diff --git a/src/Definition/ArrayDefinition.php b/src/Definition/ArrayDefinition.php deleted file mode 100644 index a0d29681..00000000 --- a/src/Definition/ArrayDefinition.php +++ /dev/null @@ -1,176 +0,0 @@ - $value) { - // force lower names - $dataArray[$class] = array_change_key_case($dataArray[$class], CASE_LOWER); - } - foreach ($dataArray as $class => $definition) { - if (isset($definition['methods']) && is_array($definition['methods'])) { - foreach ($definition['methods'] as $type => $requirement) { - if (!is_int($requirement)) { - $dataArray[$class]['methods'][$type] = InjectionMethod::detectMethodRequirement($requirement); - } - } - } - } - $this->dataArray = $dataArray; - } - - /** - * {@inheritDoc} - */ - public function getClasses() - { - return array_keys($this->dataArray); - } - - /** - * {@inheritDoc} - */ - public function hasClass($class) - { - return array_key_exists($class, $this->dataArray); - } - - /** - * {@inheritDoc} - */ - public function getClassSupertypes($class) - { - if (!isset($this->dataArray[$class])) { - return []; - } - - if (!isset($this->dataArray[$class]['supertypes'])) { - return []; - } - - return $this->dataArray[$class]['supertypes']; - } - - /** - * {@inheritDoc} - */ - public function getInstantiator($class) - { - if (!isset($this->dataArray[$class])) { - return; - } - - if (!isset($this->dataArray[$class]['instantiator'])) { - return '__construct'; - } - - return $this->dataArray[$class]['instantiator']; - } - - /** - * {@inheritDoc} - */ - public function hasMethods($class) - { - if (!isset($this->dataArray[$class])) { - return false; - } - - if (!isset($this->dataArray[$class]['methods'])) { - return false; - } - - return (count($this->dataArray[$class]['methods']) > 0); - } - - /** - * {@inheritDoc} - */ - public function hasMethod($class, $method) - { - if (!isset($this->dataArray[$class])) { - return false; - } - - if (!isset($this->dataArray[$class]['methods'])) { - return false; - } - - return array_key_exists($method, $this->dataArray[$class]['methods']); - } - - /** - * {@inheritDoc} - */ - public function getMethods($class) - { - if (!isset($this->dataArray[$class])) { - return []; - } - - if (!isset($this->dataArray[$class]['methods'])) { - return []; - } - - return $this->dataArray[$class]['methods']; - } - - /** - * {@inheritDoc} - */ - public function hasMethodParameters($class, $method) - { - return isset($this->dataArray[$class]['parameters'][$method]); - } - - /** - * {@inheritDoc} - */ - public function getMethodParameters($class, $method) - { - if (!isset($this->dataArray[$class])) { - return []; - } - - if (!isset($this->dataArray[$class]['parameters'])) { - return []; - } - - if (!isset($this->dataArray[$class]['parameters'][$method])) { - return []; - } - - return $this->dataArray[$class]['parameters'][$method]; - } - - /** - * @return array - */ - public function toArray() - { - return $this->dataArray; - } -} diff --git a/src/Definition/Builder/InjectionMethod.php b/src/Definition/Builder/InjectionMethod.php deleted file mode 100644 index 703a7e34..00000000 --- a/src/Definition/Builder/InjectionMethod.php +++ /dev/null @@ -1,115 +0,0 @@ -name = $name; - - return $this; - } - - /** - * @return null|string - */ - public function getName() - { - return $this->name; - } - - /** - * @param string $name - * @param string|null $class - * @param mixed|null $isRequired - * @param mixed|null $default - * @return InjectionMethod - */ - public function addParameter($name, $class = null, $isRequired = null, $default = null) - { - $this->parameters[] = [ - $name, - $class, - self::detectMethodRequirement($isRequired), - $default, - ]; - - return $this; - } - - /** - * @return array - */ - public function getParameters() - { - return $this->parameters; - } - - /** - * - * @param mixed $requirement - * @return int - */ - public static function detectMethodRequirement($requirement) - { - if (is_bool($requirement)) { - return $requirement ? Di::METHOD_IS_REQUIRED : Di::METHOD_IS_OPTIONAL; - } - - if (null === $requirement) { - //This is mismatch to ClassDefinition::addMethod is it ok ? is optional? - return Di::METHOD_IS_REQUIRED; - } - - if (is_int($requirement)) { - return $requirement; - } - - if (is_string($requirement)) { - switch (strtolower($requirement)) { - default: - case "require": - case "required": - return Di::METHOD_IS_REQUIRED; - case "aware": - return Di::METHOD_IS_AWARE; - case "optional": - return Di::METHOD_IS_OPTIONAL; - case "constructor": - return Di::METHOD_IS_CONSTRUCTOR; - case "instantiator": - return Di::METHOD_IS_INSTANTIATOR; - case "eager": - return Di::METHOD_IS_EAGER; - } - } - return 0; - } -} diff --git a/src/Definition/Builder/PhpClass.php b/src/Definition/Builder/PhpClass.php deleted file mode 100644 index efbc7125..00000000 --- a/src/Definition/Builder/PhpClass.php +++ /dev/null @@ -1,175 +0,0 @@ -name = $name; - - return $this; - } - - /** - * Get name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param string|\Callable|array $instantiator - * @return PhpClass - */ - public function setInstantiator($instantiator) - { - $this->instantiator = $instantiator; - - return $this; - } - - /** - * @return array|\Callable|string - */ - public function getInstantiator() - { - return $this->instantiator; - } - - /** - * @param string $superType - * @return PhpClass - */ - public function addSuperType($superType) - { - $this->superTypes[] = $superType; - - return $this; - } - - /** - * Get super types - * - * @return array - */ - public function getSuperTypes() - { - return $this->superTypes; - } - - /** - * Add injection method - * - * @param InjectionMethod $injectionMethod - * @return PhpClass - */ - public function addInjectionMethod(InjectionMethod $injectionMethod) - { - $this->injectionMethods[] = $injectionMethod; - - return $this; - } - - /** - * Create and register an injection method - * - * Optionally takes the method name. - * - * This method may be used in lieu of addInjectionMethod() in - * order to provide a more fluent interface for building classes with - * injection methods. - * - * @param null|string $name - * @return InjectionMethod - */ - public function createInjectionMethod($name = null) - { - $builder = $this->defaultMethodBuilder; - /* @var $method InjectionMethod */ - $method = new $builder(); - if (null !== $name) { - $method->setName($name); - } - $this->addInjectionMethod($method); - - return $method; - } - - /** - * Override which class will be used by {@link createInjectionMethod()} - * - * @param string $class - * @return PhpClass - */ - public function setMethodBuilder($class) - { - $this->defaultMethodBuilder = $class; - - return $this; - } - - /** - * Determine what class will be used by {@link createInjectionMethod()} - * - * Mainly to provide the ability to temporarily override the class used. - * - * @return string - */ - public function getMethodBuilder() - { - return $this->defaultMethodBuilder; - } - - /** - * @return InjectionMethod[] - */ - public function getInjectionMethods() - { - return $this->injectionMethods; - } -} diff --git a/src/Definition/BuilderDefinition.php b/src/Definition/BuilderDefinition.php deleted file mode 100644 index f8b3ec3c..00000000 --- a/src/Definition/BuilderDefinition.php +++ /dev/null @@ -1,321 +0,0 @@ - $classInfo) { - $class = new Builder\PhpClass(); - $class->setName($className); - foreach ($classInfo as $type => $typeData) { - switch (strtolower($type)) { - case 'supertypes': - foreach ($typeData as $superType) { - $class->addSuperType($superType); - } - break; - case 'instantiator': - $class->setInstantiator($typeData); - break; - case 'methods': - case 'method': - foreach ($typeData as $injectionMethodName => $injectionMethodData) { - $injectionMethod = new Builder\InjectionMethod(); - $injectionMethod->setName($injectionMethodName); - foreach ($injectionMethodData as $parameterName => $parameterType) { - $parameterType = ($parameterType) ?: null; // force empty string to null - $injectionMethod->addParameter($parameterName, $parameterType); - } - $class->addInjectionMethod($injectionMethod); - } - break; - - } - } - $this->addClass($class); - } - } - - /** - * Add class - * - * @param Builder\PhpClass $phpClass - * @return BuilderDefinition - */ - public function addClass(Builder\PhpClass $phpClass) - { - $this->classes[] = $phpClass; - - return $this; - } - - /** - * Create a class builder object using default class builder class - * - * This method is a factory that can be used in place of addClass(). - * - * @param null|string $name Optional name of class to assign - * @return Builder\PhpClass - */ - public function createClass($name = null) - { - $builderClass = $this->defaultClassBuilder; - /* @var $class Builder\PhpClass */ - $class = new $builderClass(); - - if (null !== $name) { - $class->setName($name); - } - - $this->addClass($class); - - return $class; - } - - /** - * Set the class to use with {@link createClass()} - * - * @param string $class - * @return BuilderDefinition - */ - public function setClassBuilder($class) - { - $this->defaultClassBuilder = $class; - - return $this; - } - - /** - * Get the class used for {@link createClass()} - * - * This is primarily to allow developers to temporarily override - * the builder strategy. - * - * @return string - */ - public function getClassBuilder() - { - return $this->defaultClassBuilder; - } - - /** - * {@inheritDoc} - */ - public function getClasses() - { - $classNames = []; - - /* @var $class Builder\PhpClass */ - foreach ($this->classes as $class) { - $classNames[] = $class->getName(); - } - - return $classNames; - } - - /** - * {@inheritDoc} - */ - public function hasClass($class) - { - foreach ($this->classes as $classObj) { - if ($classObj->getName() === $class) { - return true; - } - } - - return false; - } - - /** - * @param string $name - * @return bool|Builder\PhpClass - */ - protected function getClass($name) - { - foreach ($this->classes as $classObj) { - if ($classObj->getName() === $name) { - return $classObj; - } - } - - return false; - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\RuntimeException - */ - public function getClassSupertypes($class) - { - $class = $this->getClass($class); - - if ($class === false) { - throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); - } - - return $class->getSuperTypes(); - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\RuntimeException - */ - public function getInstantiator($class) - { - $class = $this->getClass($class); - if ($class === false) { - throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); - } - - return $class->getInstantiator(); - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\RuntimeException - */ - public function hasMethods($class) - { - /* @var $class \Zend\Di\Definition\Builder\PhpClass */ - $class = $this->getClass($class); - if ($class === false) { - throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); - } - - return (count($class->getInjectionMethods()) > 0); - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\RuntimeException - */ - public function getMethods($class) - { - $class = $this->getClass($class); - if ($class === false) { - throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); - } - $methods = $class->getInjectionMethods(); - $methodNames = []; - - /* @var $methodObj Builder\InjectionMethod */ - foreach ($methods as $methodObj) { - $methodNames[] = $methodObj->getName(); - } - - return $methodNames; - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\RuntimeException - */ - public function hasMethod($class, $method) - { - $class = $this->getClass($class); - if ($class === false) { - throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); - } - $methods = $class->getInjectionMethods(); - - /* @var $methodObj Builder\InjectionMethod */ - foreach ($methods as $methodObj) { - if ($methodObj->getName() === $method) { - return true; - } - } - - return false; - } - - /** - * {@inheritDoc} - */ - public function hasMethodParameters($class, $method) - { - $class = $this->getClass($class); - if ($class === false) { - return false; - } - $methods = $class->getInjectionMethods(); - /* @var $methodObj Builder\InjectionMethod */ - foreach ($methods as $methodObj) { - if ($methodObj->getName() === $method) { - $method = $methodObj; - } - } - if (!$method instanceof Builder\InjectionMethod) { - return false; - } - - /* @var $method Builder\InjectionMethod */ - - return (count($method->getParameters()) > 0); - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\RuntimeException - */ - public function getMethodParameters($class, $method) - { - $class = $this->getClass($class); - - if ($class === false) { - throw new Exception\RuntimeException('Cannot find class object in this builder definition.'); - } - - $methods = $class->getInjectionMethods(); - - /* @var $methodObj Builder\InjectionMethod */ - foreach ($methods as $methodObj) { - if ($methodObj->getName() === $method) { - $method = $methodObj; - } - } - - if (!$method instanceof Builder\InjectionMethod) { - throw new Exception\RuntimeException('Cannot find method object for method ' . $method . ' in this builder definition.'); - } - - $methodParameters = []; - - /* @var $method Builder\InjectionMethod */ - foreach ($method->getParameters() as $name => $info) { - $methodParameters[$class->getName() . '::' . $method->getName() . ':' . $name] = $info; - } - - return $methodParameters; - } -} diff --git a/src/Definition/ClassDefinition.php b/src/Definition/ClassDefinition.php deleted file mode 100644 index 0b2cd428..00000000 --- a/src/Definition/ClassDefinition.php +++ /dev/null @@ -1,231 +0,0 @@ -class = $class; - } - - /** - * @param null|\Callable|array|string $instantiator - * @return self - */ - public function setInstantiator($instantiator) - { - $this->instantiator = $instantiator; - - return $this; - } - - /** - * @param string[] $supertypes - * @return self - */ - public function setSupertypes(array $supertypes) - { - $this->supertypes = $supertypes; - - return $this; - } - - /** - * @param string $method - * @param mixed|bool|null $isRequired - * @return self - */ - public function addMethod($method, $isRequired = null) - { - if ($isRequired === null) { - if ($method === '__construct') { - $methodRequirementType = Di::METHOD_IS_CONSTRUCTOR; - } else { - $methodRequirementType = Di::METHOD_IS_OPTIONAL; - } - } else { - $methodRequirementType = InjectionMethod::detectMethodRequirement($isRequired); - } - - $this->methods[$method] = $methodRequirementType; - - return $this; - } - - /** - * @param $method - * @param $parameterName - * @param array $parameterInfo (keys: required, type) - * @return ClassDefinition - */ - public function addMethodParameter($method, $parameterName, array $parameterInfo) - { - if (!array_key_exists($method, $this->methods)) { - if ($method === '__construct') { - $this->methods[$method] = Di::METHOD_IS_CONSTRUCTOR; - } else { - $this->methods[$method] = Di::METHOD_IS_OPTIONAL; - } - } - - if (!array_key_exists($method, $this->methodParameters)) { - $this->methodParameters[$method] = []; - } - - $type = (isset($parameterInfo['type'])) ? $parameterInfo['type'] : null; - $required = (isset($parameterInfo['required'])) ? (bool) $parameterInfo['required'] : false; - $default = (isset($parameterInfo['default'])) ? $parameterInfo['default'] : null; - - $fqName = $this->class . '::' . $method . ':' . $parameterName; - $this->methodParameters[$method][$fqName] = [ - $parameterName, - $type, - $required, - $default - ]; - - return $this; - } - - /** - * {@inheritDoc} - */ - public function getClasses() - { - return [$this->class]; - } - - /** - * {@inheritDoc} - */ - public function hasClass($class) - { - return ($class === $this->class); - } - - /** - * {@inheritDoc} - */ - public function getClassSupertypes($class) - { - if ($this->class !== $class) { - return []; - } - return $this->supertypes; - } - - /** - * {@inheritDoc} - */ - public function getInstantiator($class) - { - if ($this->class !== $class) { - return; - } - return $this->instantiator; - } - - /** - * {@inheritDoc} - */ - public function hasMethods($class) - { - return (count($this->methods) > 0); - } - - /** - * {@inheritDoc} - */ - public function getMethods($class) - { - if ($this->class !== $class) { - return []; - } - return $this->methods; - } - - /** - * {@inheritDoc} - */ - public function hasMethod($class, $method) - { - if ($this->class !== $class) { - return; - } - - if (is_array($this->methods)) { - return array_key_exists($method, $this->methods); - } - - return; - } - - /** - * {@inheritDoc} - */ - public function hasMethodParameters($class, $method) - { - if ($this->class !== $class) { - return false; - } - return (array_key_exists($method, $this->methodParameters)); - } - - /** - * {@inheritDoc} - */ - public function getMethodParameters($class, $method) - { - if ($this->class !== $class) { - return; - } - - if (array_key_exists($method, $this->methodParameters)) { - return $this->methodParameters[$method]; - } - - return; - } -} diff --git a/src/Definition/ClassDefinitionInterface.php b/src/Definition/ClassDefinitionInterface.php new file mode 100644 index 00000000..de414482 --- /dev/null +++ b/src/Definition/ClassDefinitionInterface.php @@ -0,0 +1,33 @@ +introspectionStrategy = ($introspectionStrategy) ?: new IntrospectionStrategy(); - $this->directoryScanner = new AggregateDirectoryScanner(); - } - - /** - * Set introspection strategy - * - * @param IntrospectionStrategy $introspectionStrategy - */ - public function setIntrospectionStrategy(IntrospectionStrategy $introspectionStrategy) - { - $this->introspectionStrategy = $introspectionStrategy; - } - - /** - * @param bool $allowReflectionExceptions - */ - public function setAllowReflectionExceptions($allowReflectionExceptions = true) - { - $this->allowReflectionExceptions = (bool) $allowReflectionExceptions; - } - - /** - * Get introspection strategy - * - * @return IntrospectionStrategy - */ - public function getIntrospectionStrategy() - { - return $this->introspectionStrategy; - } - - /** - * Add directory - * - * @param string $directory - */ - public function addDirectory($directory) - { - $this->addDirectoryScanner(new DirectoryScanner($directory)); - } - - /** - * Add directory scanner - * - * @param DirectoryScanner $directoryScanner - */ - public function addDirectoryScanner(DirectoryScanner $directoryScanner) - { - $this->directoryScanner->addDirectoryScanner($directoryScanner); - } - - /** - * Add code scanner file - * - * @param FileScanner $fileScanner - */ - public function addCodeScannerFile(FileScanner $fileScanner) - { - if ($this->directoryScanner === null) { - $this->directoryScanner = new DirectoryScanner(); - } - - $this->directoryScanner->addFileScanner($fileScanner); - } - - /** - * Compile - * - * @return void - */ - public function compile() - { - /* @var $classScanner DerivedClassScanner */ - foreach ($this->directoryScanner->getClassNames() as $class) { - $this->processClass($class); - } - } - - /** - * @return ArrayDefinition - */ - public function toArrayDefinition() - { - return new ArrayDefinition( - $this->classes - ); - } - - /** - * @param string $class - * @throws \ReflectionException - */ - protected function processClass($class) - { - $strategy = $this->introspectionStrategy; // localize for readability - - try { - $rClass = new Reflection\ClassReflection($class); - } catch (\ReflectionException $e) { - if (!$this->allowReflectionExceptions) { - throw $e; - } - - return; - } - $className = $rClass->getName(); - $matches = null; // used for regex below - - // setup the key in classes - $this->classes[$className] = [ - 'supertypes' => [], - 'instantiator' => null, - 'methods' => [], - 'parameters' => [] - ]; - - $def = &$this->classes[$className]; // localize for brevity - - // class annotations? - if ($strategy->getUseAnnotations() == true) { - $annotations = $rClass->getAnnotations($strategy->getAnnotationManager()); - - if (($annotations instanceof AnnotationCollection) - && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Instantiator') - ) { - // @todo Instantiator support in annotations - } - } - - /* @var $rTarget \Zend\Code\Reflection\ClassReflection */ - $rTarget = $rClass; - $supertypes = []; - do { - $supertypes = array_merge($supertypes, $rTarget->getInterfaceNames()); - if (!($rTargetParent = $rTarget->getParentClass())) { - break; - } - $supertypes[] = $rTargetParent->getName(); - $rTarget = $rTargetParent; - } while (true); - - $def['supertypes'] = $supertypes; - - if ($def['instantiator'] === null) { - if ($rClass->isInstantiable()) { - $def['instantiator'] = '__construct'; - } - } - - if ($rClass->hasMethod('__construct')) { - $def['methods']['__construct'] = true; // required - try { - $this->processParams($def, $rClass, $rClass->getMethod('__construct')); - } catch (\ReflectionException $e) { - if (!$this->allowReflectionExceptions) { - throw $e; - } - - return; - } - } - - foreach ($rClass->getMethods(Reflection\MethodReflection::IS_PUBLIC) as $rMethod) { - $methodName = $rMethod->getName(); - - if ($rMethod->getName() === '__construct' || $rMethod->isStatic()) { - continue; - } - - if ($strategy->getUseAnnotations() == true) { - $annotations = $rMethod->getAnnotations($strategy->getAnnotationManager()); - - if (($annotations instanceof AnnotationCollection) - && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Inject') - ) { - $def['methods'][$methodName] = true; - $this->processParams($def, $rClass, $rMethod); - continue; - } - } - - $methodPatterns = $this->introspectionStrategy->getMethodNameInclusionPatterns(); - - // matches a method injection pattern? - foreach ($methodPatterns as $methodInjectorPattern) { - preg_match($methodInjectorPattern, $methodName, $matches); - if ($matches) { - $def['methods'][$methodName] = false; // check ot see if this is required? - $this->processParams($def, $rClass, $rMethod); - continue 2; - } - } - - // method - // by annotation - // by setter pattern, - // by interface - } - - $interfaceInjectorPatterns = $this->introspectionStrategy->getInterfaceInjectionInclusionPatterns(); - - // matches the interface injection pattern - /** @var $rIface \ReflectionClass */ - foreach ($rClass->getInterfaces() as $rIface) { - foreach ($interfaceInjectorPatterns as $interfaceInjectorPattern) { - preg_match($interfaceInjectorPattern, $rIface->getName(), $matches); - if ($matches) { - foreach ($rIface->getMethods() as $rMethod) { - if (($rMethod->getName() === '__construct') || !count($rMethod->getParameters())) { - // constructor not allowed in interfaces - // ignore methods without parameters - continue; - } - $def['methods'][$rMethod->getName()] = true; - $this->processParams($def, $rClass, $rMethod); - } - continue 2; - } - } - } - } - - /** - * @param array $def - * @param \Zend\Code\Reflection\ClassReflection $rClass - * @param \Zend\Code\Reflection\MethodReflection $rMethod - */ - protected function processParams(&$def, Reflection\ClassReflection $rClass, Reflection\MethodReflection $rMethod) - { - if (count($rMethod->getParameters()) === 0) { - return; - } - - $methodName = $rMethod->getName(); - - // @todo annotations here for alternate names? - - $def['parameters'][$methodName] = []; - - foreach ($rMethod->getParameters() as $p) { - /** @var $p \ReflectionParameter */ - $actualParamName = $p->getName(); - $fqName = $rClass->getName() . '::' . $rMethod->getName() . ':' . $p->getPosition(); - $def['parameters'][$methodName][$fqName] = []; - - // set the class name, if it exists - $def['parameters'][$methodName][$fqName][] = $actualParamName; - $def['parameters'][$methodName][$fqName][] = ($p->getClass() !== null) ? $p->getClass()->getName() : null; - $def['parameters'][$methodName][$fqName][] = !($optional =$p->isOptional()); - $def['parameters'][$methodName][$fqName][] = $optional && $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null; - } - } - - /** - * {@inheritDoc} - */ - public function getClasses() - { - return array_keys($this->classes); - } - - /** - * {@inheritDoc} - */ - public function hasClass($class) - { - return (array_key_exists($class, $this->classes)); - } - - /** - * {@inheritDoc} - */ - public function getClassSupertypes($class) - { - if (!array_key_exists($class, $this->classes)) { - $this->processClass($class); - } - - return $this->classes[$class]['supertypes']; - } - - /** - * {@inheritDoc} - */ - public function getInstantiator($class) - { - if (!array_key_exists($class, $this->classes)) { - $this->processClass($class); - } - - return $this->classes[$class]['instantiator']; - } - - /** - * {@inheritDoc} - */ - public function hasMethods($class) - { - if (!array_key_exists($class, $this->classes)) { - $this->processClass($class); - } - - return (count($this->classes[$class]['methods']) > 0); - } - - /** - * {@inheritDoc} - */ - public function hasMethod($class, $method) - { - if (!array_key_exists($class, $this->classes)) { - $this->processClass($class); - } - - return isset($this->classes[$class]['methods'][$method]); - } - - /** - * {@inheritDoc} - */ - public function getMethods($class) - { - if (!array_key_exists($class, $this->classes)) { - $this->processClass($class); - } - - return $this->classes[$class]['methods']; - } - - /** - * {@inheritDoc} - */ - public function hasMethodParameters($class, $method) - { - if (!isset($this->classes[$class])) { - return false; - } - - return (array_key_exists($method, $this->classes[$class]['parameters'])); - } - - /** - * {@inheritDoc} - */ - public function getMethodParameters($class, $method) - { - if (!is_array($this->classes[$class])) { - $this->processClass($class); - } - - return $this->classes[$class]['parameters'][$method]; - } -} diff --git a/src/Definition/DefinitionInterface.php b/src/Definition/DefinitionInterface.php index 305f1512..3db79212 100644 --- a/src/Definition/DefinitionInterface.php +++ b/src/Definition/DefinitionInterface.php @@ -9,93 +9,30 @@ namespace Zend\Di\Definition; +/** + * Interface for class definitions + */ interface DefinitionInterface { /** - * Retrieves classes in this definition - * - * @abstract - * @return string[] - */ - public function getClasses(); - - /** - * Return whether the class exists in this definition - * - * @abstract - * @param string $class - * @return bool - */ - public function hasClass($class); - - /** - * Return the supertypes for this class + * All class names in this definition * - * @abstract - * @param string $class * @return string[] */ - public function getClassSupertypes($class); + public function getClasses(): array; /** - * @abstract - * @param string $class - * @return string|array - */ - public function getInstantiator($class); - - /** - * Return if there are injection methods + * Whether a class exists in this definition * - * @abstract * @param string $class * @return bool */ - public function hasMethods($class); + public function hasClass(string $class): bool; /** - * Return an array of the injection methods for a given class - * - * @abstract * @param string $class - * @return string[] - */ - public function getMethods($class); - - /** - * @abstract - * @param string $class - * @param string $method - * @return bool - */ - public function hasMethod($class, $method); - - /** - * @abstract - * @param $class - * @param $method - * @return bool - */ - public function hasMethodParameters($class, $method); - - /** - * getMethodParameters() return information about a methods parameters. - * - * Should return an ordered named array of parameters for a given method. - * Each value should be an array, of length 4 with the following information: - * - * array( - * 0, // string|null: Type Name (if it exists) - * 1, // bool: whether this param is required - * 2, // string: fully qualified path to this parameter - * 3, // mixed: default value - * ); - * - * - * @abstract - * @param string $class - * @param string $method - * @return array + * @throws \Zend\Di\Exception\ClassNotFoundException + * @return ClassDefinitionInterface */ - public function getMethodParameters($class, $method); + public function getClassDefinition(string $class): ClassDefinitionInterface; } diff --git a/src/Definition/IntrospectionStrategy.php b/src/Definition/IntrospectionStrategy.php deleted file mode 100644 index 9cf17a36..00000000 --- a/src/Definition/IntrospectionStrategy.php +++ /dev/null @@ -1,135 +0,0 @@ -annotationManager = ($annotationManager) ?: $this->createDefaultAnnotationManager(); - } - - /** - * Get annotation manager - * - * @return null|AnnotationManager - */ - public function getAnnotationManager() - { - return $this->annotationManager; - } - - /** - * Create default annotation manager - * - * @return AnnotationManager - */ - public function createDefaultAnnotationManager() - { - $annotationManager = new AnnotationManager; - $parser = new GenericAnnotationParser(); - $parser->registerAnnotation(new Annotation\Inject()); - $annotationManager->attach($parser); - - return $annotationManager; - } - - /** - * set use annotations - * - * @param bool $useAnnotations - */ - public function setUseAnnotations($useAnnotations) - { - $this->useAnnotations = (bool) $useAnnotations; - } - - /** - * Get use annotations - * - * @return bool - */ - public function getUseAnnotations() - { - return $this->useAnnotations; - } - - /** - * Set method name inclusion pattern - * - * @param array $methodNameInclusionPatterns - */ - public function setMethodNameInclusionPatterns(array $methodNameInclusionPatterns) - { - $this->methodNameInclusionPatterns = $methodNameInclusionPatterns; - } - - /** - * Get method name inclusion pattern - * - * @return array - */ - public function getMethodNameInclusionPatterns() - { - return $this->methodNameInclusionPatterns; - } - - /** - * Set interface injection inclusion patterns - * - * @param array $interfaceInjectionInclusionPatterns - */ - public function setInterfaceInjectionInclusionPatterns(array $interfaceInjectionInclusionPatterns) - { - $this->interfaceInjectionInclusionPatterns = $interfaceInjectionInclusionPatterns; - } - - /** - * Get interface injection inclusion patterns - * - * @return array - */ - public function getInterfaceInjectionInclusionPatterns() - { - return $this->interfaceInjectionInclusionPatterns; - } -} diff --git a/src/Definition/ParameterInterface.php b/src/Definition/ParameterInterface.php new file mode 100644 index 00000000..ffd8724c --- /dev/null +++ b/src/Definition/ParameterInterface.php @@ -0,0 +1,46 @@ +reflection = $class; + } + + /** + * @return void + */ + private function reflectSupertypes() + { + $this->supertypes = []; + $class = $this->reflection; + + while ($class = $class->getParentClass()) { + $this->supertypes[] = $class->name; + } + } + + /** + * @return ReflectionClass + */ + public function getReflection(): \ReflectionClass + { + return $this->reflection; + } + + /** + * @return string[] + */ + public function getSupertypes(): array + { + if ($this->supertypes === null) { + $this->reflectSupertypes(); + } + + return $this->supertypes; + } + + /** + * @return string[] + */ + public function getInterfaces(): array + { + return $this->reflection->getInterfaceNames(); + } + + /** + * @return void + */ + private function reflectParameters() + { + $this->parameters = []; + + if (! $this->reflection->hasMethod('__construct')) { + return; + } + + $method = $this->reflection->getMethod('__construct'); + + /** @var \ReflectionParameter $parameterReflection */ + foreach ($method->getParameters() as $parameterReflection) { + $parameter = new Parameter($parameterReflection); + $this->parameters[$parameter->getName()] = $parameter; + } + + uasort($this->parameters, function (ParameterInterface $a, ParameterInterface $b) { + return $a->getPosition() - $b->getPosition(); + }); + } + + /** + * @return Parameter[] + */ + public function getParameters(): array + { + if ($this->parameters === null) { + $this->reflectParameters(); + } + + return $this->parameters; + } +} diff --git a/src/Definition/Reflection/Parameter.php b/src/Definition/Reflection/Parameter.php new file mode 100644 index 00000000..ab42b730 --- /dev/null +++ b/src/Definition/Reflection/Parameter.php @@ -0,0 +1,93 @@ +reflection = $reflection; + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Definition\ParameterInterface::getDefault() + */ + public function getDefault() + { + return $this->reflection->getDefaultValue(); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Definition\ParameterInterface::getName() + */ + public function getName(): string + { + return $this->reflection->getName(); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Definition\ParameterInterface::getPosition() + */ + public function getPosition(): int + { + return $this->reflection->getPosition(); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Definition\ParameterInterface::getType() + */ + public function getType(): ?string + { + if ($this->reflection->hasType()) { + return (string)$this->reflection->getType(); + } + + return null; + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Definition\ParameterInterface::isRequired() + */ + public function isRequired(): bool + { + return ! $this->reflection->isOptional(); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Definition\ParameterInterface::isScalar() + */ + public function isBuiltin(): bool + { + if ($this->reflection->hasType()) { + return $this->reflection->getType()->isBuiltin(); + } + + return false; + } +} diff --git a/src/Definition/RuntimeDefinition.php b/src/Definition/RuntimeDefinition.php index 3f89457c..4d7c1aed 100644 --- a/src/Definition/RuntimeDefinition.php +++ b/src/Definition/RuntimeDefinition.php @@ -9,9 +9,7 @@ namespace Zend\Di\Definition; -use Zend\Code\Annotation\AnnotationCollection; -use Zend\Code\Reflection; -use Zend\Di\Di; +use Zend\Di\Exception; /** * Class definitions based on runtime reflection @@ -19,321 +17,112 @@ class RuntimeDefinition implements DefinitionInterface { /** - * @var array + * @var \Zend\Di\Definition\Reflection\ClassDefinition[string] */ - protected $classes = []; + private $definition = []; /** - * @var bool + * @var bool[string] */ - protected $explicitLookups = false; + private $explicitClasses = null; /** - * @var IntrospectionStrategy + * @param string[]|null $explicitClasses */ - protected $introspectionStrategy = null; - - /** - * @var array - */ - protected $injectionMethods = []; - - /** - * @var array - */ - protected $processedClass = []; - - /** - * Constructor - * - * @param null|IntrospectionStrategy $introspectionStrategy - * @param array|null $explicitClasses - */ - public function __construct(IntrospectionStrategy $introspectionStrategy = null, array $explicitClasses = null) + public function __construct(array $explicitClasses = null) { - $this->introspectionStrategy = ($introspectionStrategy) ?: new IntrospectionStrategy(); if ($explicitClasses) { $this->setExplicitClasses($explicitClasses); } } /** - * @param IntrospectionStrategy $introspectionStrategy - * @return void - */ - public function setIntrospectionStrategy(IntrospectionStrategy $introspectionStrategy) - { - $this->introspectionStrategy = $introspectionStrategy; - } - - /** - * @return IntrospectionStrategy - */ - public function getIntrospectionStrategy() - { - return $this->introspectionStrategy; - } - - /** - * Set explicit classes + * Set explicit class names * - * @param array $explicitClasses + * @see addExplicitClass() + * @param string[] $explicitClasses An array of class names + * @throws \Zend\Di\Exception\ClassNotFoundException + * @return self */ - public function setExplicitClasses(array $explicitClasses) + public function setExplicitClasses(array $explicitClasses): self { - $this->explicitLookups = true; - foreach ($explicitClasses as $eClass) { - $this->classes[$eClass] = true; - } - $this->classes = $explicitClasses; - } + $this->explicitClasses = []; - /** - * @param string $class - */ - public function forceLoadClass($class) - { - $this->processClass($class, true); - } + foreach ($explicitClasses as $class) { + $this->addExplicitClass($class); + } - /** - * {@inheritDoc} - */ - public function getClasses() - { - return array_keys($this->classes); + return $this; } /** - * {@inheritDoc} + * Add class name explicitly + * + * Adding classes this way will cause the defintion to report them when getClasses() + * is called, even when they're not yet loaded. + * + * @param string $class + * @throws \Zend\Di\Exception\ClassNotFoundException + * @return self */ - public function hasClass($class) + public function addExplicitClass(string $class): self { - if ($this->explicitLookups === true) { - return (array_key_exists($class, $this->classes)); + if (! class_exists($class)) { + throw new Exception\ClassNotFoundException($class); } - return class_exists($class) || interface_exists($class); - } - - /** - * {@inheritDoc} - */ - public function getClassSupertypes($class) - { - $this->processClass($class); - return $this->classes[$class]['supertypes']; - } + if (! $this->explicitClasses) { + $this->explicitClasses = []; + } - /** - * {@inheritDoc} - */ - public function getInstantiator($class) - { - $this->processClass($class); - return $this->classes[$class]['instantiator']; + $this->explicitClasses[$class] = true; + return $this; } /** - * {@inheritDoc} + * @param string $class The class name to load + * @throws \Zend\Di\Exception\ClassNotFoundException */ - public function hasMethods($class) + private function loadClass(string $class) { - $this->processClass($class); - return (count($this->classes[$class]['methods']) > 0); - } - - /** - * {@inheritDoc} - */ - public function hasMethod($class, $method) - { - $this->processClass($class); - return isset($this->classes[$class]['methods'][$method]); - } + if (! $this->hasClass($class)) { + throw new Exception\ClassNotFoundException($class); + } - /** - * {@inheritDoc} - */ - public function getMethods($class) - { - $this->processClass($class); - return $this->classes[$class]['methods']; + $this->definition[$class] = new Reflection\ClassDefinition($class); } /** - * {@inheritDoc} + * @return string[] */ - public function hasMethodParameters($class, $method) + public function getClasses(): array { - $this->processClass($class); - return (array_key_exists($method, $this->classes[$class]['parameters'])); - } + if (! $this->explicitClasses) { + return array_keys($this->definition); + } - /** - * {@inheritDoc} - */ - public function getMethodParameters($class, $method) - { - $this->processClass($class); - return $this->classes[$class]['parameters'][$method]; + return array_keys(array_merge($this->definition, $this->explicitClasses)); } /** - * @param string $class - * @param bool $forceLoad + * @return bool */ - protected function processClass($class, $forceLoad = false) + public function hasClass(string $class): bool { - if (!isset($this->processedClass[$class]) || $this->processedClass[$class] === false) { - $this->processedClass[$class] = (array_key_exists($class, $this->classes) && is_array($this->classes[$class])); - } - - if (!$forceLoad && $this->processedClass[$class]) { - return; - } - - $strategy = $this->introspectionStrategy; // localize for readability - - /** @var $rClass \Zend\Code\Reflection\ClassReflection */ - $rClass = new Reflection\ClassReflection($class); - $className = $rClass->getName(); - $matches = null; // used for regex below - - // setup the key in classes - $this->classes[$className] = [ - 'supertypes' => [], - 'instantiator' => null, - 'methods' => [], - 'parameters' => [] - ]; - - $def = &$this->classes[$className]; // localize for brevity - - // class annotations? - if ($strategy->getUseAnnotations() == true) { - $annotations = $rClass->getAnnotations($strategy->getAnnotationManager()); - - if (($annotations instanceof AnnotationCollection) - && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Instantiator')) { - // @todo Instantiator support in annotations - } - } - - $rTarget = $rClass; - $supertypes = []; - do { - $supertypes = array_merge($supertypes, $rTarget->getInterfaceNames()); - if (!($rTargetParent = $rTarget->getParentClass())) { - break; - } - $supertypes[] = $rTargetParent->getName(); - $rTarget = $rTargetParent; - } while (true); - - $def['supertypes'] = array_keys(array_flip($supertypes)); - - if ($def['instantiator'] === null) { - if ($rClass->isInstantiable()) { - $def['instantiator'] = '__construct'; - } - } - - if ($rClass->hasMethod('__construct')) { - $def['methods']['__construct'] = Di::METHOD_IS_CONSTRUCTOR; // required - $this->processParams($def, $rClass, $rClass->getMethod('__construct')); - } - - foreach ($rClass->getMethods(Reflection\MethodReflection::IS_PUBLIC) as $rMethod) { - $methodName = $rMethod->getName(); - - if ($rMethod->getName() === '__construct' || $rMethod->isStatic()) { - continue; - } - - if ($strategy->getUseAnnotations() == true) { - $annotations = $rMethod->getAnnotations($strategy->getAnnotationManager()); - - if (($annotations instanceof AnnotationCollection) - && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Inject')) { - // use '@inject' and search for parameters - $def['methods'][$methodName] = Di::METHOD_IS_EAGER; - $this->processParams($def, $rClass, $rMethod); - continue; - } - } - - $methodPatterns = $this->introspectionStrategy->getMethodNameInclusionPatterns(); - - // matches a method injection pattern? - foreach ($methodPatterns as $methodInjectorPattern) { - preg_match($methodInjectorPattern, $methodName, $matches); - if ($matches) { - $def['methods'][$methodName] = Di::METHOD_IS_OPTIONAL; // check ot see if this is required? - $this->processParams($def, $rClass, $rMethod); - continue 2; - } - } - - // method - // by annotation - // by setter pattern, - // by interface - } - - $interfaceInjectorPatterns = $this->introspectionStrategy->getInterfaceInjectionInclusionPatterns(); - - // matches the interface injection pattern - /** @var $rIface \ReflectionClass */ - foreach ($rClass->getInterfaces() as $rIface) { - foreach ($interfaceInjectorPatterns as $interfaceInjectorPattern) { - preg_match($interfaceInjectorPattern, $rIface->getName(), $matches); - if ($matches) { - foreach ($rIface->getMethods() as $rMethod) { - if (($rMethod->getName() === '__construct') || !count($rMethod->getParameters())) { - // constructor not allowed in interfaces - // Don't call interface methods without a parameter (Some aware interfaces define setters in ZF2) - continue; - } - $def['methods'][$rMethod->getName()] = Di::METHOD_IS_AWARE; - $this->processParams($def, $rClass, $rMethod); - } - continue 2; - } - } - } + return class_exists($class); } /** - * @param array $def - * @param \Zend\Code\Reflection\ClassReflection $rClass - * @param \Zend\Code\Reflection\MethodReflection $rMethod + * @param string $class + * @return \Zend\Di\Definition\Reflection\ClassDefinition + * @throws \Zend\Di\Exception\ClassNotFoundException */ - protected function processParams(&$def, Reflection\ClassReflection $rClass, Reflection\MethodReflection $rMethod) + public function getClassDefinition(string $class): ClassDefinitionInterface { - if (count($rMethod->getParameters()) === 0) { - return; + if (! isset($this->definition[$class])) { + $this->loadClass($class); } - $methodName = $rMethod->getName(); - - // @todo annotations here for alternate names? - - $def['parameters'][$methodName] = []; - - foreach ($rMethod->getParameters() as $p) { - /** @var $p \ReflectionParameter */ - $actualParamName = $p->getName(); - - $fqName = $rClass->getName() . '::' . $rMethod->getName() . ':' . $p->getPosition(); - - $def['parameters'][$methodName][$fqName] = []; - - // set the class name, if it exists - $def['parameters'][$methodName][$fqName][] = $actualParamName; - $def['parameters'][$methodName][$fqName][] = ($p->getClass() !== null) ? $p->getClass()->getName() : null; - $def['parameters'][$methodName][$fqName][] = !($optional = $p->isOptional() && $p->isDefaultValueAvailable()); - $def['parameters'][$methodName][$fqName][] = $optional ? $p->getDefaultValue() : null; - } + return $this->definition[$class]; } } diff --git a/src/DefinitionList.php b/src/DefinitionList.php deleted file mode 100644 index 13548c0f..00000000 --- a/src/DefinitionList.php +++ /dev/null @@ -1,352 +0,0 @@ -runtimeDefinitions = new SplDoublyLinkedList(); - if (!is_array($definitions)) { - $definitions = [$definitions]; - } - foreach ($definitions as $definition) { - $this->addDefinition($definition, true); - } - } - - /** - * Add definitions - * - * @param Definition\DefinitionInterface $definition - * @param bool $addToBackOfList - * @return void - */ - public function addDefinition(Definition\DefinitionInterface $definition, $addToBackOfList = true) - { - if ($addToBackOfList) { - $this->push($definition); - } else { - $this->unshift($definition); - } - } - - protected function getDefinitionClassMap(Definition\DefinitionInterface $definition) - { - $definitionClasses = $definition->getClasses(); - if (empty($definitionClasses)) { - return []; - } - return array_combine(array_values($definitionClasses), array_fill(0, count($definitionClasses), $definition)); - } - - public function unshift($definition) - { - $result = parent::unshift($definition); - if ($definition instanceof RuntimeDefinition) { - $this->runtimeDefinitions->unshift($definition); - } - $this->classes = array_merge($this->classes, $this->getDefinitionClassMap($definition)); - return $result; - } - - public function push($definition) - { - $result = parent::push($definition); - if ($definition instanceof RuntimeDefinition) { - $this->runtimeDefinitions->push($definition); - } - $this->classes = array_merge($this->getDefinitionClassMap($definition), $this->classes); - return $result; - } - - /** - * @param string $type - * @return Definition\DefinitionInterface[] - */ - public function getDefinitionsByType($type) - { - $definitions = []; - foreach ($this as $definition) { - if ($definition instanceof $type) { - $definitions[] = $definition; - } - } - - return $definitions; - } - - /** - * Get definition by type - * - * @param string $type - * @return Definition\DefinitionInterface|false - */ - public function getDefinitionByType($type) - { - foreach ($this as $definition) { - if ($definition instanceof $type) { - return $definition; - } - } - - return false; - } - - /** - * @param string $class - * @return bool|Definition\DefinitionInterface - */ - public function getDefinitionForClass($class) - { - if (array_key_exists($class, $this->classes)) { - return $this->classes[$class]; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this->runtimeDefinitions as $definition) { - if ($definition->hasClass($class)) { - return $definition; - } - } - - return false; - } - - /** - * @param string $class - * @return bool|Definition\DefinitionInterface - */ - public function forClass($class) - { - return $this->getDefinitionForClass($class); - } - - /** - * {@inheritDoc} - */ - public function getClasses() - { - return array_keys($this->classes); - } - - /** - * {@inheritDoc} - */ - public function hasClass($class) - { - if (array_key_exists($class, $this->classes)) { - return true; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this->runtimeDefinitions as $definition) { - if ($definition->hasClass($class)) { - return true; - } - } - - return false; - } - - /** - * {@inheritDoc} - */ - public function getClassSupertypes($class) - { - if (false === ($classDefinition = $this->getDefinitionForClass($class))) { - return []; - } - $supertypes = $classDefinition->getClassSupertypes($class); - if (! $classDefinition instanceof Definition\PartialMarker) { - return $supertypes; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this as $definition) { - if ($definition === $classDefinition) { - continue; - } - if ($definition->hasClass($class)) { - $supertypes = array_merge($supertypes, $definition->getClassSupertypes($class)); - if ($definition instanceof Definition\PartialMarker) { - continue; - } - - return $supertypes; - } - } - return $supertypes; - } - - /** - * {@inheritDoc} - */ - public function getInstantiator($class) - { - if (! $classDefinition = $this->getDefinitionForClass($class)) { - return false; - } - $value = $classDefinition->getInstantiator($class); - if (null !== $value) { - return $value; - } - if (! $classDefinition instanceof Definition\PartialMarker) { - return false; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this as $definition) { - if ($definition === $classDefinition) { - continue; - } - if ($definition->hasClass($class)) { - $value = $definition->getInstantiator($class); - if ($value === null && $definition instanceof Definition\PartialMarker) { - continue; - } - - return $value; - } - } - - return false; - } - - /** - * {@inheritDoc} - */ - public function hasMethods($class) - { - if (! $classDefinition = $this->getDefinitionForClass($class)) { - return false; - } - if (false !== ($methods = $classDefinition->hasMethods($class))) { - return $methods; - } - if (! $classDefinition instanceof Definition\PartialMarker) { - return false; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this as $definition) { - if ($definition === $classDefinition) { - continue; - } - if ($definition->hasClass($class)) { - if ($definition->hasMethods($class) === false && $definition instanceof Definition\PartialMarker) { - continue; - } - - return $definition->hasMethods($class); - } - } - - return false; - } - - /** - * {@inheritDoc} - */ - public function hasMethod($class, $method) - { - if (!$this->hasMethods($class)) { - return false; - } - $classDefinition = $this->getDefinitionForClass($class); - if ($classDefinition->hasMethod($class, $method)) { - return true; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this->runtimeDefinitions as $definition) { - if ($definition === $classDefinition) { - continue; - } - if ($definition->hasClass($class) && $definition->hasMethod($class, $method)) { - return true; - } - } - - return false; - } - - /** - * {@inheritDoc} - */ - public function getMethods($class) - { - if (false === ($classDefinition = $this->getDefinitionForClass($class))) { - return []; - } - $methods = $classDefinition->getMethods($class); - if (! $classDefinition instanceof Definition\PartialMarker) { - return $methods; - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this as $definition) { - if ($definition === $classDefinition) { - continue; - } - if ($definition->hasClass($class)) { - if (!$definition instanceof Definition\PartialMarker) { - return array_merge($definition->getMethods($class), $methods); - } - - $methods = array_merge($definition->getMethods($class), $methods); - } - } - - return $methods; - } - - /** - * {@inheritDoc} - */ - public function hasMethodParameters($class, $method) - { - $methodParameters = $this->getMethodParameters($class, $method); - - return ($methodParameters !== []); - } - - /** - * {@inheritDoc} - */ - public function getMethodParameters($class, $method) - { - if (false === ($classDefinition = $this->getDefinitionForClass($class))) { - return []; - } - if ($classDefinition->hasMethod($class, $method) && $classDefinition->hasMethodParameters($class, $method)) { - return $classDefinition->getMethodParameters($class, $method); - } - /** @var $definition Definition\DefinitionInterface */ - foreach ($this as $definition) { - if ($definition === $classDefinition) { - continue; - } - if ($definition->hasClass($class) - && $definition->hasMethod($class, $method) - && $definition->hasMethodParameters($class, $method) - ) { - return $definition->getMethodParameters($class, $method); - } - } - - return []; - } -} diff --git a/src/DependencyInjectionInterface.php b/src/DependencyInjectionInterface.php deleted file mode 100644 index e9d1d0ca..00000000 --- a/src/DependencyInjectionInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -definitions = ($definitions) ?: new DefinitionList(new Definition\RuntimeDefinition()); - $this->instanceManager = ($instanceManager) ?: new InstanceManager(); - - if ($config) { - $this->configure($config); - } - } - - /** - * Provide a configuration object to configure this instance - * - * @param Config $config - * @return void - */ - public function configure(Config $config) - { - $config->configure($this); - } - - /** - * @param DefinitionList $definitions - * @return self - */ - public function setDefinitionList(DefinitionList $definitions) - { - $this->definitions = $definitions; - - return $this; - } - - /** - * @return DefinitionList - */ - public function definitions() - { - return $this->definitions; - } - - /** - * Set the instance manager - * - * @param InstanceManager $instanceManager - * @return Di - */ - public function setInstanceManager(InstanceManager $instanceManager) - { - $this->instanceManager = $instanceManager; - - return $this; - } - - /** - * - * @return InstanceManager - */ - public function instanceManager() - { - return $this->instanceManager; - } - - /** - * Utility method used to retrieve the class of a particular instance. This is here to allow extending classes to - * override how class names are resolved - * - * @internal this method is used by the ServiceLocator\DependencyInjectorProxy class to interact with instances - * and is a hack to be used internally until a major refactor does not split the `resolveMethodParameters`. Do not - * rely on its functionality. - * @param object $instance - * @return string - */ - protected function getClass($instance) - { - return get_class($instance); - } - - /** - * @param $name - * @param array $params - * @param string $method - * @return array - */ - protected function getCallParameters($name, array $params, $method = "__construct") - { - $im = $this->instanceManager; - $class = $im->hasAlias($name) ? $im->getClassFromAlias($name) : $name; - if ($this->definitions->hasClass($class)) { - $callParameters = []; - if ($this->definitions->hasMethod($class, $method)) { - foreach ($this->definitions->getMethodParameters($class, $method) as $param) { - if (isset($params[$param[0]])) { - $callParameters[$param[0]] = $params[$param[0]]; - } - } - } - return $callParameters; - } - return $params; - } - - /** - * Is the DI container capable of returning the named instance? - * - * @param string $name - * @return bool - */ - public function has($name) - { - $definitions = $this->definitions; - $instanceManager = $this->instanceManager(); - - $class = $instanceManager->hasAlias($name) - ? $instanceManager->getClassFromAlias($name) - : $name; - - return $definitions->hasClass($class); - } - - /** - * Lazy-load a class - * - * Attempts to load the class (or service alias) provided. If it has been - * loaded before, the previous instance will be returned (unless the service - * definition indicates shared instances should not be used). - * - * @param string $name Class name or service alias - * @param null|array $params Parameters to pass to the constructor - * @return object|null - */ - public function get($name, array $params = []) - { - array_push($this->instanceContext, ['GET', $name, null]); - - $im = $this->instanceManager; - - $callParameters = $this->getCallParameters($name, $params); - if ($callParameters) { - $fastHash = $im->hasSharedInstanceWithParameters($name, $callParameters, true); - if ($fastHash) { - array_pop($this->instanceContext); - return $im->getSharedInstanceWithParameters(null, [], $fastHash); - } - - if (! $this->definitions->hasClass($name) && $im->hasSharedInstance($name)) { - array_pop($this->instanceContext); - return $im->getSharedInstance($name); - } - } elseif ($im->hasSharedInstance($name)) { - array_pop($this->instanceContext); - return $im->getSharedInstance($name); - } - - $config = $im->getConfig($name); - $instance = $this->newInstance($name, $params, $config['shared']); - array_pop($this->instanceContext); - - return $instance; - } - - /** - * Retrieve a new instance of a class - * - * Forces retrieval of a discrete instance of the given class, using the - * constructor parameters provided. - * - * @param mixed $name Class name or service alias - * @param array $params Parameters to pass to the constructor - * @param bool $isShared - * @return object|null - * @throws Exception\ClassNotFoundException - * @throws Exception\RuntimeException - */ - public function newInstance($name, array $params = [], $isShared = true) - { - // localize dependencies - $definitions = $this->definitions; - $instanceManager = $this->instanceManager(); - - if ($instanceManager->hasAlias($name)) { - $class = $instanceManager->getClassFromAlias($name); - $alias = $name; - } else { - $class = $name; - $alias = null; - } - - array_push($this->instanceContext, ['NEW', $class, $alias]); - - if (!$definitions->hasClass($class)) { - $aliasMsg = ($alias) ? '(specified by alias ' . $alias . ') ' : ''; - throw new Exception\ClassNotFoundException( - 'Class ' . $aliasMsg . $class . ' could not be located in provided definitions.' - ); - } - - $instantiator = $definitions->getInstantiator($class); - $injectionMethods = []; - $injectionMethods[$class] = $definitions->getMethods($class); - - foreach ($definitions->getClassSupertypes($class) as $supertype) { - $injectionMethods[$supertype] = $definitions->getMethods($supertype); - } - - if ($instantiator === '__construct') { - $instance = $this->createInstanceViaConstructor($class, $params, $alias); - if (array_key_exists('__construct', $injectionMethods)) { - unset($injectionMethods['__construct']); - } - } elseif (is_callable($instantiator, false)) { - $instance = $this->createInstanceViaCallback($instantiator, $params, $alias); - } else { - if (is_array($instantiator)) { - $msg = sprintf( - 'Invalid instantiator: %s::%s() is not callable.', - isset($instantiator[0]) ? $instantiator[0] : 'NoClassGiven', - isset($instantiator[1]) ? $instantiator[1] : 'NoMethodGiven' - ); - } else { - $msg = sprintf( - 'Invalid instantiator of type "%s" for "%s".', - gettype($instantiator), - $name - ); - } - throw new Exception\RuntimeException($msg); - } - - if ($isShared) { - if ($callParameters = $this->getCallParameters($name, $params)) { - $this->instanceManager->addSharedInstanceWithParameters($instance, $name, $callParameters); - } else { - $this->instanceManager->addSharedInstance($instance, $name); - } - } - - $this->handleInjectDependencies($instance, $injectionMethods, $params, $class, $alias, $name); - - array_pop($this->instanceContext); - - return $instance; - } - - /** - * Inject dependencies - * - * @param object $instance - * @param array $params - * @return void - */ - public function injectDependencies($instance, array $params = []) - { - $definitions = $this->definitions(); - $class = $this->getClass($instance); - $injectionMethods = [ - $class => ($definitions->hasClass($class)) ? $definitions->getMethods($class) : [] - ]; - $parent = $class; - while ($parent = get_parent_class($parent)) { - if ($definitions->hasClass($parent)) { - $injectionMethods[$parent] = $definitions->getMethods($parent); - } - } - foreach (class_implements($class) as $interface) { - if ($definitions->hasClass($interface)) { - $injectionMethods[$interface] = $definitions->getMethods($interface); - } - } - $this->handleInjectDependencies($instance, $injectionMethods, $params, $class, null, null); - } - - /** - * @param object $instance - * @param array $injectionMethods - * @param array $params - * @param string|null $instanceClass - * @param string|null$instanceAlias - * @param string $requestedName - * @throws Exception\RuntimeException - */ - protected function handleInjectDependencies($instance, $injectionMethods, $params, $instanceClass, $instanceAlias, $requestedName) - { - // localize dependencies - $definitions = $this->definitions; - $instanceManager = $this->instanceManager(); - - $calledMethods = ['__construct' => true]; - - if ($injectionMethods) { - foreach ($injectionMethods as $type => $typeInjectionMethods) { - foreach ($typeInjectionMethods as $typeInjectionMethod => $methodRequirementType) { - if (!isset($calledMethods[$typeInjectionMethod])) { - if ($this->resolveAndCallInjectionMethodForInstance($instance, $typeInjectionMethod, $params, $instanceAlias, $methodRequirementType, $type)) { - $calledMethods[$typeInjectionMethod] = true; - } - } - } - } - - if ($requestedName) { - $instanceConfig = $instanceManager->getConfig($requestedName); - - if ($instanceConfig['injections']) { - $objectsToInject = $methodsToCall = []; - foreach ($instanceConfig['injections'] as $injectName => $injectValue) { - if (is_int($injectName) && is_string($injectValue)) { - $objectsToInject[] = $this->get($injectValue, $params); - } elseif (is_string($injectName) && is_array($injectValue)) { - if (is_string(key($injectValue))) { - $methodsToCall[] = ['method' => $injectName, 'args' => $injectValue]; - } else { - foreach ($injectValue as $methodCallArgs) { - $methodsToCall[] = ['method' => $injectName, 'args' => $methodCallArgs]; - } - } - } elseif (is_object($injectValue)) { - $objectsToInject[] = $injectValue; - } elseif (is_int($injectName) && is_array($injectValue)) { - throw new Exception\RuntimeException( - 'An injection was provided with a keyed index and an array of data, try using' - . ' the name of a particular method as a key for your injection data.' - ); - } - } - if ($objectsToInject) { - foreach ($objectsToInject as $objectToInject) { - $calledMethods = ['__construct' => true]; - foreach ($injectionMethods as $type => $typeInjectionMethods) { - foreach ($typeInjectionMethods as $typeInjectionMethod => $methodRequirementType) { - if (!isset($calledMethods[$typeInjectionMethod])) { - $methodParams = $definitions->getMethodParameters($type, $typeInjectionMethod); - if ($methodParams) { - foreach ($methodParams as $methodParam) { - $objectToInjectClass = $this->getClass($objectToInject); - if ($objectToInjectClass == $methodParam[1] || is_subclass_of($objectToInjectClass, $methodParam[1])) { - if ($this->resolveAndCallInjectionMethodForInstance($instance, $typeInjectionMethod, [$methodParam[0] => $objectToInject], $instanceAlias, self::METHOD_IS_REQUIRED, $type)) { - $calledMethods[$typeInjectionMethod] = true; - } - continue 3; - } - } - } - } - } - } - } - } - if ($methodsToCall) { - foreach ($methodsToCall as $methodInfo) { - $this->resolveAndCallInjectionMethodForInstance($instance, $methodInfo['method'], $methodInfo['args'], $instanceAlias, self::METHOD_IS_REQUIRED, $instanceClass); - } - } - } - } - } - } - - /** - * Retrieve a class instance based on class name - * - * Any parameters provided will be used as constructor arguments. If any - * given parameter is a DependencyReference object, it will be fetched - * from the container so that the instance may be injected. - * - * @param string $class - * @param array $params - * @param string|null $alias - * @return object - */ - protected function createInstanceViaConstructor($class, $params, $alias = null) - { - $callParameters = []; - if ($this->definitions->hasMethod($class, '__construct')) { - $callParameters = $this->resolveMethodParameters($class, '__construct', $params, $alias, self::METHOD_IS_CONSTRUCTOR, true); - } - - if (!class_exists($class)) { - if (interface_exists($class)) { - throw new Exception\ClassNotFoundException(sprintf( - 'Cannot instantiate interface "%s"', - $class - )); - } - throw new Exception\ClassNotFoundException(sprintf( - 'Class "%s" does not exist; cannot instantiate', - $class - )); - } - - // Hack to avoid Reflection in most common use cases - switch (count($callParameters)) { - case 0: - return new $class(); - case 1: - return new $class($callParameters[0]); - case 2: - return new $class($callParameters[0], $callParameters[1]); - case 3: - return new $class($callParameters[0], $callParameters[1], $callParameters[2]); - default: - $r = new \ReflectionClass($class); - - return $r->newInstanceArgs($callParameters); - } - } - - /** - * Get an object instance from the defined callback - * - * @param callable $callback - * @param array $params - * @param string $alias - * @return object - * @throws Exception\InvalidCallbackException - * @throws Exception\RuntimeException - */ - protected function createInstanceViaCallback($callback, $params, $alias) - { - if (!is_callable($callback)) { - throw new Exception\InvalidCallbackException('An invalid constructor callback was provided'); - } - - if (is_array($callback)) { - $class = (is_object($callback[0])) ? $this->getClass($callback[0]) : $callback[0]; - $method = $callback[1]; - } elseif (is_string($callback) && strpos($callback, '::') !== false) { - list($class, $method) = explode('::', $callback, 2); - } else { - throw new Exception\RuntimeException('Invalid callback type provided to ' . __METHOD__); - } - - $callParameters = []; - if ($this->definitions->hasMethod($class, $method)) { - $callParameters = $this->resolveMethodParameters($class, $method, $params, $alias, self::METHOD_IS_INSTANTIATOR, true); - } - - return call_user_func_array($callback, $callParameters); - } - - /** - * This parameter will handle any injection methods and resolution of - * dependencies for such methods - * - * @param object $instance - * @param string $method - * @param array $params - * @param string $alias - * @param bool $methodRequirementType - * @param string|null $methodClass - * @return bool - */ - protected function resolveAndCallInjectionMethodForInstance($instance, $method, $params, $alias, $methodRequirementType, $methodClass = null) - { - $methodClass = ($methodClass) ?: $this->getClass($instance); - $callParameters = $this->resolveMethodParameters($methodClass, $method, $params, $alias, $methodRequirementType); - if ($callParameters == false) { - return false; - } - if ($callParameters !== array_fill(0, count($callParameters), null)) { - call_user_func_array([$instance, $method], $callParameters); - - return true; - } - - return false; - } - - /** - * Resolve parameters referencing other services - * - * @param string $class - * @param string $method - * @param array $callTimeUserParams - * @param string $alias - * @param int|bool $methodRequirementType - * @param bool $isInstantiator - * @throws Exception\MissingPropertyException - * @throws Exception\CircularDependencyException - * @return array - */ - protected function resolveMethodParameters($class, $method, array $callTimeUserParams, $alias, $methodRequirementType, $isInstantiator = false) - { - //for BC - if (is_bool($methodRequirementType)) { - if ($isInstantiator) { - $methodRequirementType = Di::METHOD_IS_INSTANTIATOR; - } elseif ($methodRequirementType) { - $methodRequirementType = Di::METHOD_IS_REQUIRED; - } else { - $methodRequirementType = Di::METHOD_IS_OPTIONAL; - } - } - // parameters for this method, in proper order, to be returned - $resolvedParams = []; - - // parameter requirements from the definition - $injectionMethodParameters = $this->definitions->getMethodParameters($class, $method); - - // computed parameters array - $computedParams = [ - 'value' => [], - 'retrieval' => [], - 'optional' => [] - ]; - - // retrieve instance configurations for all contexts - $iConfig = []; - $aliases = $this->instanceManager->getAliases(); - - // for the alias in the dependency tree - if ($alias && $this->instanceManager->hasConfig($alias)) { - $iConfig['thisAlias'] = $this->instanceManager->getConfig($alias); - } - - // for the current class in the dependency tree - if ($this->instanceManager->hasConfig($class)) { - $iConfig['thisClass'] = $this->instanceManager->getConfig($class); - } - - // for the parent class, provided we are deeper than one node - if (isset($this->instanceContext[0])) { - list($requestedClass, $requestedAlias) = ($this->instanceContext[0][0] == 'NEW') - ? [$this->instanceContext[0][1], $this->instanceContext[0][2]] - : [$this->instanceContext[1][1], $this->instanceContext[1][2]]; - } else { - $requestedClass = $requestedAlias = null; - } - - if ($requestedClass != $class && $this->instanceManager->hasConfig($requestedClass)) { - $iConfig['requestedClass'] = $this->instanceManager->getConfig($requestedClass); - - if (array_key_exists('parameters', $iConfig['requestedClass'])) { - $newParameters = []; - - foreach ($iConfig['requestedClass']['parameters'] as $name => $parameter) { - $newParameters[$requestedClass.'::'.$method.'::'.$name] = $parameter; - } - - $iConfig['requestedClass']['parameters'] = $newParameters; - } - - if ($requestedAlias) { - $iConfig['requestedAlias'] = $this->instanceManager->getConfig($requestedAlias); - } - } - - // This is a 2 pass system for resolving parameters - // first pass will find the sources, the second pass will order them and resolve lookups if they exist - // MOST methods will only have a single parameters to resolve, so this should be fast - - foreach ($injectionMethodParameters as $fqParamPos => $info) { - list($name, $type, $isRequired) = $info; - - $fqParamName = substr_replace($fqParamPos, ':' . $info[0], strrpos($fqParamPos, ':')); - - // PRIORITY 1 - consult user provided parameters - if (isset($callTimeUserParams[$fqParamPos]) || isset($callTimeUserParams[$name])) { - if (isset($callTimeUserParams[$fqParamPos])) { - $callTimeCurValue =& $callTimeUserParams[$fqParamPos]; - } elseif (isset($callTimeUserParams[$fqParamName])) { - $callTimeCurValue =& $callTimeUserParams[$fqParamName]; - } else { - $callTimeCurValue =& $callTimeUserParams[$name]; - } - - if ($type !== false && is_string($callTimeCurValue)) { - if ($this->instanceManager->hasAlias($callTimeCurValue)) { - // was an alias provided? - $computedParams['retrieval'][$fqParamPos] = [ - $callTimeUserParams[$name], - $this->instanceManager->getClassFromAlias($callTimeCurValue) - ]; - } elseif ($this->definitions->hasClass($callTimeUserParams[$name])) { - // was a known class provided? - $computedParams['retrieval'][$fqParamPos] = [ - $callTimeCurValue, - $callTimeCurValue - ]; - } else { - // must be a value - $computedParams['value'][$fqParamPos] = $callTimeCurValue; - } - } else { - // int, float, null, object, etc - $computedParams['value'][$fqParamPos] = $callTimeCurValue; - } - unset($callTimeCurValue); - continue; - } - - // PRIORITY 2 -specific instance configuration (thisAlias) - this alias - // PRIORITY 3 -THEN specific instance configuration (thisClass) - this class - // PRIORITY 4 -THEN specific instance configuration (requestedAlias) - requested alias - // PRIORITY 5 -THEN specific instance configuration (requestedClass) - requested class - - foreach (['thisAlias', 'thisClass', 'requestedAlias', 'requestedClass'] as $thisIndex) { - // check the provided parameters config - if (isset($iConfig[$thisIndex]['parameters'][$fqParamPos]) - || isset($iConfig[$thisIndex]['parameters'][$fqParamName]) - || isset($iConfig[$thisIndex]['parameters'][$name])) { - if (isset($iConfig[$thisIndex]['parameters'][$fqParamPos])) { - $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$fqParamPos]; - } elseif (isset($iConfig[$thisIndex]['parameters'][$fqParamName])) { - $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$fqParamName]; - } else { - $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$name]; - } - - if ($type === false && is_string($iConfigCurValue)) { - $computedParams['value'][$fqParamPos] = $iConfigCurValue; - } elseif (is_string($iConfigCurValue) - && isset($aliases[$iConfigCurValue])) { - $computedParams['retrieval'][$fqParamPos] = [ - $iConfig[$thisIndex]['parameters'][$name], - $this->instanceManager->getClassFromAlias($iConfigCurValue) - ]; - } elseif (is_string($iConfigCurValue) - && $this->definitions->hasClass($iConfigCurValue)) { - $computedParams['retrieval'][$fqParamPos] = [ - $iConfigCurValue, - $iConfigCurValue - ]; - } elseif (is_object($iConfigCurValue) - && $iConfigCurValue instanceof Closure - && $type !== 'Closure') { - /* @var $iConfigCurValue Closure */ - $computedParams['value'][$fqParamPos] = $iConfigCurValue(); - } else { - $computedParams['value'][$fqParamPos] = $iConfigCurValue; - } - unset($iConfigCurValue); - continue 2; - } - } - - // PRIORITY 6 - globally preferred implementations - - // next consult alias level preferred instances - // RESOLVE_EAGER wants to inject the cross-cutting concerns. - // If you want to retrieve an instance from TypePreferences, - // use AwareInterface or specify the method requirement option METHOD_IS_EAGER at ClassDefinition - if ($methodRequirementType & self::RESOLVE_EAGER) { - if ($alias && $this->instanceManager->hasTypePreferences($alias)) { - $pInstances = $this->instanceManager->getTypePreferences($alias); - foreach ($pInstances as $pInstance) { - if (is_object($pInstance)) { - $computedParams['value'][$fqParamPos] = $pInstance; - continue 2; - } - $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? - $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; - if ($pInstanceClass === $type || is_subclass_of($pInstanceClass, $type)) { - $computedParams['retrieval'][$fqParamPos] = [$pInstance, $pInstanceClass]; - continue 2; - } - } - } - - // next consult class level preferred instances - if ($type && $this->instanceManager->hasTypePreferences($type)) { - $pInstances = $this->instanceManager->getTypePreferences($type); - foreach ($pInstances as $pInstance) { - if (is_object($pInstance)) { - $computedParams['value'][$fqParamPos] = $pInstance; - continue 2; - } - $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? - $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; - if ($pInstanceClass === $type || is_subclass_of($pInstanceClass, $type)) { - $computedParams['retrieval'][$fqParamPos] = [$pInstance, $pInstanceClass]; - continue 2; - } - } - } - } - if (!$isRequired) { - $computedParams['optional'][$fqParamPos] = true; - } - - if ($type && $isRequired && ($methodRequirementType & self::RESOLVE_EAGER)) { - $computedParams['retrieval'][$fqParamPos] = [$type, $type]; - } - } - - $index = 0; - foreach ($injectionMethodParameters as $fqParamPos => $value) { - $name = $value[0]; - - if (isset($computedParams['value'][$fqParamPos])) { - // if there is a value supplied, use it - $resolvedParams[$index] = $computedParams['value'][$fqParamPos]; - } elseif (isset($computedParams['retrieval'][$fqParamPos])) { - // detect circular dependencies! (they can only happen in instantiators) - if ($isInstantiator && in_array($computedParams['retrieval'][$fqParamPos][1], $this->currentDependencies) - && (!isset($alias) || in_array($computedParams['retrieval'][$fqParamPos][0], $this->currentAliasDependenencies)) - ) { - $msg = "Circular dependency detected: $class depends on {$value[1]} and viceversa"; - if (isset($alias)) { - $msg .= " (Aliased as $alias)"; - } - throw new Exception\CircularDependencyException($msg); - } - - array_push($this->currentDependencies, $class); - if (isset($alias)) { - array_push($this->currentAliasDependenencies, $alias); - } - - $dConfig = $this->instanceManager->getConfig($computedParams['retrieval'][$fqParamPos][0]); - - try { - if ($dConfig['shared'] === false) { - $resolvedParams[$index] = $this->newInstance($computedParams['retrieval'][$fqParamPos][0], $callTimeUserParams, false); - } else { - $resolvedParams[$index] = $this->get($computedParams['retrieval'][$fqParamPos][0], $callTimeUserParams); - } - } catch (DiRuntimeException $e) { - if ($methodRequirementType & self::RESOLVE_STRICT) { - //finally ( be aware to do at the end of flow) - array_pop($this->currentDependencies); - if (isset($alias)) { - array_pop($this->currentAliasDependenencies); - } - // if this item was marked strict, - // plus it cannot be resolve, and no value exist, bail out - throw new Exception\MissingPropertyException( - sprintf( - 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method, - (($value[0] === null) ? 'value' : 'instance/object') - ), - $e->getCode(), - $e - ); - } else { - //finally ( be aware to do at the end of flow) - array_pop($this->currentDependencies); - if (isset($alias)) { - array_pop($this->currentAliasDependenencies); - } - return false; - } - } catch (ContainerException $e) { - // Exceptions thrown by nested/peered containers (e.g., zend-servicemanager) - if ($methodRequirementType & self::RESOLVE_STRICT) { - //finally ( be aware to do at the end of flow) - array_pop($this->currentDependencies); - if (isset($alias)) { - array_pop($this->currentAliasDependenencies); - } - // if this item was marked strict, - // plus it cannot be resolve, and no value exist, bail out - throw new Exception\MissingPropertyException( - sprintf( - 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method, - (($value[0] === null) ? 'value' : 'instance/object') - ), - $e->getCode(), - $e - ); - } else { - //finally ( be aware to do at the end of flow) - array_pop($this->currentDependencies); - if (isset($alias)) { - array_pop($this->currentAliasDependenencies); - } - return false; - } - } - array_pop($this->currentDependencies); - if (isset($alias)) { - array_pop($this->currentAliasDependenencies); - } - } elseif (!array_key_exists($fqParamPos, $computedParams['optional'])) { - if ($methodRequirementType & self::RESOLVE_STRICT) { - // if this item was not marked as optional, - // plus it cannot be resolve, and no value exist, bail out - throw new Exception\MissingPropertyException(sprintf( - 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method, - (($value[0] === null) ? 'value' : 'instance/object') - )); - } else { - return false; - } - } else { - $resolvedParams[$index] = $value[3]; - } - - $index++; - } - - return $resolvedParams; // return ordered list of parameters - } - - /** - * Checks if the object has this class as one of its parents - * - * @see https://bugs.php.net/bug.php?id=53727 - * @see https://github.com/zendframework/zf2/pull/1807 - * - * @deprecated since zf 2.3 requires PHP >= 5.3.23 - * - * @param string $className - * @param $type - * @return bool - */ - protected static function isSubclassOf($className, $type) - { - return is_subclass_of($className, $type); - } -} diff --git a/src/Display/Console.php b/src/Display/Console.php deleted file mode 100644 index d58b221b..00000000 --- a/src/Display/Console.php +++ /dev/null @@ -1,176 +0,0 @@ -addRuntimeClasses($runtimeClasses); - $console->render($di); - } - - /** - * Constructor - * - * @param null|Di $di - */ - public function __construct(Di $di = null) - { - $this->di = ($di) ?: new Di; - } - - /** - * @param string[] $runtimeClasses - */ - public function addRuntimeClasses(array $runtimeClasses) - { - foreach ($runtimeClasses as $runtimeClass) { - $this->addRuntimeClass($runtimeClass); - } - } - - /** - * @param string $runtimeClass - */ - public function addRuntimeClass($runtimeClass) - { - $this->runtimeClasses[] = $runtimeClass; - } - - public function render() - { - $knownClasses = []; - - echo 'Definitions' . PHP_EOL . PHP_EOL; - - foreach ($this->di->definitions() as $definition) { - $this->renderDefinition($definition); - foreach ($definition->getClasses() as $class) { - $knownClasses[] = $class; - $this->renderClassDefinition($definition, $class); - } - if (count($definition->getClasses()) == 0) { - echo PHP_EOL .' No Classes Found' . PHP_EOL . PHP_EOL; - } - } - - if ($this->runtimeClasses) { - echo ' Runtime classes:' . PHP_EOL; - } - - $unknownRuntimeClasses = array_diff($this->runtimeClasses, $knownClasses); - foreach ($unknownRuntimeClasses as $runtimeClass) { - $definition = $this->di->definitions()->getDefinitionForClass($runtimeClass); - $this->renderClassDefinition($definition, $runtimeClass); - } - - echo PHP_EOL . 'Instance Configuration Info:' . PHP_EOL; - - echo PHP_EOL . ' Aliases:' . PHP_EOL; - - $configuredTypes = []; - foreach ($this->di->instanceManager()->getAliases() as $alias => $class) { - echo ' ' . $alias . ' [type: ' . $class . ']' . PHP_EOL; - $configuredTypes[] = $alias; - } - - echo PHP_EOL . ' Classes:' . PHP_EOL; - - foreach ($this->di->instanceManager()->getClasses() as $class) { - echo ' ' . $class . PHP_EOL; - $configuredTypes[] = $class; - } - - echo PHP_EOL . ' Configurations:' . PHP_EOL; - - foreach ($configuredTypes as $type) { - $info = $this->di->instanceManager()->getConfig($type); - echo ' ' . $type . PHP_EOL; - - if ($info['parameters']) { - echo ' parameters:' . PHP_EOL; - foreach ($info['parameters'] as $param => $value) { - echo ' ' . $param . ' = ' . $value . PHP_EOL; - } - } - - if ($info['injections']) { - echo ' injections:' . PHP_EOL; - foreach ($info['injections'] as $injection => $value) { - var_dump($injection, $value); - } - } - } - } - - /** - * @param object $definition - */ - protected function renderDefinition($definition) - { - echo ' Definition Type: ' . get_class($definition) . PHP_EOL; - $r = new \ReflectionClass($definition); - foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) { - $property->setAccessible(true); - echo ' internal property: ' . $property->getName(); - $value = $property->getValue($definition); - if (is_object($value)) { - echo ' instance of ' . get_class($value); - } else { - echo ' = ' . $value; - } - echo PHP_EOL; - } - } - - /** - * @param \Zend\Di\Definition\DefinitionInterface $definition - * @param string $class - */ - protected function renderClassDefinition($definition, $class) - { - echo PHP_EOL . ' Parameters For Class: ' . $class . PHP_EOL; - foreach ($definition->getMethods($class) as $methodName => $methodIsRequired) { - foreach ($definition->getMethodParameters($class, $methodName) as $fqName => $pData) { - echo ' ' . $pData[0] . ' [type: '; - echo($pData[1]) ? $pData[1] : 'scalar'; - echo($pData[2] === true && $methodIsRequired) ? ', required' : ', not required'; - echo ', injection-method: ' . $methodName; - echo ' fq-name: ' . $fqName; - echo ']' . PHP_EOL; - } - } - echo PHP_EOL; - } -} diff --git a/src/Exception/ClassNotFoundException.php b/src/Exception/ClassNotFoundException.php index 04e8f99b..7d22ee05 100644 --- a/src/Exception/ClassNotFoundException.php +++ b/src/Exception/ClassNotFoundException.php @@ -13,4 +13,13 @@ class ClassNotFoundException extends DomainException implements ExceptionInterface { + /** + * @param string $classname + * @param int $code + * @param \Throwable|null $previous + */ + public function __construct($classname, $code = null, $previous = null) + { + parent::__construct("The class '$classname' does not exist.", $code, $previous); + } } diff --git a/test/TestAsset/AggregateClasses/ItemInterface.php b/src/Exception/GenerateCodeException.php similarity index 70% rename from test/TestAsset/AggregateClasses/ItemInterface.php rename to src/Exception/GenerateCodeException.php index c8f2f868..db20c66d 100644 --- a/test/TestAsset/AggregateClasses/ItemInterface.php +++ b/src/Exception/GenerateCodeException.php @@ -7,10 +7,11 @@ * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace ZendTest\Di\TestAsset\AggregateClasses; +namespace Zend\Di\Exception; - -interface ItemInterface +/** + * Runtime exception for code generators + */ +class GenerateCodeException extends RuntimeException { - } diff --git a/src/Definition/PartialMarker.php b/src/Exception/LogicException.php similarity index 75% rename from src/Definition/PartialMarker.php rename to src/Exception/LogicException.php index b7a978e3..83b7ae4f 100644 --- a/src/Definition/PartialMarker.php +++ b/src/Exception/LogicException.php @@ -7,8 +7,8 @@ * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace Zend\Di\Definition; +namespace Zend\Di\Exception; -interface PartialMarker +class LogicException extends \LogicException implements ExceptionInterface { } diff --git a/test/TestAsset/AggregateClasses/Item.php b/src/Exception/UnexpectedValueException.php similarity index 71% rename from test/TestAsset/AggregateClasses/Item.php rename to src/Exception/UnexpectedValueException.php index 3afc6877..5146f917 100644 --- a/test/TestAsset/AggregateClasses/Item.php +++ b/src/Exception/UnexpectedValueException.php @@ -7,8 +7,8 @@ * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace ZendTest\Di\TestAsset\AggregateClasses; +namespace Zend\Di\Exception; -class Item implements ItemInterface +class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface { } diff --git a/src/Injector.php b/src/Injector.php new file mode 100644 index 00000000..b6ec494c --- /dev/null +++ b/src/Injector.php @@ -0,0 +1,251 @@ +definition = $definition ? : new Definition\RuntimeDefinition(); + $this->config = $config ? : new Config(); + $this->resolver = $resolver ? : new Resolver\DependencyResolver($this->definition, $this->config); + $this->setContainer($container ? : new DefaultContainer($this)); + } + + /** + * Set the ioc container + * + * Sets the ioc container to utilize for fetching instances of dependencies + * + * @param ContainerInterface $container + * @return self + */ + public function setContainer(ContainerInterface $container) + { + $this->resolver->setContainer($container); + $this->container = $container; + + return $this; + } + + /** + * @return \Psr\Container\ContainerInterface + */ + public function getContainer(): ContainerInterface + { + return $this->container; + } + + /** + * Returns the class name for the requested type + * + * @param string $type + */ + private function getClassName(string $type): string + { + if ($this->config->isAlias($type)) { + return $this->config->getClassForAlias($type); + } + + return $type; + } + + /** + * Check if the given type name can be instanciated + * + * This will be the case if the name points to a class. + * + * @param string $name + * @return bool + * @see \Zend\Di\InjectorInterface::canCreate() + */ + public function canCreate(string $name): bool + { + $class = $this->getClassName($name); + return (class_exists($class) && ! interface_exists($class)); + } + + /** + * Create the instance with auto wiring + * + * @param string $name Class name or service alias + * @param array $parameters Constructor paramters + * @return object|null + * @throws Exception\ClassNotFoundException + * @throws Exception\RuntimeException + */ + public function create(string $name, array $parameters = []) + { + if (in_array($name, $this->instanciationStack)) { + throw new Exception\CircularDependencyException(sprintf( + 'Circular dependency: %s -> %s', + implode(' -> ', $this->instanciationStack), + $name + )); + } + + $this->instanciationStack[] = $name; + + try { + $instance = $this->createInstance($name, $parameters); + } catch (\Exception $e) { + throw $e; + } finally { + array_pop($this->instanciationStack); + } + + return $instance; + } + + /** + * Retrieve a class instance based on class name + * + * Any parameters provided will be used as constructor/instantiator arguments only. + * + * @param string $name The type name to instanciate + * @param array $params Constructor/instantiator arguments + * @return object + * + * @throws Exception\InvalidCallbackException + * @throws Exception\ClassNotFoundException + */ + protected function createInstance(string $name, array $params) + { + $class = $this->getClassName($name); + + if (! $this->definition->hasClass($class)) { + $aliasMsg = ($name != $class) ? ' (specified by alias ' . $name . ')' : ''; + throw new Exception\ClassNotFoundException( + 'Class ' . $class . $aliasMsg . + ' could not be located in provided definitions.' + ); + } + + if (! class_exists($class) || interface_exists($class)) { + throw new Exception\ClassNotFoundException($class); + } + + $definition = $this->definition->getClassDefinition($class); + $callParameters = $this->resolveParameters($name, $params); + + // Hack to avoid Reflection in most common use cases + switch (count($callParameters)) { + case 0: + return new $class(); + case 1: + return new $class($callParameters[0]); + case 2: + return new $class($callParameters[0], $callParameters[1]); + case 3: + return new $class($callParameters[0], $callParameters[1], $callParameters[2]); + default: + return $definition->getReflection()->newInstanceArgs($callParameters); + } + } + + /** + * Resolve parameters + * + * At first this method utilizes the resolver to obtain the types to inject. + * If this was successful (the resolver returned a non-null value), it will use + * the ioc container to fetch the instances + * + * @param string $type The class or alias name to resolve for + * @param array $params Provided call time parameters + * @throws Exception\UndefinedReferenceException When a type cannot be obtained via the ioc container and the + * method is required for injection + * @throws Exception\CircularDependencyException When a circular dependency is detected + * @return array The resulting arguments in call order + */ + private function resolveParameters(string $type, array $params = []): array + { + $resolved = $this->resolver->resolveParameters($type, $params); + $params = []; + $container = $this->container; + $containerTypes = [ + ContainerInterface::class, + 'Interop\Container\ContainerInterface' // Be backwards compatible with interop/container + ]; + + foreach ($resolved as $position => $injection) { + if ($injection instanceof Resolver\ValueInjection) { + $params[] = $injection->getValue(); + continue; + } + + if (! $injection instanceof Resolver\TypeInjection) { + throw new Exception\UnexpectedValueException( + 'Invalid injection type: ' . + (is_object($injection) ? get_class($injection) : gettype($injection)) + ); + } + + $type = $injection->getType(); + + if (! $container->has($type)) { + if (in_array($type, $containerTypes)) { + $params[] = $container; + continue; + } + + throw new Exception\UndefinedReferenceException( + 'Could not obtain instance ' . $type . + ' from ioc container for parameter ' . $position . + ' of type ' . $type + ); + } + + $params[] = $container->get($type); + } + + return $params; + } +} diff --git a/src/InjectorInterface.php b/src/InjectorInterface.php new file mode 100644 index 00000000..f424d1ab --- /dev/null +++ b/src/InjectorInterface.php @@ -0,0 +1,34 @@ + [], 'hashLong' => []]; - - /** - * Array of class aliases - * @var array key: alias, value: class - */ - protected $aliases = []; - - /** - * The template to use for housing configuration information - * @var array - */ - protected $configurationTemplate = [ - /** - * alias|class => alias|class - * interface|abstract => alias|class|object - * name => value - */ - 'parameters' => [], - /** - * injection type => array of ordered method params - */ - 'injections' => [], - /** - * alias|class => bool - */ - 'shared' => true - ]; - - /** - * An array of instance configuration data - * @var array - */ - protected $configurations = []; - - /** - * An array of globally preferred implementations for interfaces/abstracts - * @var array - */ - protected $typePreferences = []; - - /** - * Does this instance manager have this shared instance - * @param string $classOrAlias - * @return bool - */ - public function hasSharedInstance($classOrAlias) - { - return isset($this->sharedInstances[$classOrAlias]); - } - - /** - * getSharedInstance() - */ - public function getSharedInstance($classOrAlias) - { - return $this->sharedInstances[$classOrAlias]; - } - - /** - * Add shared instance - * - * @param object $instance - * @param string $classOrAlias - * @throws Exception\InvalidArgumentException - */ - public function addSharedInstance($instance, $classOrAlias) - { - if (!is_object($instance)) { - throw new Exception\InvalidArgumentException('This method requires an object to be shared. Class or Alias given: ' . $classOrAlias); - } - - $this->sharedInstances[$classOrAlias] = $instance; - } - - /** - * hasSharedInstanceWithParameters() - * - * @param string $classOrAlias - * @param array $params - * @param bool $returnFastHashLookupKey - * @return bool|string - */ - public function hasSharedInstanceWithParameters($classOrAlias, array $params, $returnFastHashLookupKey = false) - { - ksort($params); - $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); - if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) { - $hashValue = $this->createHashForValues($classOrAlias, $params); - if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) { - return ($returnFastHashLookupKey) ? $hashKey . '/' . $hashValue : true; - } - } - - return false; - } - - /** - * addSharedInstanceWithParameters() - * - * @param object $instance - * @param string $classOrAlias - * @param array $params - * @return void - */ - public function addSharedInstanceWithParameters($instance, $classOrAlias, array $params) - { - ksort($params); - $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); - $hashValue = $this->createHashForValues($classOrAlias, $params); - - if (!isset($this->sharedInstancesWithParams[$hashKey]) - || !is_array($this->sharedInstancesWithParams[$hashKey])) { - $this->sharedInstancesWithParams[$hashKey] = []; - } - - $this->sharedInstancesWithParams['hashShort'][$hashKey] = true; - $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue] = $instance; - } - - /** - * Retrieves an instance by its name and the parameters stored at its instantiation - * - * @param string $classOrAlias - * @param array $params - * @param bool|null $fastHashFromHasLookup - * @return object|bool false if no instance was found - */ - public function getSharedInstanceWithParameters($classOrAlias, array $params, $fastHashFromHasLookup = null) - { - if ($fastHashFromHasLookup) { - return $this->sharedInstancesWithParams['hashLong'][$fastHashFromHasLookup]; - } - - ksort($params); - $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params)); - if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) { - $hashValue = $this->createHashForValues($classOrAlias, $params); - if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) { - return $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue]; - } - } - - return false; - } - - /** - * Check for an alias - * - * @param string $alias - * @return bool - */ - public function hasAlias($alias) - { - return (isset($this->aliases[$alias])); - } - - /** - * Get aliases - * - * @return array - */ - public function getAliases() - { - return $this->aliases; - } - - /** - * getClassFromAlias() - * - * @param string - * @return string|bool - * @throws Exception\RuntimeException - */ - public function getClassFromAlias($alias) - { - if (!isset($this->aliases[$alias])) { - return false; - } - $r = 0; - while (isset($this->aliases[$alias])) { - $alias = $this->aliases[$alias]; - $r++; - if ($r > 100) { - throw new Exception\RuntimeException( - sprintf('Possible infinite recursion in DI alias! Max recursion of 100 levels reached at alias "%s".', $alias) - ); - } - } - - return $alias; - } - - /** - * @param string $alias - * @return string|bool - * @throws Exception\RuntimeException - */ - protected function getBaseAlias($alias) - { - if (!$this->hasAlias($alias)) { - return false; - } - $lastAlias = false; - $r = 0; - while (isset($this->aliases[$alias])) { - $lastAlias = $alias; - $alias = $this->aliases[$alias]; - $r++; - if ($r > 100) { - throw new Exception\RuntimeException( - sprintf('Possible infinite recursion in DI alias! Max recursion of 100 levels reached at alias "%s".', $alias) - ); - } - } - - return $lastAlias; - } - - /** - * Add alias - * - * @throws Exception\InvalidArgumentException - * @param string $alias - * @param string $class - * @param array $parameters - * @return void - */ - public function addAlias($alias, $class, array $parameters = []) - { - if (!preg_match('#^[a-zA-Z0-9-_]+$#', $alias)) { - throw new Exception\InvalidArgumentException( - 'Aliases must be alphanumeric and can contain dashes and underscores only.' - ); - } - $this->aliases[$alias] = $class; - if ($parameters) { - $this->setParameters($alias, $parameters); - } - } - - /** - * Check for configuration - * - * @param string $aliasOrClass - * @return bool - */ - public function hasConfig($aliasOrClass) - { - $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass; - if (!isset($this->configurations[$key])) { - return false; - } - if ($this->configurations[$key] === $this->configurationTemplate) { - return false; - } - - return true; - } - - /** - * Sets configuration for a single alias/class - * - * @param string $aliasOrClass - * @param array $configuration - * @param bool $append - */ - public function setConfig($aliasOrClass, array $configuration, $append = false) - { - $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass; - if (!isset($this->configurations[$key]) || !$append) { - $this->configurations[$key] = $this->configurationTemplate; - } - // Ignore anything but 'parameters' and 'injections' - $configuration = [ - 'parameters' => isset($configuration['parameters']) ? $configuration['parameters'] : [], - 'injections' => isset($configuration['injections']) ? $configuration['injections'] : [], - 'shared' => isset($configuration['shared']) ? $configuration['shared'] : true - ]; - $this->configurations[$key] = array_replace_recursive($this->configurations[$key], $configuration); - } - - /** - * Get classes - * - * @return array - */ - public function getClasses() - { - $classes = []; - foreach ($this->configurations as $name => $data) { - if (strpos($name, 'alias') === 0) { - continue; - } - - $classes[] = $name; - } - - return $classes; - } - - /** - * @param string $aliasOrClass - * @return array - */ - public function getConfig($aliasOrClass) - { - $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass; - if (isset($this->configurations[$key])) { - return $this->configurations[$key]; - } - - return $this->configurationTemplate; - } - - /** - * setParameters() is a convenience method for: - * setConfig($type, array('parameters' => array(...)), true); - * - * @param string $aliasOrClass Alias or Class - * @param array $parameters Multi-dim array of parameters and their values - * @return void - */ - public function setParameters($aliasOrClass, array $parameters) - { - $this->setConfig($aliasOrClass, ['parameters' => $parameters], true); - } - - /** - * setInjections() is a convenience method for: - * setConfig($type, array('injections' => array(...)), true); - * - * @param string $aliasOrClass Alias or Class - * @param array $injections Multi-dim array of methods and their parameters - * @return void - */ - public function setInjections($aliasOrClass, array $injections) - { - $this->setConfig($aliasOrClass, ['injections' => $injections], true); - } - - /** - * Set shared - * - * @param string $aliasOrClass - * @param bool $isShared - * @return void - */ - public function setShared($aliasOrClass, $isShared) - { - $this->setConfig($aliasOrClass, ['shared' => (bool) $isShared], true); - } - - /** - * Check for type preferences - * - * @param string $interfaceOrAbstract - * @return bool - */ - public function hasTypePreferences($interfaceOrAbstract) - { - $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; - - return (isset($this->typePreferences[$key]) && $this->typePreferences[$key]); - } - - /** - * Set type preference - * - * @param string $interfaceOrAbstract - * @param array $preferredImplementations - * @return InstanceManager - */ - public function setTypePreference($interfaceOrAbstract, array $preferredImplementations) - { - $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; - foreach ($preferredImplementations as $preferredImplementation) { - $this->addTypePreference($key, $preferredImplementation); - } - - return $this; - } - - /** - * Get type preferences - * - * @param string $interfaceOrAbstract - * @return array - */ - public function getTypePreferences($interfaceOrAbstract) - { - $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; - if (isset($this->typePreferences[$key])) { - return $this->typePreferences[$key]; - } - - return []; - } - - /** - * Unset type preferences - * - * @param string $interfaceOrAbstract - * @return void - */ - public function unsetTypePreferences($interfaceOrAbstract) - { - $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; - unset($this->typePreferences[$key]); - } - - /** - * Adds a type preference. A type preference is a redirection to a preferred alias or type when an abstract type - * $interfaceOrAbstract is requested - * - * @param string $interfaceOrAbstract - * @param string $preferredImplementation - * @return self - */ - public function addTypePreference($interfaceOrAbstract, $preferredImplementation) - { - $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; - if (!isset($this->typePreferences[$key])) { - $this->typePreferences[$key] = []; - } - $this->typePreferences[$key][] = $preferredImplementation; - - return $this; - } - - /** - * Removes a previously set type preference - * - * @param string $interfaceOrAbstract - * @param string $preferredType - * @return bool|self - */ - public function removeTypePreference($interfaceOrAbstract, $preferredType) - { - $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract; - if (!isset($this->typePreferences[$key]) || !in_array($preferredType, $this->typePreferences[$key])) { - return false; - } - unset($this->typePreferences[$key][array_search($key, $this->typePreferences)]); - - return $this; - } - - /** - * @param string $classOrAlias - * @param string[] $paramKeys - * @return string - */ - protected function createHashForKeys($classOrAlias, $paramKeys) - { - return $classOrAlias . ':' . implode('|', $paramKeys); - } - - /** - * @param string $classOrAlias - * @param array $paramValues - * @return string - */ - protected function createHashForValues($classOrAlias, $paramValues) - { - $hashValue = ''; - foreach ($paramValues as $param) { - switch (gettype($param)) { - case 'object': - $hashValue .= spl_object_hash($param) . '|'; - break; - case 'integer': - case 'string': - case 'boolean': - case 'NULL': - case 'double': - $hashValue .= $param . '|'; - break; - case 'array': - $hashValue .= $this->createHashForValues($classOrAlias, $param) . '|'; - break; - case 'resource': - $hashValue .= 'resource|'; - break; - } - } - - return $hashValue; - } -} diff --git a/src/LegacyConfig.php b/src/LegacyConfig.php new file mode 100644 index 00000000..cddd2337 --- /dev/null +++ b/src/LegacyConfig.php @@ -0,0 +1,98 @@ +configureInstance($config['instance']); + } + } + + private function prepareParametersArray($parameters, string $class) + { + $prepared = []; + + foreach ($parameters as $key => $value) { + if (strpos($key, ':') !== false) { + trigger_error('Full qualified parameter positions are no longer supported', E_USER_DEPRECATED); + } + + $prepared[$key] = $value; + } + + return $prepared; + } + + private function configureInstance($config) + { + foreach ($config as $target => $data) { + switch ($target) { + case 'aliases': + case 'alias': + foreach ($data as $name => $class) { + if (class_exists($class) || interface_exists($class)) { + $this->setAlias($name, $class); + } + } + break; + + case 'preferences': + case 'preference': + foreach ($data as $type => $pref) { + $preference = is_array($pref) ? array_pop($pref) : $pref; + $this->setTypePreference($type, $preference); + } + break; + + default: + $config = new Parameters($data); + $parameters = $config->get('parameters', $config->get('parameter')); + + if (is_array($parameters) || ($parameters instanceof \Traversable)) { + $parameters = $this->prepareParametersArray($parameters, $target); + $this->setParameters($target, $parameters); + } + break; + } + } + } + + /** + * Export the configuraton to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'preferences' => $this->preferences, + 'types' => $this->types + ]; + } +} diff --git a/src/Module.php b/src/Module.php new file mode 100644 index 00000000..d227a4c6 --- /dev/null +++ b/src/Module.php @@ -0,0 +1,43 @@ + [ + * 'Zend\\Di', + * // ... + * ] + * ]; + * ``` + */ +class Module +{ + /** + * Returns the configuration for zend-mvc + * + * @return array + */ + public function getConfig(): array + { + $provider = new ConfigProvider(); + return [ + 'service_manager' => $provider->getDependencyConfig(), + ]; + } +} diff --git a/src/Resolver/AbstractInjection.php b/src/Resolver/AbstractInjection.php new file mode 100644 index 00000000..75cc04b9 --- /dev/null +++ b/src/Resolver/AbstractInjection.php @@ -0,0 +1,46 @@ +parameterName = $name; + return $this; + } + + /** + * @return string + */ + public function getParameterName(): string + { + return $this->parameterName; + } + + /** + * @return string + */ + abstract public function export(): string; + + /** + * @return bool + */ + abstract public function isExportable(): bool; +} diff --git a/src/Resolver/DependencyResolver.php b/src/Resolver/DependencyResolver.php new file mode 100644 index 00000000..d5fb38e5 --- /dev/null +++ b/src/Resolver/DependencyResolver.php @@ -0,0 +1,338 @@ +definition = $definition; + $this->config = $config; + } + + /** + * @param string $type + * @return \Zend\Di\Definition\ClassDefinitionInterface + */ + private function getClassDefinition(string $type): ClassDefinitionInterface + { + if ($this->config->isAlias($type)) { + $type = $this->config->getClassForAlias($type); + } + + return $this->definition->getClassDefinition($type); + } + + /** + * Returns the configured injections for the requested type + * + * If type is an alias it will try to fall back to the class configuration if no parameters + * were defined for it + * + * @param string $requestedType The type name to get injections for + * @return array Injections for the method indexed by the parameter name + */ + private function getConfiguredParameters(string $requestedType): array + { + $config = $this->config; + $params = $config->getParameters($requestedType); + $isAlias = $config->isAlias($requestedType); + $class = $isAlias ? $config->getClassForAlias($requestedType) : $requestedType; + + if ($isAlias) { + $params = array_merge($config->getParameters($class), $params); + } + + $definition = $this->getClassDefinition($class); + + foreach ($definition->getSupertypes() as $supertype) { + $supertypeParams = $config->getParameters($supertype); + + if (! empty($supertypeParams)) { + $params = array_merge($supertypeParams, $params); + } + } + + // A type configuration may define a parameter should be auto resolved + // even it was defined earlier + $params = array_filter($params, function ($value) { + return ($value != '*'); + }); + + return $params; + } + + /** + * Check if $type satisfies $requiredType + * + * @param string $type The type to check + * @param string $requiredType The required type to check against + * @return bool + */ + private function isTypeOf(string $type, string $requiredType): bool + { + if ($this->config->isAlias($type)) { + $type = $this->config->getClassForAlias($type); + } + + if ($type == $requiredType) { + return true; + } + + if (interface_exists($type) && interface_exists($requiredType)) { + $reflection = new \ReflectionClass($type); + return in_array($requiredType, $reflection->getInterfaceNames()); + } + + if (! $this->definition->hasClass($type)) { + return false; + } + + $definition = $this->definition->getClassDefinition($type); + return in_array($requiredType, $definition->getSupertypes()) || + in_array($requiredType, $definition->getInterfaces()); + } + + /** + * @param string $type + * @param string $requiredType + * @return boolean + */ + private function isUsableType(string $type, string $requiredType) + { + return ($this->isTypeOf($type, $requiredType) && (! $this->container || $this->container->has($type))); + } + + /** + * Check if the given value sadisfies the given type + * + * @param mixed $value The value to check + * @param string $type The typename to check against + * @return bool + */ + private function isValueOf($value, string $type) + { + if (! $this->isBuiltinType($type)) { + return ($value instanceof $type); + } + + if ($type == 'callable') { + return is_callable($value); + } + + if ($type == 'iterable') { + return (is_array($value) || ($value instanceof \Traversable)); + } + + return ($type == gettype($value)); + } + + /** + * @param string $type + * @return bool + */ + private function isBuiltinType(string $type): bool + { + return in_array($type, $this->builtinTypes); + } + + /** + * @see \Zend\Di\Resolver\DependencyResolverInterface::setContainer() + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + return $this; + } + + /** + * Prepare a candidate for injection + * + * If the candidate is usable, its injection representation is returned + * + * @param mixed $value + * @param string $requiredType + * @return null|TypeInjection|ValueInjection + */ + private function prepareInjection($value, ?string $requiredType): ?AbstractInjection + { + if (($value instanceof ValueInjection) || ($value instanceof TypeInjection)) { + return $value; + } + + if (! $requiredType) { + $isAvailableInContainer = (is_string($value) && $this->container && $this->container->has($value)); + return $isAvailableInContainer ? new TypeInjection($value) : new ValueInjection($value); + } + + if (is_string($value) && ($requiredType != 'string')) { + return $this->isUsableType($value, $requiredType) ? new TypeInjection($value) : null; + } + + return $this->isValueOf($value, $requiredType) ? new ValueInjection($value) : null; + } + + /** + * {@inheritDoc} + * + * @see \Zend\Di\Resolver\DependencyResolverInterface::resolveParameters() + * @param string $requestedType + * @param array $callTimeParameters + * @throws \Zend\Di\Exception\UnexpectedValueException + * @throws \Zend\Di\Exception\MissingPropertyException + * @return AbstractInjection[] + */ + public function resolveParameters(string $requestedType, array $callTimeParameters = []): array + { + $definition = $this->getClassDefinition($requestedType); + $params = $definition->getParameters(); + $result = []; + + if (empty($params)) { + return $result; + } + + $configuredParameters = $this->getConfiguredParameters($requestedType); + + foreach ($params as $paramInfo) { + $name = $paramInfo->getName(); + $type = $paramInfo->getType(); + + if (isset($callTimeParameters[$name])) { + $result[$name] = new ValueInjection($callTimeParameters[$name]); + continue; + } + + if (isset($configuredParameters[$name]) && ($configuredParameters[$name] !== '*')) { + $injection = $this->prepareInjection($configuredParameters[$name], $type); + + if (! $injection) { + throw new Exception\UnexpectedValueException( + 'Unusable configured injection for parameter "' . $name . + '" of type "' . $type . '"' + ); + } + + $result[$name] = $injection; + continue; + } + + if ($type && ! $paramInfo->isBuiltin()) { + $preference = $this->resolvePreference($type, $requestedType); + + if ($preference) { + $result[$name] = new TypeInjection($preference); + continue; + } + + if (($type === ContainerInterface::class) || ! $this->container || $this->container->has($type)) { + $result[$name] = new TypeInjection($type); + continue; + } + } + + // The parameter is required, but we can't find anything that is suitable + if ($paramInfo->isRequired()) { + $isAlias = $this->config->isAlias($requestedType); + $class = $isAlias ? $this->config->getClassForAlias($requestedType) : $requestedType; + throw new Exception\MissingPropertyException(sprintf( + 'Could not resolve value for parameter "%s" of type %s in class %s (requested as %s)', + $name, + $type ? : 'any', + $class, + $requestedType + )); + } + + $result[$name] = new ValueInjection($paramInfo->getDefault()); + } + + foreach ($result as $name => $injection) { + $injection->setParameterName($name); + } + + return $result; + } + + /** + * @see \Zend\Di\Resolver\DependencyResolverInterface::resolvePreference() + */ + public function resolvePreference(string $type, ?string $context = null): ?string + { + if ($context) { + $preference = $this->config->getTypePreference($type, $context); + + if ($preference && $this->isUsableType($preference, $type)) { + return $preference; + } + + $definition = $this->getClassDefinition($context); + + foreach ($definition->getSupertypes() as $supertype) { + $preference = $this->config->getTypePreference($type, $supertype); + + if ($preference && $this->isUsableType($preference, $type)) { + return $preference; + } + } + + foreach ($definition->getInterfaces() as $interface) { + $preference = $this->config->getTypePreference($type, $interface); + + if ($preference && $this->isUsableType($preference, $type)) { + return $preference; + } + } + } + + $preference = $this->config->getTypePreference($type); + + if (! $preference || ! $this->isUsableType($preference, $type)) { + $preference = null; + } + + return $preference; + } +} diff --git a/src/Resolver/DependencyResolverInterface.php b/src/Resolver/DependencyResolverInterface.php new file mode 100644 index 00000000..a55a9143 --- /dev/null +++ b/src/Resolver/DependencyResolverInterface.php @@ -0,0 +1,51 @@ +type = $type; + } + + /** + * Get the type name to look up for injection + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Resolver\AbstractInjection::export() + */ + public function export(): string + { + return var_export($this->type, true); + } + + /** + * {@inheritDoc} + * @see \Zend\Di\Resolver\AbstractInjection::isExportable() + */ + public function isExportable(): bool + { + return true; + } + + /** + * Simply converts to the type name string + * + * @return string + */ + public function __toString(): string + { + return $this->type; + } +} diff --git a/src/Resolver/ValueInjection.php b/src/Resolver/ValueInjection.php new file mode 100644 index 00000000..070cb65a --- /dev/null +++ b/src/Resolver/ValueInjection.php @@ -0,0 +1,87 @@ +value = $value; + } + + /** + * @param string $state + */ + public static function __set_state($state): self + { + return new self($state['value']); + } + + /** + * Get the value to inject + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Exports the encapsulated value to php code + * + * @return string + * @throws RuntimeException + */ + public function export(): string + { + if (! $this->isExportable()) { + throw new RuntimeException('Unable to export value'); + } + + return var_export($this->value, true); + } + + /** + * Checks wether the value can be exported for code generation or not + * + * @return bool + */ + public function isExportable(): bool + { + if (is_scalar($this->value) || ($this->value === null)) { + return true; + } + + if (is_object($this->value) && method_exists($this->value, '__set_state')) { + $reflection = new \ReflectionObject($this->value); + $method = $reflection->getMethod('__set_state'); + + return ($method->isStatic() && $method->isPublic()); + } + + return false; + } +} diff --git a/src/ServiceLocator.php b/src/ServiceLocator.php deleted file mode 100644 index a01965fa..00000000 --- a/src/ServiceLocator.php +++ /dev/null @@ -1,104 +0,0 @@ - - * protected $map = array('foo' => 'getFoo'); - * - * - * When encountered, the return value of that method will be used. - * - * Methods mapped in this way may expect a single, array argument, the - * $params passed to {@link get()}, if any. - * - * @var array - */ - protected $map = []; - - /** - * Registered services and cached values - * - * @var array - */ - protected $services = []; - - /** - * {@inheritDoc} - */ - public function set($name, $service) - { - $this->services[$name] = $service; - - return $this; - } - - /** - * Can the locator return the named instance? - * - * @param string $name - * @return bool - */ - public function has($name) - { - return isset($this->map[$name]) || isset($this->services[$name]); - } - - /** - * Retrieve a registered service - * - * Tests first if a value is registered for the service, and, if so, - * returns it. - * - * If the value returned is a non-object callback or closure, the return - * value is retrieved, stored, and returned. Parameters passed to the method - * are passed to the callback, but only on the first retrieval. - * - * If the service requested matches a method in the method map, the return - * value of that method is returned. Parameters are passed to the matching - * method. - * - * @param string $name - * @param array $params - * @return mixed - */ - public function get($name, array $params = []) - { - if (!isset($this->services[$name])) { - if (!isset($this->map[$name])) { - return; - } - $method = $this->map[$name]; - - return $this->$method($params); - } - - $service = $this->services[$name]; - if ($service instanceof Closure - || (!is_object($service) && is_callable($service)) - ) { - $this->services[$name] = $service = call_user_func_array($service, $params); - } - - return $service; - } -} diff --git a/src/ServiceLocator/DependencyInjectorProxy.php b/src/ServiceLocator/DependencyInjectorProxy.php deleted file mode 100644 index bc21641d..00000000 --- a/src/ServiceLocator/DependencyInjectorProxy.php +++ /dev/null @@ -1,168 +0,0 @@ -di = $di; - $this->definitions = $di->definitions(); - $this->instanceManager = $di->instanceManager(); - } - - /** - * {@inheritDoc} - * @return GeneratorInstance - */ - public function get($name, array $params = []) - { - return parent::get($name, $params); - } - - /** - * {@inheritDoc} - * @return GeneratorInstance - */ - public function newInstance($name, array $params = [], $isShared = true) - { - $instance = parent::newInstance($name, $params, $isShared); - - if ($instance instanceof GeneratorInstance) { - /* @var $instance GeneratorInstance */ - $instance->setShared($isShared); - - // When a callback is used, we don't know instance the class name. - // That's why we assume $name as the instance alias - if (null === $instance->getName()) { - $instance->setAlias($name); - } - } - - return $instance; - } - - /** - * {@inheritDoc} - * @return GeneratorInstance - */ - public function createInstanceViaConstructor($class, $params, $alias = null) - { - $callParameters = []; - - if ($this->di->definitions->hasMethod($class, '__construct') - && (count($this->di->definitions->getMethodParameters($class, '__construct')) > 0) - ) { - $callParameters = $this->resolveMethodParameters($class, '__construct', $params, $alias, true, true); - $callParameters = $callParameters ?: []; - } - - return new GeneratorInstance($class, $alias, '__construct', $callParameters); - } - - /** - * {@inheritDoc} - * @throws \Zend\Di\Exception\InvalidCallbackException - * @return GeneratorInstance - */ - public function createInstanceViaCallback($callback, $params, $alias) - { - if (is_string($callback)) { - $callback = explode('::', $callback); - } - - if (!is_callable($callback)) { - throw new Exception\InvalidCallbackException('An invalid constructor callback was provided'); - } - - if (!is_array($callback) || is_object($callback[0])) { - throw new Exception\InvalidCallbackException( - 'For purposes of service locator generation, constructor callbacks must refer to static methods only' - ); - } - - $class = $callback[0]; - $method = $callback[1]; - - $callParameters = []; - if ($this->di->definitions->hasMethod($class, $method)) { - $callParameters = $this->resolveMethodParameters($class, $method, $params, $alias, true, true); - } - - $callParameters = $callParameters ?: []; - - return new GeneratorInstance(null, $alias, $callback, $callParameters); - } - - /** - * {@inheritDoc} - */ - public function handleInjectionMethodForObject($class, $method, $params, $alias, $isRequired) - { - return [ - 'method' => $method, - 'params' => $this->resolveMethodParameters($class, $method, $params, $alias, $isRequired), - ]; - } - - /** - * {@inheritDoc} - */ - protected function resolveAndCallInjectionMethodForInstance($instance, $method, $params, $alias, $methodIsRequired, $methodClass = null) - { - if (!$instance instanceof GeneratorInstance) { - return parent::resolveAndCallInjectionMethodForInstance($instance, $method, $params, $alias, $methodIsRequired, $methodClass); - } - - /* @var $instance GeneratorInstance */ - $methodClass = $instance->getClass(); - $callParameters = $this->resolveMethodParameters($methodClass, $method, $params, $alias, $methodIsRequired); - - if ($callParameters !== false) { - $instance->addMethod([ - 'method' => $method, - 'params' => $callParameters, - ]); - - return true; - } - - return false; - } - - /** - * {@inheritDoc} - */ - protected function getClass($instance) - { - if ($instance instanceof GeneratorInstance) { - /* @var $instance GeneratorInstance */ - - return $instance->getClass(); - } - - return parent::getClass($instance); - } -} diff --git a/src/ServiceLocator/Generator.php b/src/ServiceLocator/Generator.php deleted file mode 100644 index 4e78ac43..00000000 --- a/src/ServiceLocator/Generator.php +++ /dev/null @@ -1,342 +0,0 @@ -injector = new DependencyInjectorProxy($injector); - } - - /** - * Set the class name for the generated service locator container - * - * @param string $name - * @return Generator - */ - public function setContainerClass($name) - { - $this->containerClass = $name; - - return $this; - } - - /** - * Set the namespace to use for the generated class file - * - * @param string $namespace - * @return Generator - */ - public function setNamespace($namespace) - { - $this->namespace = $namespace; - - return $this; - } - - /** - * Construct, configure, and return a PHP class file code generation object - * - * Creates a Zend\Code\Generator\FileGenerator object that has - * created the specified class and service locator methods. - * - * @param null|string $filename - * @throws \Zend\Di\Exception\RuntimeException - * @return FileGenerator - */ - public function getCodeGenerator($filename = null) - { - $injector = $this->injector; - $im = $injector->instanceManager(); - $indent = ' '; - $aliases = $this->reduceAliases($im->getAliases()); - $caseStatements = []; - $getters = []; - $definitions = $injector->definitions(); - - $fetched = array_unique(array_merge($definitions->getClasses(), $im->getAliases())); - - foreach ($fetched as $name) { - $getter = $this->normalizeAlias($name); - $meta = $injector->get($name); - $params = $meta->getParams(); - - // Build parameter list for instantiation - foreach ($params as $key => $param) { - if (null === $param || is_scalar($param) || is_array($param)) { - $string = var_export($param, 1); - if (strstr($string, '::__set_state(')) { - throw new Exception\RuntimeException('Arguments in definitions may not contain objects'); - } - $params[$key] = $string; - } elseif ($param instanceof GeneratorInstance) { - /* @var $param GeneratorInstance */ - $params[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getName())); - } else { - $message = sprintf('Unable to use object arguments when building containers. Encountered with "%s", parameter of type "%s"', $name, get_class($param)); - throw new Exception\RuntimeException($message); - } - } - - // Strip null arguments from the end of the params list - $reverseParams = array_reverse($params, true); - foreach ($reverseParams as $key => $param) { - if ('NULL' === $param) { - unset($params[$key]); - continue; - } - break; - } - - // Create instantiation code - $constructor = $meta->getConstructor(); - if ('__construct' != $constructor) { - // Constructor callback - $callback = var_export($constructor, 1); - if (strstr($callback, '::__set_state(')) { - throw new Exception\RuntimeException('Unable to build containers that use callbacks requiring object instances'); - } - if (count($params)) { - $creation = sprintf('$object = call_user_func(%s, %s);', $callback, implode(', ', $params)); - } else { - $creation = sprintf('$object = call_user_func(%s);', $callback); - } - } else { - // Normal instantiation - $className = '\\' . ltrim($name, '\\'); - $creation = sprintf('$object = new %s(%s);', $className, implode(', ', $params)); - } - - // Create method call code - $methods = ''; - foreach ($meta->getMethods() as $methodData) { - if (!isset($methodData['name']) && !isset($methodData['method'])) { - continue; - } - $methodName = isset($methodData['name']) ? $methodData['name'] : $methodData['method']; - $methodParams = $methodData['params']; - - // Create method parameter representation - foreach ($methodParams as $key => $param) { - if (null === $param || is_scalar($param) || is_array($param)) { - $string = var_export($param, 1); - if (strstr($string, '::__set_state(')) { - throw new Exception\RuntimeException('Arguments in definitions may not contain objects'); - } - $methodParams[$key] = $string; - } elseif ($param instanceof GeneratorInstance) { - $methodParams[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getName())); - } else { - $message = sprintf('Unable to use object arguments when generating method calls. Encountered with class "%s", method "%s", parameter of type "%s"', $name, $methodName, get_class($param)); - throw new Exception\RuntimeException($message); - } - } - - // Strip null arguments from the end of the params list - $reverseParams = array_reverse($methodParams, true); - foreach ($reverseParams as $key => $param) { - if ('NULL' === $param) { - unset($methodParams[$key]); - continue; - } - break; - } - - $methods .= sprintf("\$object->%s(%s);\n", $methodName, implode(', ', $methodParams)); - } - - // Generate caching statement - $storage = ''; - if ($im->hasSharedInstance($name, $params)) { - $storage = sprintf("\$this->services['%s'] = \$object;\n", $name); - } - - // Start creating getter - $getterBody = ''; - - // Create fetch of stored service - if ($im->hasSharedInstance($name, $params)) { - $getterBody .= sprintf("if (isset(\$this->services['%s'])) {\n", $name); - $getterBody .= sprintf("%sreturn \$this->services['%s'];\n}\n\n", $indent, $name); - } - - // Creation and method calls - $getterBody .= sprintf("%s\n", $creation); - $getterBody .= $methods; - - // Stored service - $getterBody .= $storage; - - // End getter body - $getterBody .= "return \$object;\n"; - - $getterDef = new MethodGenerator(); - $getterDef->setName($getter); - $getterDef->setBody($getterBody); - $getters[] = $getterDef; - - // Get cases for case statements - $cases = [$name]; - if (isset($aliases[$name])) { - $cases = array_merge($aliases[$name], $cases); - } - - // Build case statement and store - $statement = ''; - foreach ($cases as $value) { - $statement .= sprintf("%scase '%s':\n", $indent, $value); - } - $statement .= sprintf("%sreturn \$this->%s();\n", str_repeat($indent, 2), $getter); - - $caseStatements[] = $statement; - } - - // Build switch statement - $switch = sprintf("switch (%s) {\n%s\n", '$name', implode("\n", $caseStatements)); - $switch .= sprintf("%sdefault:\n%sreturn parent::get(%s, %s);\n", $indent, str_repeat($indent, 2), '$name', '$params'); - $switch .= "}\n\n"; - - // Build get() method - $nameParam = new ParameterGenerator(); - $nameParam->setName('name'); - $paramsParam = new ParameterGenerator(); - $paramsParam->setName('params') - ->setType('array') - ->setDefaultValue([]); - - $get = new MethodGenerator(); - $get->setName('get'); - $get->setParameters([ - $nameParam, - $paramsParam, - ]); - $get->setBody($switch); - - // Create getters for aliases - $aliasMethods = []; - foreach ($aliases as $class => $classAliases) { - foreach ($classAliases as $alias) { - $aliasMethods[] = $this->getCodeGenMethodFromAlias($alias, $class); - } - } - - // Create class code generation object - $container = new ClassGenerator(); - $container->setName($this->containerClass) - ->setExtendedClass('ServiceLocator') - ->addMethodFromGenerator($get) - ->addMethods($getters) - ->addMethods($aliasMethods); - - // Create PHP file code generation object - $classFile = new FileGenerator(); - $classFile->setUse('Zend\Di\ServiceLocator') - ->setClass($container); - - if (null !== $this->namespace) { - $classFile->setNamespace($this->namespace); - } - - if (null !== $filename) { - $classFile->setFilename($filename); - } - - return $classFile; - } - - /** - * Reduces aliases - * - * Takes alias list and reduces it to a 2-dimensional array of - * class names pointing to an array of aliases that resolve to - * it. - * - * @param array $aliasList - * @return array - */ - protected function reduceAliases(array $aliasList) - { - $reduced = []; - $aliases = array_keys($aliasList); - foreach ($aliasList as $alias => $service) { - if (in_array($service, $aliases)) { - do { - $service = $aliasList[$service]; - } while (in_array($service, $aliases)); - } - if (!isset($reduced[$service])) { - $reduced[$service] = []; - } - $reduced[$service][] = $alias; - } - - return $reduced; - } - - /** - * Create a PhpMethod code generation object named after a given alias - * - * @param string $alias - * @param string $class Class to which alias refers - * @return MethodGenerator - */ - protected function getCodeGenMethodFromAlias($alias, $class) - { - $alias = $this->normalizeAlias($alias); - $method = new MethodGenerator(); - $method->setName($alias); - $method->setBody(sprintf('return $this->get(\'%s\');', $class)); - - return $method; - } - - /** - * Normalize an alias to a getter method name - * - * @param string $alias - * @return string - */ - protected function normalizeAlias($alias) - { - $normalized = preg_replace('/[^a-zA-Z0-9]/', ' ', $alias); - $normalized = 'get' . str_replace(' ', '', ucwords($normalized)); - - return $normalized; - } -} diff --git a/src/ServiceLocator/GeneratorInstance.php b/src/ServiceLocator/GeneratorInstance.php deleted file mode 100644 index 4f2801ad..00000000 --- a/src/ServiceLocator/GeneratorInstance.php +++ /dev/null @@ -1,196 +0,0 @@ -class = $class; - $this->alias = $alias; - $this->constructor = $constructor; - $this->params = $params; - } - - /** - * Retrieves the best available name for this instance (instance alias first then class name) - * - * @return string|null - */ - public function getName() - { - return $this->alias ? $this->alias : $this->class; - } - - /** - * Class of the instance. Null if class is unclear (such as when the instance is produced by a callback) - * - * @return string|null - */ - public function getClass() - { - return $this->class; - } - - /** - * Alias for the instance (if any) - * - * @return string|null - */ - public function getAlias() - { - return $this->alias; - } - - /** - * Set class name - * - * In the case of an instance created via a callback, we need to set the - * class name after creating the generator instance. - * - * @param string $class - * @return GeneratorInstance - */ - public function setClass($class) - { - $this->class = $class; - - return $this; - } - - /** - * Set instance alias - * - * @param string $alias - * @return GeneratorInstance - */ - public function setAlias($alias) - { - $this->alias = $alias; - - return $this; - } - - /** - * Get instantiator - * - * @return mixed constructor method name or callable responsible for generating instance - */ - public function getConstructor() - { - return $this->constructor; - } - - /** - * Parameters passed to the instantiator as an ordered list of parameters. Each parameter that refers to another - * instance fetched recursively is a GeneratorInstance itself - * - * @return array - */ - public function getParams() - { - return $this->params; - } - - /** - * Set methods - * - * @param array $methods - * @return GeneratorInstance - */ - public function setMethods(array $methods) - { - $this->methods = $methods; - - return $this; - } - - /** - * Add a method called on the instance - * - * @param $method - * @return GeneratorInstance - */ - public function addMethod($method) - { - $this->methods[] = $method; - - return $this; - } - - /** - * Retrieves a list of methods that are called on the instance in their call order. Each returned element has form - * array('method' => 'methodName', 'params' => array( ... ordered list of call parameters ... ), where every call - * parameter that is a recursively fetched instance is a GeneratorInstance itself - * - * @return array - */ - public function getMethods() - { - return $this->methods; - } - - /** - * @param bool $shared - */ - public function setShared($shared) - { - $this->shared = (bool) $shared; - } - - /** - * Retrieves whether the instance is shared or not - * - * @return bool - */ - public function isShared() - { - return $this->shared; - } -} diff --git a/src/ServiceLocatorInterface.php b/src/ServiceLocatorInterface.php deleted file mode 100644 index 4bfd3d45..00000000 --- a/src/ServiceLocatorInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -setOutputDirectory($this->dir); + $classmap = [ + 'FooClass' => 'FooClass.php', + 'Bar\\Class' => 'Bar/Class.php' + ]; + + $generator->generate($classmap); + $this->assertFileExists($this->dir . '/Autoloader.php'); + $this->assertFileExists($this->dir . '/autoload.php'); + } +} diff --git a/test/CodeGenerator/FactoryGeneratorTest.php b/test/CodeGenerator/FactoryGeneratorTest.php new file mode 100644 index 00000000..fe5c2a13 --- /dev/null +++ b/test/CodeGenerator/FactoryGeneratorTest.php @@ -0,0 +1,58 @@ +setOutputDirectory($this->dir . '/Factory'); + $generator->generate(TestAsset\RequiresA::class); + + $this->assertFileExists($this->dir . '/Factory/ZendTest/Di/TestAsset/RequiresAFactory.php'); + } + + public function testGenerateBuildsUpClassMap() + { + $config = new Config(); + $resolver = new DependencyResolver(new RuntimeDefinition(), $config); + $generator = new FactoryGenerator($config, $resolver, self::DEFAULT_NAMESPACE); + + $generator->setOutputDirectory($this->dir . '/FactoryMultiple'); + + $f1 = $generator->generate(TestAsset\RequiresA::class); + $f2 = $generator->generate(TestAsset\Constructor\EmptyConstructor::class); + + $expected = [ + $f1 => str_replace('\\', '/', TestAsset\RequiresA::class) . 'Factory.php', + $f2 => str_replace('\\', '/', TestAsset\Constructor\EmptyConstructor::class) . 'Factory.php', + ]; + + $this->assertEquals($expected, $generator->getClassmap()); + } +} diff --git a/test/CodeGenerator/GeneratorTestTrait.php b/test/CodeGenerator/GeneratorTestTrait.php new file mode 100644 index 00000000..d99ddcb9 --- /dev/null +++ b/test/CodeGenerator/GeneratorTestTrait.php @@ -0,0 +1,68 @@ +isDir()) { + $result = rmdir($file->getPathname()); + } else { + $result = unlink($file->getPathname()); + } + + if (! $result) { + throw new \RuntimeException('Failed to remove "' . $file->getPathname() . '"'); + } + } + + if (! rmdir($dir)) { + throw new \RuntimeException('Failed to remove "' . $file->getPathname() . '"'); + } + } + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + parent::setUp(); + $this->dir = __DIR__ . '/_result'; + + $this->removeDirectory($this->dir); + mkdir($this->dir, 0777); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->removeDirectory($this->dir); + parent::tearDown(); + } +} diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 826267f2..315f1b25 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -10,123 +10,69 @@ namespace ZendTest\Di; use Zend\Di\Config; -use Zend\Di\Di; -use PHPUnit_Framework_TestCase as TestCase; +use PHPUnit\Framework\TestCase; class ConfigTest extends TestCase { - public function testConfigCanConfigureInstanceManagerWithIniFile() - { - $ini = include __DIR__ . '/_files/sample-definitions.php'; - $ini = $ini['section-a']; - $config = new Config($ini['di']); - $di = new Di(); - $di->configure($config); - - $im = $di->instanceManager(); - - $this->assertTrue($im->hasAlias('my-repository')); - $this->assertEquals('My\RepositoryA', $im->getClassFromAlias('my-repository')); - - $this->assertTrue($im->hasAlias('my-mapper')); - $this->assertEquals('My\Mapper', $im->getClassFromAlias('my-mapper')); - - $this->assertTrue($im->hasAlias('my-dbAdapter')); - $this->assertEquals('My\DbAdapter', $im->getClassFromAlias('my-dbAdapter')); - - $this->assertTrue($im->hasTypePreferences('my-repository')); - $this->assertContains('my-mapper', $im->getTypePreferences('my-repository')); + /** + * @var array + */ + private $fixture; - $this->assertTrue($im->hasTypePreferences('my-mapper')); - $this->assertContains('my-dbAdapter', $im->getTypePreferences('my-mapper')); - - $this->assertTrue($im->hasConfig('My\DbAdapter')); - $expected = ['parameters' => ['username' => 'readonly', 'password' => 'mypassword'], 'injections' => [], 'shared' => true]; - $this->assertEquals($expected, $im->getConfig('My\DbAdapter')); - - $this->assertTrue($im->hasConfig('my-dbAdapter')); - $expected = ['parameters' => ['username' => 'readwrite'], 'injections' => [], 'shared' => true]; - $this->assertEquals($expected, $im->getConfig('my-dbAdapter')); + protected function setUp() + { + parent::setUp(); + $this->fixture = include __DIR__ . '/_files/sample-config.php'; } - public function testConfigCanConfigureBuilderDefinitionFromIni() + public function testGetConfiguredTypeName() { - $this->markTestIncomplete('Builder not updated to new DI yet'); - $ini = include __DIR__ . '/_files/sample-definitions.php'; - $ini = $ini['section-b']; - $config = new Config($ini['di']); - $di = new Di($config); - $definition = $di->getDefinition(); - - $this->assertTrue($definition->hasClass('My\DbAdapter')); - $this->assertEquals('__construct', $definition->getInstantiator('My\DbAdapter')); - $this->assertEquals( - ['username' => null, 'password' => null], - $definition->getInjectionMethodParameters('My\DbAdapter', '__construct') - ); - - $this->assertTrue($definition->hasClass('My\Mapper')); - $this->assertEquals('__construct', $definition->getInstantiator('My\Mapper')); - $this->assertEquals( - ['dbAdapter' => 'My\DbAdapter'], - $definition->getInjectionMethodParameters('My\Mapper', '__construct') - ); - - $this->assertTrue($definition->hasClass('My\Repository')); - $this->assertEquals('__construct', $definition->getInstantiator('My\Repository')); - $this->assertEquals( - ['mapper' => 'My\Mapper'], - $definition->getInjectionMethodParameters('My\Repository', '__construct') - ); + $config = new Config($this->fixture); + $this->assertEquals(['Foo', 'Bar'], $config->getConfiguredTypeNames()); } - public function testConfigCanConfigureRuntimeDefinitionDefaultFromIni() + public function testIsAlias() { - $ini = include __DIR__ . '/_files/sample-definitions.php'; - $ini = $ini['section-c']; - $config = new Config($ini['di']); - $di = new Di(); - $di->configure($config); - $definition = $di->definitions()->getDefinitionByType('Zend\Di\Definition\RuntimeDefinition'); - $this->assertInstanceOf('Zend\Di\Definition\RuntimeDefinition', $definition); - $this->assertFalse($definition->getIntrospectionStrategy()->getUseAnnotations()); + $config = new Config($this->fixture); + $this->assertTrue($config->isAlias('Bar')); + $this->assertFalse($config->isAlias('Foo')); + $this->assertFalse($config->isAlias('DoesNotExist')); } - public function testConfigCanConfigureRuntimeDefinitionDisabledFromIni() + public function testGetClassForAlias() { - $ini = include __DIR__ . '/_files/sample-definitions.php'; - $ini = $ini['section-d']; - $config = new Config($ini['di']); - $di = new Di(); - $di->configure($config); - $definition = $di->definitions()->getDefinitionByType('Zend\Di\Definition\RuntimeDefinition'); - $this->assertFalse($definition); + $config = new Config($this->fixture); + $this->assertEquals('Foo', $config->getClassForAlias('Bar')); + $this->assertNull($config->getClassForAlias('Foo')); + $this->assertNull($config->getClassForAlias('DoesNotExist')); } - public function testConfigCanConfigureRuntimeDefinitionUseAnnotationFromIni() + public function testGetParameters() { - $ini = include __DIR__ . '/_files/sample-definitions.php'; - $ini = $ini['section-e']; - $config = new Config($ini['di']); - $di = new Di(); - $di->configure($config); - $definition = $di->definitions()->getDefinitionByType('Zend\Di\Definition\RuntimeDefinition'); - $this->assertTrue($definition->getIntrospectionStrategy()->getUseAnnotations()); + $config = new Config($this->fixture); + $this->assertEquals(['a' => '*'], $config->getParameters('Foo')); + $this->assertEquals([], $config->getParameters('Bar')); + $this->assertEquals([], $config->getParameters('A')); + $this->assertEquals([], $config->getParameters('B')); } - public function testConfigCanConfigureCompiledDefinition() + public function testGetTypePreference() { - $config = include __DIR__ . '/_files/sample.php'; - $config = new Config($config['di']); - $di = new Di(); - $di->configure($config); - $definition = $di->definitions()->getDefinitionByType('Zend\Di\Definition\ArrayDefinition'); - $this->assertInstanceOf('Zend\Di\Definition\ArrayDefinition', $definition); - $this->assertTrue($di->definitions()->hasClass('My\DbAdapter')); - $this->assertTrue($di->definitions()->hasClass('My\EntityA')); - $this->assertTrue($di->definitions()->hasClass('My\Mapper')); - $this->assertTrue($di->definitions()->hasClass('My\RepositoryA')); - $this->assertTrue($di->definitions()->hasClass('My\RepositoryB')); - $this->assertFalse($di->definitions()->hasClass('My\Foo')); + $config = new Config($this->fixture); + $this->assertEquals('GlobalA', $config->getTypePreference('A')); + $this->assertEquals('GlobalB', $config->getTypePreference('B')); + $this->assertNull($config->getTypePreference('NotDefined')); + + $this->assertEquals('LocalA', $config->getTypePreference('A', 'Foo')); + $this->assertNull($config->getTypePreference('B', 'Foo')); + $this->assertNull($config->getTypePreference('NotDefined', 'Foo')); + + $this->assertEquals('LocalB', $config->getTypePreference('B', 'Bar')); + $this->assertNull($config->getTypePreference('A', 'Bar')); + $this->assertNull($config->getTypePreference('NotDefined', 'Bar')); + + $this->assertNull($config->getTypePreference('A', 'NotDefinedType')); + $this->assertNull($config->getTypePreference('B', 'NotDefinedType')); + $this->assertNull($config->getTypePreference('NotDefined', 'NotDefinedType')); } } diff --git a/test/DefaultContainerTest.php b/test/DefaultContainerTest.php new file mode 100644 index 00000000..1135b620 --- /dev/null +++ b/test/DefaultContainerTest.php @@ -0,0 +1,107 @@ +getMockForAbstractClass(InjectorInterface::class); + } + + /** + * Tests DefaultContainer->setInstance() + */ + public function testSetInstance() + { + $injector = $this->mockInjector(); + $injector->expects($this->never())->method($this->logicalNot($this->equalTo(''))); + $container = new DefaultContainer($injector); + $expected = new \stdClass(); + $key = uniqid('Test'); + + $container->setInstance($key, $expected); + $this->assertTrue($container->has($key)); + $this->assertSame($expected, $container->get($key)); + } + + /** + * Tests DefaultContainer->has() + */ + public function testHasConsultatesInjector() + { + $injector = $this->mockInjector(); + $key = uniqid('TestClass'); + + $injector->expects($this->atLeastOnce()) + ->method('canCreate') + ->with($key) + ->willReturn(true); + + $injector2 = $this->mockInjector(); + $injector2->expects($this->atLeastOnce()) + ->method('canCreate') + ->with($key) + ->willReturn(false); + + $container = new DefaultContainer($injector); + $container2 = new DefaultContainer($injector2) + ; + $this->assertTrue($container->has($key)); + $this->assertFalse($container2->has($key)); + } + + /** + * Tests DefaultContainer->get() + */ + public function testGetUsesInjector() + { + $injector = $this->mockInjector(); + $key = uniqid('TestClass'); + $expected = new \stdClass(); + + $injector->expects($this->atLeastOnce()) + ->method('create') + ->with($key) + ->willReturn($expected); + + $this->assertSame($expected, (new DefaultContainer($injector))->get($key)); + } + + /** + * Tests DefaultContainer->get() + */ + public function testGetInstanciatesOnlyOnce() + { + $injector = $this->mockInjector(); + $key = uniqid('TestClass'); + + $injector->expects($this->once()) + ->method('create') + ->with($key) + ->willReturnCallback(function () { + return new \stdClass(); + }); + + $container = new DefaultContainer($injector); + $expected = $container->get($key); + $this->assertSame($expected, $container->get($key)); + } +} diff --git a/test/Definition/ArrayDefinitionTest.php b/test/Definition/ArrayDefinitionTest.php deleted file mode 100644 index 44c0a2f8..00000000 --- a/test/Definition/ArrayDefinitionTest.php +++ /dev/null @@ -1,95 +0,0 @@ -definition = new ArrayDefinition(include __DIR__ . '/../_files/definition-array.php'); - } - - public function testArrayDefinitionHasClasses() - { - $this->assertTrue($this->definition->hasClass('My\DbAdapter')); - $this->assertTrue($this->definition->hasClass('My\EntityA')); - $this->assertTrue($this->definition->hasClass('My\Mapper')); - $this->assertTrue($this->definition->hasClass('My\RepositoryA')); - $this->assertTrue($this->definition->hasClass('My\RepositoryB')); - $this->assertFalse($this->definition->hasClass('My\Foo')); - } - - public function testArrayDefinitionHasMethods() - { - $this->assertTrue($this->definition->hasMethods('My\Mapper')); - $this->assertFalse($this->definition->hasMethods('My\EntityA')); - $this->assertTrue($this->definition->hasMethods('My\Mapper')); - $this->assertFalse($this->definition->hasMethods('My\RepositoryA')); - $this->assertFalse($this->definition->hasMethods('My\RepositoryB')); - $this->assertFalse($this->definition->hasMethods('My\Foo')); - } - - public function testArrayDefinitionCanGetClassses() - { - $list = [ - 'My\DbAdapter', - 'My\EntityA', - 'My\Mapper', - 'My\RepositoryA', - 'My\RepositoryB' - ]; - - $classes = $this->definition->getClasses(); - - foreach ($list as $class) { - $this->assertContains($class, $classes); - } - } - - public function testArrayDefinitionCanGetClassSupertypes() - { - $this->assertEquals([], $this->definition->getClassSupertypes('My\EntityA')); - $this->assertContains('My\RepositoryA', $this->definition->getClassSupertypes('My\RepositoryB')); - } - - public function testArrayDefinitionCanGetInstantiator() - { - $this->assertEquals('__construct', $this->definition->getInstantiator('My\RepositoryA')); - $this->assertNull($this->definition->getInstantiator('My\Foo')); - } - - public function testArrayDefinitionHasInjectionMethods() - { - $this->markTestIncomplete(); - } - - public function testArrayDefinitionHasInjectionMethod() - { - $this->markTestIncomplete(); - } - - public function testArrayDefinitionGetInjectionMethods() - { - $this->markTestIncomplete(); - } - - public function testArrayDefinitionGetInjectionMethodParameters() - { - $this->markTestIncomplete(); - } -} diff --git a/test/Definition/BuilderDefinitionTest.php b/test/Definition/BuilderDefinitionTest.php deleted file mode 100644 index dfaca0d2..00000000 --- a/test/Definition/BuilderDefinitionTest.php +++ /dev/null @@ -1,159 +0,0 @@ -assertInstanceOf('Zend\Di\Definition\DefinitionInterface', $builder); - } - - public function testBuilderCanBuildClassWithMethods() - { - $class = new Builder\PhpClass(); - $class->setName('Foo'); - $class->addSuperType('Parent'); - - $injectionMethod = new Builder\InjectionMethod(); - $injectionMethod->setName('injectBar'); - $injectionMethod->addParameter('bar', 'Bar'); - - $class->addInjectionMethod($injectionMethod); - - $definition = new BuilderDefinition(); - $definition->addClass($class); - - $this->assertTrue($definition->hasClass('Foo')); - $this->assertEquals('__construct', $definition->getInstantiator('Foo')); - $this->assertContains('Parent', $definition->getClassSupertypes('Foo')); - $this->assertTrue($definition->hasMethods('Foo')); - $this->assertTrue($definition->hasMethod('Foo', 'injectBar')); - $this->assertContains('injectBar', $definition->getMethods('Foo')); - $this->assertEquals( - ['Foo::injectBar:0' => ['bar', 'Bar', true, null]], - $definition->getMethodParameters('Foo', 'injectBar') - ); - } - - public function testBuilderDefinitionHasMethodsThrowsRuntimeException() - { - $definition = new BuilderDefinition(); - - $this->setExpectedException('Zend\Di\Exception\RuntimeException'); - $definition->hasMethods('Foo'); - } - - public function testBuilderDefinitionHasMethods() - { - $class = new Builder\PhpClass(); - $class->setName('Foo'); - - $definition = new BuilderDefinition(); - $definition->addClass($class); - - $this->assertFalse($definition->hasMethods('Foo')); - $class->createInjectionMethod('injectBar'); - - $this->assertTrue($definition->hasMethods('Foo')); - } - - public function testBuilderCanBuildFromArray() - { - $ini = include __DIR__ . '/../_files/sample-definitions.php'; - $iniAsArray = $ini['section-b']; - $definitionArray = $iniAsArray['di']['definitions'][1]; - unset($definitionArray['class']); - - $definition = new BuilderDefinition(); - $definition->createClassesFromArray($definitionArray); - - $this->assertTrue($definition->hasClass('My\DbAdapter')); - $this->assertEquals('__construct', $definition->getInstantiator('My\DbAdapter')); - $this->assertEquals( - [ - 'My\DbAdapter::__construct:0' => ['username', null, true, null], - 'My\DbAdapter::__construct:1' => ['password', null, true, null], - ], - $definition->getMethodParameters('My\DbAdapter', '__construct') - ); - - $this->assertTrue($definition->hasClass('My\Mapper')); - $this->assertEquals('__construct', $definition->getInstantiator('My\Mapper')); - $this->assertEquals( - ['My\Mapper::__construct:0' => ['dbAdapter', 'My\DbAdapter', true, null]], - $definition->getMethodParameters('My\Mapper', '__construct') - ); - - $this->assertTrue($definition->hasClass('My\Repository')); - $this->assertEquals('__construct', $definition->getInstantiator('My\Repository')); - $this->assertEquals( - ['My\Repository::__construct:0' => ['mapper', 'My\Mapper', true, null]], - $definition->getMethodParameters('My\Repository', '__construct') - ); - } - - public function testCanCreateClassFromFluentInterface() - { - $builder = new BuilderDefinition(); - $class = $builder->createClass('Foo'); - - $this->assertTrue($builder->hasClass('Foo')); - } - - public function testCanCreateInjectionMethodsAndPopulateFromFluentInterface() - { - $builder = new BuilderDefinition(); - $foo = $builder->createClass('Foo'); - $foo->setName('Foo'); - $foo->createInjectionMethod('setBar') - ->addParameter('bar', 'Bar'); - $foo->createInjectionMethod('setConfig') - ->addParameter('config', null); - - $this->assertTrue($builder->hasClass('Foo')); - $this->assertTrue($builder->hasMethod('Foo', 'setBar')); - $this->assertTrue($builder->hasMethod('Foo', 'setConfig')); - - $this->assertEquals( - ['Foo::setBar:0' => ['bar', 'Bar', true, null]], - $builder->getMethodParameters('Foo', 'setBar') - ); - $this->assertEquals( - ['Foo::setConfig:0' => ['config', null, true, null]], - $builder->getMethodParameters('Foo', 'setConfig') - ); - } - - public function testBuilderCanSpecifyClassToUseWithCreateClass() - { - $builder = new BuilderDefinition(); - $this->assertEquals('Zend\Di\Definition\Builder\PhpClass', $builder->getClassBuilder()); - - $builder->setClassBuilder('Foo'); - $this->assertEquals('Foo', $builder->getClassBuilder()); - } - - public function testClassBuilderCanSpecifyClassToUseWhenCreatingInjectionMethods() - { - $builder = new BuilderDefinition(); - $class = $builder->createClass('Foo'); - - $this->assertEquals('Zend\Di\Definition\Builder\InjectionMethod', $class->getMethodBuilder()); - - $class->setMethodBuilder('Foo'); - $this->assertEquals('Foo', $class->getMethodBuilder()); - } -} diff --git a/test/Definition/ClassDefinitionTest.php b/test/Definition/ClassDefinitionTest.php deleted file mode 100644 index e35e78af..00000000 --- a/test/Definition/ClassDefinitionTest.php +++ /dev/null @@ -1,88 +0,0 @@ -assertInstanceOf('Zend\Di\Definition\DefinitionInterface', $definition); - } - - public function testClassDefinitionHasMethods() - { - $definition = new ClassDefinition('Foo'); - $this->assertFalse($definition->hasMethods('Foo')); - $definition->addMethod('doBar'); - $this->assertTrue($definition->hasMethods('Foo')); - } - - public function testGetClassSupertypes() - { - $definition = new ClassDefinition('Foo'); - $definition->setSupertypes(['superFoo']); - $this->assertEquals([], $definition->getClassSupertypes('Bar')); - $this->assertEquals(['superFoo'], $definition->getClassSupertypes('Foo')); - } - - public function testGetInstantiator() - { - $definition = new ClassDefinition('Foo'); - $definition->setInstantiator('__construct'); - $this->assertNull($definition->getInstantiator('Bar')); - $this->assertEquals('__construct', $definition->getInstantiator('Foo')); - } - - public function testGetMethods() - { - $definition = new ClassDefinition('Foo'); - $definition->addMethod("setVar", true); - $this->assertEquals([], $definition->getMethods('Bar')); - $this->assertEquals(['setVar' => true], $definition->getMethods('Foo')); - } - - public function testHasMethod() - { - $definition = new ClassDefinition('Foo'); - $definition->addMethod("setVar", true); - $this->assertNull($definition->hasMethod('Bar', "setVar")); - $this->assertTrue($definition->hasMethod('Foo', "setVar")); - } - - public function testHasMethodParameters() - { - $definition = new ClassDefinition('Foo'); - $definition->addMethodParameter("setVar", "var", [null, true]); - $this->assertFalse($definition->hasMethodParameters("Bar", "setVar")); - $this->assertTrue($definition->hasMethodParameters("Foo", "setVar")); - } - - public function testGetMethodParameters() - { - $definition = new ClassDefinition('Foo'); - $definition->addMethodParameter("setVar", "var", ['type' => null, 'required' => true, 'default' => 'test']); - $this->assertNull($definition->getMethodParameters("Bar", "setVar")); - $this->assertEquals( - ['Foo::setVar:var' => ["var", null, true, 'test']], - $definition->getMethodParameters("Foo", "setVar") - ); - } - - public function testAddMethodSetsCorrectConstructorType() - { - $definition = new ClassDefinition('Foo'); - $definition->addMethod('__construct'); - $this->assertEquals(['__construct' => Di::METHOD_IS_CONSTRUCTOR], $definition->getMethods('Foo')); - } -} diff --git a/test/Definition/CompilerDefinitionTest.php b/test/Definition/CompilerDefinitionTest.php deleted file mode 100644 index 780e3377..00000000 --- a/test/Definition/CompilerDefinitionTest.php +++ /dev/null @@ -1,156 +0,0 @@ -addDirectory(__DIR__ . '/../TestAsset/CompilerClasses'); - $definition->compile(); - - $this->assertTrue($definition->hasClass('ZendTest\Di\TestAsset\CompilerClasses\A')); - - $assertClasses = [ - 'ZendTest\Di\TestAsset\CompilerClasses\A', - 'ZendTest\Di\TestAsset\CompilerClasses\B', - 'ZendTest\Di\TestAsset\CompilerClasses\C', - 'ZendTest\Di\TestAsset\CompilerClasses\D', - ]; - $classes = $definition->getClasses(); - foreach ($assertClasses as $assertClass) { - $this->assertContains($assertClass, $classes); - } - - // @todo this needs to be resolved, not the short name - // $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\D')); - - $this->assertEquals('__construct', $definition->getInstantiator('ZendTest\Di\TestAsset\CompilerClasses\A')); - $this->assertTrue($definition->hasMethods('ZendTest\Di\TestAsset\CompilerClasses\C')); - - - $this->assertArrayHasKey('setB', $definition->getMethods('ZendTest\Di\TestAsset\CompilerClasses\C')); - $this->assertTrue($definition->hasMethod('ZendTest\Di\TestAsset\CompilerClasses\C', 'setB')); - - $this->assertEquals( - ['ZendTest\Di\TestAsset\CompilerClasses\C::setB:0' => ['b', 'ZendTest\Di\TestAsset\CompilerClasses\B', true, null]], - $definition->getMethodParameters('ZendTest\Di\TestAsset\CompilerClasses\C', 'setB') - ); - } - - public function testCompilerSupertypes() - { - $definition = new CompilerDefinition; - $definition->addDirectory(__DIR__ . '/../TestAsset/CompilerClasses'); - $definition->compile(); - $this->assertEquals(0, count($definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\C'))); - $this->assertEquals(1, count($definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\D'))); - $this->assertEquals(2, count($definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E'))); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\D')); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E')); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\D', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E')); - } - - public function testCompilerDirectoryScannerAndFileScanner() - { - $definition = new CompilerDefinition; - $definition->addDirectoryScanner(new DirectoryScanner(__DIR__ . '/../TestAsset/CompilerClasses')); - $definition->addCodeScannerFile(new FileScanner(__DIR__ . '/../TestAsset/CompilerClasses/A.php')); - $definition->compile(); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\D')); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E')); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\D', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E')); - } - - public function testCompilerFileScanner() - { - $definition = new CompilerDefinition; - $definition->addCodeScannerFile(new FileScanner(__DIR__ . '/../TestAsset/CompilerClasses/C.php')); - $definition->addCodeScannerFile(new FileScanner(__DIR__ . '/../TestAsset/CompilerClasses/D.php')); - $definition->addCodeScannerFile(new FileScanner(__DIR__ . '/../TestAsset/CompilerClasses/E.php')); - $definition->compile(); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\D')); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\C', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E')); - $this->assertContains('ZendTest\Di\TestAsset\CompilerClasses\D', $definition->getClassSupertypes('ZendTest\Di\TestAsset\CompilerClasses\E')); - } - - public function testCompilerReflectionException() - { - $this->setExpectedException('ReflectionException', 'Class ZendTest\Di\TestAsset\InvalidCompilerClasses\Foo does not exist'); - $definition = new CompilerDefinition; - $definition->addDirectory(__DIR__ . '/../TestAsset/InvalidCompilerClasses'); - $definition->compile(); - } - - public function testCompilerAllowReflectionException() - { - $definition = new CompilerDefinition; - $definition->setAllowReflectionExceptions(); - $definition->addDirectory(__DIR__ . '/../TestAsset/InvalidCompilerClasses'); - $definition->compile(); - $parameters = $definition->getMethodParameters('ZendTest\Di\TestAsset\InvalidCompilerClasses\InvalidClass', '__construct'); - - // The exception gets caught before the parameter's class is set - $this->assertCount(1, current($parameters)); - } - - /** - * @group ZF2-308 - */ - public function testStaticMethodsNotIncludedInDefinitions() - { - $definition = new CompilerDefinition; - $definition->addDirectory(__DIR__ . '/../TestAsset/SetterInjection'); - $definition->compile(); - $this->assertTrue($definition->hasMethod('ZendTest\Di\TestAsset\SetterInjection\StaticSetter', 'setFoo')); - $this->assertFalse($definition->hasMethod('ZendTest\Di\TestAsset\SetterInjection\StaticSetter', 'setName')); - } - - /** - * Test if methods from aware interfaces without params are excluded - */ - public function testExcludeAwareMethodsWithoutParameters() - { - $definition = new CompilerDefinition(); - $definition->addDirectory(__DIR__ . '/../TestAsset/AwareClasses'); - $definition->compile(); - - $this->assertTrue($definition->hasMethod('ZendTest\Di\TestAsset\AwareClasses\B', 'setSomething')); - $this->assertFalse($definition->hasMethod('ZendTest\Di\TestAsset\AwareClasses\B', 'getSomething')); - } - - public function testHasMethodParameters() - { - $definition = new CompilerDefinition(); - $definition->addDirectory(__DIR__ . '/../TestAsset/ConstructorInjection'); - $definition->addDirectory(__DIR__ . '/../TestAsset/SetterInjection'); - $definition->addDirectory(__DIR__ . '/../TestAsset/CompilerClasses'); - $definition->compile(); - - // constructor injection - $this->assertTrue($definition->hasMethodParameters('ZendTest\Di\TestAsset\ConstructorInjection\B', '__construct')); - // setter injection - $this->assertTrue($definition->hasMethodParameters('ZendTest\Di\TestAsset\SetterInjection\B', 'setA')); - // setter injection with method from derived class - $this->assertTrue($definition->hasMethodParameters('ZendTest\Di\TestAsset\CompilerClasses\D', 'setB')); - // class does not exist - $this->assertFalse($definition->hasMethodParameters('ZendTest\Di\TestAsset\ConstructorInjection\BB', '__construct')); - // method not existing - $this->assertFalse($definition->hasMethodParameters('ZendTest\Di\TestAsset\SetterInjection\B', 'setB')); - // method exists but has no parameters - $this->assertFalse($definition->hasMethodParameters('ZendTest\Di\TestAsset\SetterInjection\StaticSetter', 'setFoo')); - } -} diff --git a/test/Definition/Reflection/ClassDefinitionTest.php b/test/Definition/Reflection/ClassDefinitionTest.php new file mode 100644 index 00000000..ab87fe88 --- /dev/null +++ b/test/Definition/Reflection/ClassDefinitionTest.php @@ -0,0 +1,132 @@ +getReflection(); + + $this->assertInstanceOf(\ReflectionClass::class, $result); + $this->assertEquals(HierarchyAsset\A::class, $result->getName()); + } + + public function testGetSupertypesReturnsAllClasses() + { + $supertypes = (new ClassDefinition(HierarchyAsset\C::class))->getSupertypes(); + $expected = [ + HierarchyAsset\A::class, + HierarchyAsset\B::class + ]; + + $this->assertInternalType('array', $supertypes); + + sort($expected); + sort($supertypes); + + $this->assertEquals($expected, $supertypes); + } + + public function testGetSupertypesReturnsEmptyArray() + { + $supertypes = (new ClassDefinition(HierarchyAsset\A::class))->getSupertypes(); + + $this->assertInternalType('array', $supertypes); + $this->assertEmpty($supertypes); + } + + /** + * Tests ClassDefinition->getInterfaces() + */ + public function testGetInterfacesReturnsAllInterfaces() + { + $result = (new ClassDefinition(HierarchyAsset\C::class))->getInterfaces(); + $expected = [ + HierarchyAsset\InterfaceA::class, + HierarchyAsset\InterfaceB::class, + HierarchyAsset\InterfaceC::class + ]; + + $this->assertInternalType('array', $result); + + sort($result); + sort($expected); + + $this->assertEquals($expected, $result); + } + + /** + * Tests ClassDefinition->getInterfaces() + */ + public function testGetInterfacesReturnsArray() + { + $result = (new ClassDefinition(HierarchyAsset\A::class))->getInterfaces(); + + $this->assertInternalType('array', $result); + $this->assertEmpty($result); + } + + public function provideClassesWithParameters() + { + return [ + [ConstructorAsset\OptionalArguments::class, 2], + [ConstructorAsset\RequiredArguments::class, 3] + ]; + } + + /** + * @dataProvider provideClassesWithParameters + */ + public function testGetParametersReturnsAllParameters($class, $expectedItemCount) + { + $result = (new ClassDefinition($class))->getParameters(); + + $this->assertInternalType('array', $result); + $this->assertCount($expectedItemCount, $result); + $this->assertContainsOnlyInstancesOf(ParameterInterface::class, $result); + } + + public function testGetParametersWithScalarTypehints() + { + $result = (new ClassDefinition(ConstructorAsset\Php7::class))->getParameters(); + + $this->assertInternalType('array', $result); + $this->assertCount(3, $result); + $this->assertContainsOnlyInstancesOf(ParameterInterface::class, $result); + } + + public function provideParameterlessClasses() + { + return [ + [ConstructorAsset\EmptyConstructor::class], + [ConstructorAsset\NoConstructor::class] + ]; + } + + /** + * @dataProvider provideParameterlessClasses + */ + public function testGetParametersReturnsAnArray($class) + { + $result = (new ClassDefinition($class))->getParameters(); + $this->assertInternalType('array', $result); + $this->assertEmpty($result); + } +} diff --git a/test/Definition/Reflection/ParameterTest.php b/test/Definition/Reflection/ParameterTest.php new file mode 100644 index 00000000..87a81f43 --- /dev/null +++ b/test/Definition/Reflection/ParameterTest.php @@ -0,0 +1,111 @@ +getMethod('general')->getParameters(); + + return [ + [ $params[0], 'a', 0, true, null ], + [ $params[1], 'b', 1, true, null ], + [ $params[2], 'c', 2, false, 'something' ] + ]; + } + + /** + * @dataProvider provideGeneralParameters + */ + public function testParamterReflectedCorrectly( + \ReflectionParameter $reflection, + $expectedName, + $expectedPosition, + $expectRequired, + $expectedDefault + ) { + $instance = new Parameter($reflection); + + $this->assertSame($expectedName, $instance->getName()); + $this->assertSame($expectedPosition, $instance->getPosition()); + + if ($expectRequired) { + $this->assertTrue($instance->isRequired(), 'Parameter is expected to be required'); + } else { + $this->assertFalse($instance->isRequired(), 'Param is not expected to be required'); + $this->assertSame($expectedDefault, $instance->getDefault()); + } + } + + /** + * @dataProvider provideTypehintedParameterReflections + */ + public function testTypehintedParameter(\ReflectionParameter $reflection, $expectedType) + { + $required = new Parameter($reflection); + $this->assertSame($expectedType, $required->getType()); + $this->assertFalse($required->isBuiltin()); + } + + /** + * @dataProvider provideTypelessParameterReflections + */ + public function testTypelessParamter(\ReflectionParameter $reflection) + { + $param = new Parameter($reflection); + $this->assertNull($param->getType(), 'Parameter type must be null'); + $this->assertFalse($param->isBuiltin(), 'Parameter must not be exposed builtin'); + } + + public function provideScalarTypehintedReflections() + { + return $this->buildReflectionArgsFromClass(TestAsset\ScalarTypehintParameters::class); + } + + /** + * @dataProvider provideBuiltinTypehintedReflections + */ + public function testBuiltinTypehintedParameters(\ReflectionParameter $reflection, $expectedType) + { + $param = new Parameter($reflection); + $this->assertTrue($param->isBuiltin()); + $this->assertSame($expectedType, $param->getType()); + } + + /** + * @dataProvider provideScalarTypehintedReflections + */ + public function testScalarTypehintedParameters(\ReflectionParameter $reflection, $expectedType) + { + $param = new Parameter($reflection); + $this->assertTrue($param->isBuiltin()); + $this->assertSame($expectedType, $param->getType()); + } + + public function testIterablePseudoType() + { + $reflections = (new ClassReflection(TestAsset\IterableDependency::class))->getConstructor()->getParameters(); + $param = new Parameter($reflections[0]); + + $this->assertTrue($param->isBuiltin()); + $this->assertSame('iterable', $param->getType()); + } +} diff --git a/test/Definition/Reflection/ParameterTestTrait.php b/test/Definition/Reflection/ParameterTestTrait.php new file mode 100644 index 00000000..b749170e --- /dev/null +++ b/test/Definition/Reflection/ParameterTestTrait.php @@ -0,0 +1,57 @@ +getMethod($methodName)->getParameters(); + return $all[$parameterIndex]; + } + + private function buildReflectionArgsFromClass($classname) + { + $class = new \ReflectionClass($classname); + $invocationArgs = []; + + /** @var \ReflectionMethod $method */ + foreach ($class->getMethods() as $method) { + $params = $method->getParameters(); + $typename = substr($method->name, 0, -4); + $invocationArgs[] = [ $params[0], $typename ]; + } + + return $invocationArgs; + } + + public function provideBuiltinTypehintedReflections() + { + return $this->buildReflectionArgsFromClass(TestAsset\BuiltinTypehintParameters::class); + } + + public function provideTypehintedParameterReflections() + { + return [ + [$this->reflectAsset('typehintRequired'), TestAsset\A::class], + [$this->reflectAsset('typehintOptional'), TestAsset\A::class] + ]; + } + + public function provideTypelessParameterReflections() + { + return [ + [$this->reflectAsset('typelessRequired')], + [$this->reflectAsset('typelessOptional')] + ]; + } +} diff --git a/test/Definition/RuntimeDefinitionTest.php b/test/Definition/RuntimeDefinitionTest.php index c54e3ef9..1e273fff 100644 --- a/test/Definition/RuntimeDefinitionTest.php +++ b/test/Definition/RuntimeDefinitionTest.php @@ -3,115 +3,159 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace ZendTest\Di\Definition; -use PHPUnit_Framework_TestCase as TestCase; use Zend\Di\Definition\RuntimeDefinition; +use Zend\Di\Exception; +use ZendTest\Di\TestAsset; +use Zend\Di\Definition\ClassDefinitionInterface; +use PHPUnit\Framework\TestCase; +/** + * @coversDefaultClass Zend\Di\Definition\RuntimeDefinition + */ class RuntimeDefinitionTest extends TestCase { + public function testSetExplicitClasses() + { + $expected = [ + TestAsset\A::class, + TestAsset\B::class + ]; + + $definition = new RuntimeDefinition(); + $definition->setExplicitClasses($expected); + + $this->assertEquals($expected, $definition->getClasses()); + } + + public function testSetExplicitClassesViaConstructor() + { + $expected = [ + TestAsset\A::class, + TestAsset\B::class + ]; + + $definition = new RuntimeDefinition($expected); + $this->assertEquals($expected, $definition->getClasses()); + } + + public function testSetExplicitClassesReplacesPrefiousValues() + { + $expected = [ + TestAsset\A::class, + TestAsset\B::class + ]; + + $definition = new RuntimeDefinition(); + $definition->setExplicitClasses([TestAsset\Parameters::class]); + $definition->setExplicitClasses($expected); + + $this->assertEquals($expected, $definition->getClasses()); + } + + public function provideExistingClasses() + { + return [ + [TestAsset\A::class], + [TestAsset\B::class], + [TestAsset\Constructor\NoConstructor::class] + ]; + } + + public function provideInvalidClasses() + { + return [ + [TestAsset\DummyInterface::class], + ['No\\Such\\Class.Because.Bad.Naming'] + ]; + } + /** - * @group ZF2-308 + * @dataProvider provideInvalidClasses */ - public function testStaticMethodsNotIncludedInDefinitions() + public function testSetInvalidExplicitClassThrowsException($class) { - $definition = new RuntimeDefinition; - $this->assertTrue($definition->hasMethod('ZendTest\Di\TestAsset\SetterInjection\StaticSetter', 'setFoo')); - $this->assertFalse($definition->hasMethod('ZendTest\Di\TestAsset\SetterInjection\StaticSetter', 'setName')); + $definition = new RuntimeDefinition(); + + $this->expectException(Exception\ClassNotFoundException::class); + $definition->setExplicitClasses([ $class ]); } - public function testIncludesDefaultMethodParameters() + /** + * Tests RuntimeDefinition->addExplicitClass() + */ + public function testAddExplicitClass() { + $expected = [ + TestAsset\A::class, + TestAsset\B::class + ]; + $definition = new RuntimeDefinition(); + $definition->setExplicitClasses([TestAsset\A::class]); + $definition->addExplicitClass(TestAsset\B::class); - $definition->forceLoadClass('ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters'); - - $this->assertSame( - [ - 'ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters::__construct:0' => [ - 'a', - null, - false, - null, - ], - 'ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters::__construct:1' => [ - 'b', - null, - false, - 'defaultConstruct', - ], - 'ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters::__construct:2' => [ - 'c', - null, - false, - [], - ], - ], - $definition->getMethodParameters( - 'ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters', - '__construct' - ) - ); + $this->assertEquals($expected, $definition->getClasses()); } - public function testExceptionDefaultValue() + /** + * @dataProvider provideInvalidClasses + */ + public function testAddInvalidExplicitClassThrowsException($class) { $definition = new RuntimeDefinition(); - $definition->forceLoadClass('RecursiveIteratorIterator'); - - $this->assertSame( - [ - 'RecursiveIteratorIterator::__construct:0' => [ - 'iterator', - 'Traversable', - true, - null, - ], - 'RecursiveIteratorIterator::__construct:1' => [ - 'mode', - null, - true, - null, - ], - 'RecursiveIteratorIterator::__construct:2' => [ - 'flags', - null, - true, - null, - ], - ], - $definition->getMethodParameters( - 'RecursiveIteratorIterator', - '__construct' - ) + $this->expectException(Exception\ClassNotFoundException::class); + $definition->addExplicitClass($class); + } + + /** + * @dataProvider provideExistingClasses + */ + public function testHasClassReturnsTrueDynamically($class) + { + $this->assertTrue( + (new RuntimeDefinition())->hasClass($class) ); } /** - * Test if methods from aware interfaces without params are excluded + * @dataProvider provideInvalidClasses */ - public function testExcludeAwareMethodsWithoutParameters() + public function testHasClassReturnsFalseForInvalidClasses($class) + { + $this->assertFalse( + (new RuntimeDefinition())->hasClass($class) + ); + } + + /** + * @dataProvider provideExistingClasses + */ + public function testGetClassDefinition($class) { $definition = new RuntimeDefinition(); - $this->assertTrue($definition->hasMethod('ZendTest\Di\TestAsset\AwareClasses\B', 'setSomething')); - $this->assertFalse($definition->hasMethod('ZendTest\Di\TestAsset\AwareClasses\B', 'getSomething')); + $result = $definition->getClassDefinition($class); + + $this->assertInstanceOf(ClassDefinitionInterface::class, $result); + $this->assertInstanceOf(\ReflectionClass::class, $result->getReflection()); + $this->assertSame($class, $result->getReflection()->name); } /** - * Test to see if we can introspect explicit classes + * @dataProvider provideExistingClasses */ - public function testExplicitClassesStillGetProccessedByIntrospectionStrategy() + public function testGetClassDefinitionAutoPopulatesClass($class) { - $className = 'ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters'; - $explicitClasses = [$className => true]; - $definition = new RuntimeDefinition(null, $explicitClasses); + $definition = new RuntimeDefinition(); - $this->assertTrue($definition->hasClass($className)); - $this->assertSame(["__construct"=> 3], $definition->getMethods($className)); + $this->assertSame([], $definition->getClasses()); + $definition->getClassDefinition($class); + $this->assertEquals([$class], $definition->getClasses()); } } diff --git a/test/DefinitionListTest.php b/test/DefinitionListTest.php deleted file mode 100644 index 5bddc38d..00000000 --- a/test/DefinitionListTest.php +++ /dev/null @@ -1,58 +0,0 @@ -setSupertypes($superTypesA); - - $definitionClassB = new ClassDefinition("B"); - $definitionClassB->setSupertypes(["superB"]); - - $definitionList = new DefinitionList([$definitionClassA, $definitionClassB]); - - $this->assertEquals($superTypesA, $definitionList->getClassSupertypes("A")); - } - - public function testHasMethod() - { - $definitionClass = new ClassDefinition('foo'); - $definitionClass->addMethod('doFoo'); - $definitionList = new DefinitionList([$definitionClass]); - - $this->assertTrue($definitionList->hasMethod('foo', 'doFoo')); - $this->assertFalse($definitionList->hasMethod('foo', 'doBar')); - - $definitionClass->addMethod('doBar'); - - $this->assertTrue($definitionList->hasMethod('foo', 'doBar')); - } - - public function testHasMethodAvoidAskingFromDefinitionsWhichDoNotIncludeClass() - { - $builderDefinition = new BuilderDefinition(); - - $definitionClass = new ClassDefinition('foo'); - $definitionClass->addMethod('doFoo'); - - $definitionList = new DefinitionList([$builderDefinition, $definitionClass]); - - $this->assertTrue($definitionList->hasMethod('foo', 'doFoo')); - } -} diff --git a/test/DiCompatibilityTest.php b/test/DiCompatibilityTest.php deleted file mode 100644 index 16e11095..00000000 --- a/test/DiCompatibilityTest.php +++ /dev/null @@ -1,128 +0,0 @@ -get($class); - - $this->assertInstanceOf($class, $bareObject, 'Test instantiate simple'); - $this->assertInstanceOf($class, $diObject, 'Test $di->get'); - } - - /** - * provides known classes invokable without parameters - * - * @return array - */ - public function providesSimpleClasses() - { - return [ - [Di::class], - [SplStack::class], - [TestAsset\BasicClass::class], - ]; - } - - /** - * - * error: Missing argument 1 for $class::__construct() - * @dataProvider providesClassWithConstructionParameters - * @param string $class - */ - public function testRaiseErrorMissingConstructorRequiredParameter($class) - { - if (version_compare(PHP_VERSION, '7', '>=')) { - $this->markTestSkipped('Errors have changed to E_FATAL, no longer allowing test to run'); - } - - $phpunit = $this; - $caught = false; - set_error_handler(function ($errno, $errstr) use ($phpunit, &$caught) { - if ($errno === E_WARNING && 0 !== strpos($errstr, 'Missing argument')) { - $phpunit->fail('Unexpected error caught during instantiation'); - return false; - } - - throw new BadMethodCallException('TRAPPED'); - }, E_WARNING|E_RECOVERABLE_ERROR); - try { - $bareObject = new $class; - } catch (Exception $e) { - if ($e instanceof PHPUnit_Framework_Error - || ($e instanceof BadMethodCallException && $e->getMessage() === 'TRAPPED') - ) { - $caught = true; - } - } - $this->assertTrue($caught); - } - - /** - * - * @dataProvider providesClassWithConstructionParameters - * @expectedException \Zend\Di\Exception\MissingPropertyException - * @param string $class - */ - public function testWillThrowExceptionMissingConstructorRequiredParameterWithDi($class) - { - $di = new Di(); - $diObject = $di->get($class); - $this->assertInstanceOf($class, $diObject, 'Test $di->get'); - } - - /** - * - * @dataProvider providesClassWithConstructionParameters - * @param string $class - */ - public function testCanCreateInstanceWithConstructorRequiredParameter($class, $args) - { - $reflection = new \ReflectionClass($class); - $bareObject = $reflection->newInstanceArgs($args); - $this->assertInstanceOf($class, $bareObject, 'Test instantiate with constructor required parameters'); - } - - /** - * @dataProvider providesClassWithConstructionParameters - * @param string $class - */ - public function testCanCreateInstanceWithConstructorRequiredParameterWithDi($class, $args) - { - $di = new Di(); - $diObject = $di->get($class, $args); - $this->assertInstanceOf($class, $diObject, 'Test $di->get with constructor required paramters'); - } - - public function providesClassWithConstructionParameters() - { - return [ - [TestAsset\BasicClassWithParam::class, ['foo' => 'bar']], - [TestAsset\ConstructorInjection\X::class, ['one' => 1, 'two' => 2]], - ]; - } -} diff --git a/test/DiTest.php b/test/DiTest.php deleted file mode 100644 index d11367bc..00000000 --- a/test/DiTest.php +++ /dev/null @@ -1,1193 +0,0 @@ -assertInstanceOf('Zend\Di\InstanceManager', $di->instanceManager()); - - $definitions = $di->definitions(); - - $this->assertInstanceOf('Zend\Di\DefinitionList', $definitions); - $this->assertInstanceOf('Zend\Di\Definition\RuntimeDefinition', $definitions->top()); - } - - public function testDiConstructorCanTakeDependencies() - { - $dl = new DefinitionList([]); - $im = new InstanceManager(); - $cg = new Config([]); - $di = new Di($dl, $im, $cg); - - $this->assertSame($dl, $di->definitions()); - $this->assertSame($im, $di->instanceManager()); - - $di->setDefinitionList($dl); - $di->setInstanceManager($im); - - $this->assertSame($dl, $di->definitions()); - $this->assertSame($im, $di->instanceManager()); - } - - public function testGetRetrievesObjectWithMatchingClassDefinition() - { - $di = new Di(); - $obj = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj); - } - - public function testGetRetrievesSameInstanceOnSubsequentCalls() - { - $config = new Config([ - 'instance' => [ - 'ZendTest\Di\TestAsset\BasicClass' => [ - 'shared' => true, - ], - ], - ]); - $di = new Di(null, null, $config); - $obj1 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); - $this->assertSame($obj1, $obj2); - } - - public function testGetRetrievesDifferentInstanceOnSubsequentCallsIfSharingDisabled() - { - $config = new Config([ - 'instance' => [ - 'ZendTest\Di\TestAsset\BasicClass' => [ - 'shared' => false, - ], - ], - ]); - $di = new Di(null, null, $config); - $obj1 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); - $this->assertNotSame($obj1, $obj2); - } - - public function testGetRetrievesSameSharedInstanceOnUsingInConstructor() - { - $config = new Config([ - 'instance' => [ - 'ZendTest\Di\TestAsset\BasicClass' => [ - 'shared' => true, - ], - ], - ]); - $di = new Di(null, null, $config); - $obj1 = $di->get('ZendTest\Di\TestAsset\BasicClassWithParent', ['foo' => 0]); - $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClassWithParent', ['foo' => 1]); - $obj3 = $di->get('ZendTest\Di\TestAsset\BasicClassWithParent', ['foo' => 2, 'non_exists' => 1]); - $objParent1 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $objParent2 = $di->get('ZendTest\Di\TestAsset\BasicClass', ['foo' => 1]); - - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClassWithParent', $obj1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClassWithParent', $obj2); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClassWithParent', $obj3); - $this->assertSame($obj1->parent, $obj2->parent); - $this->assertSame($obj2->parent, $obj3->parent); - $this->assertSame($obj3->parent, $objParent1); - $this->assertSame($obj3->parent, $objParent2); - } - - public function testGetThrowsExceptionWhenUnknownClassIsUsed() - { - $di = new Di(); - - $this->setExpectedException('Zend\Di\Exception\ClassNotFoundException', 'could not be located in'); - $obj1 = $di->get('ZendTest\Di\TestAsset\NonExistentClass'); - } - - public function testGetThrowsExceptionWhenMissingParametersAreEncountered() - { - $di = new Di(); - - $this->setExpectedException('Zend\Di\Exception\MissingPropertyException', 'Missing instance/object for '); - $obj1 = $di->get('ZendTest\Di\TestAsset\BasicClassWithParam'); - } - - public function testNewInstanceReturnsDifferentInstances() - { - $di = new Di(); - $obj1 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass'); - $obj2 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); - $this->assertNotSame($obj1, $obj2); - } - - public function testNewInstanceReturnsInstanceThatIsSharedWithGet() - { - $di = new Di(); - $obj1 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass'); - $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); - $this->assertSame($obj1, $obj2); - } - - public function testNewInstanceReturnsInstanceThatIsNotSharedWithGet() - { - $di = new Di(); - $obj1 = $di->newInstance('ZendTest\Di\TestAsset\BasicClass', [], false); - $obj2 = $di->get('ZendTest\Di\TestAsset\BasicClass'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj1); - $this->assertInstanceOf('ZendTest\Di\TestAsset\BasicClass', $obj2); - $this->assertNotSame($obj1, $obj2); - } - - public function testNewInstanceCanHandleClassesCreatedByCallback() - { - $definitionList = new DefinitionList([ - $classdef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\CallbackClasses\A'), - new Definition\RuntimeDefinition() - ]); - $classdef->setInstantiator('ZendTest\Di\TestAsset\CallbackClasses\A::factory'); - - $di = new Di($definitionList); - $a = $di->get('ZendTest\Di\TestAsset\CallbackClasses\A'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\CallbackClasses\A', $a); - } - - public function testNewInstanceCanHandleComplexCallback() - { - $definitionList = new DefinitionList([ - $classdefB = new Definition\ClassDefinition('ZendTest\Di\TestAsset\CallbackClasses\B'), - $classdefC = new Definition\ClassDefinition('ZendTest\Di\TestAsset\CallbackClasses\C'), - new Definition\RuntimeDefinition() - ]); - - $classdefB->setInstantiator('ZendTest\Di\TestAsset\CallbackClasses\B::factory'); - $classdefB->addMethod('factory', true); - $classdefB->addMethodParameter('factory', 'c', ['type' => 'ZendTest\Di\TestAsset\CallbackClasses\C', 'required' => true]); - $classdefB->addMethodParameter('factory', 'params', ['type' => 'Array', 'required'=>false]); - - $di = new Di($definitionList); - $b = $di->get('ZendTest\Di\TestAsset\CallbackClasses\B', ['params'=>['foo' => 'bar']]); - $this->assertInstanceOf('ZendTest\Di\TestAsset\CallbackClasses\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\CallbackClasses\C', $b->c); - $this->assertEquals(['foo' => 'bar'], $b->params); - } - - -// public function testCanSetInstantiatorToStaticFactory() -// { -// $config = new Config(array( -// 'definition' => array( -// 'class' => array( -// 'ZendTest\Di\TestAsset\DummyParams' => array( -// 'instantiator' => array('ZendTest\Di\TestAsset\StaticFactory', 'factory'), -// ), -// 'ZendTest\Di\TestAsset\StaticFactory' => array( -// 'methods' => array( -// 'factory' => array( -// 'struct' => array( -// 'type' => 'ZendTest\Di\TestAsset\Struct', -// 'required' => true, -// ), -// 'params' => array( -// 'required' => true, -// ), -// ), -// ), -// ), -// ), -// ), -// 'instance' => array( -// 'ZendTest\Di\TestAsset\DummyParams' => array( -// 'parameters' => array( -// 'struct' => 'ZendTest\Di\TestAsset\Struct', -// 'params' => array( -// 'foo' => 'bar', -// ), -// ), -// ), -// 'ZendTest\Di\TestAsset\Struct' => array( -// 'parameters' => array( -// 'param1' => 'hello', -// 'param2' => 'world', -// ), -// ), -// ), -// )); -// $di = new Di(); -// $di->configure($config); -// $dummyParams = $di->get('ZendTest\Di\TestAsset\DummyParams'); -// $this->assertEquals($dummyParams->params['param1'], 'hello'); -// $this->assertEquals($dummyParams->params['param2'], 'world'); -// $this->assertEquals($dummyParams->params['foo'], 'bar'); -// $this->assertArrayNotHasKey('methods', $di->definitions()->hasMethods('ZendTest\Di\TestAsset\StaticFactory')); -// } - - /** - * @group ConstructorInjection - */ - public function testGetWillResolveConstructorInjectionDependencies() - { - $di = new Di(); - $b = $di->get('ZendTest\Di\TestAsset\ConstructorInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b->a); - } - - /** - * @group ConstructorInjection - */ - public function testGetWillResolveConstructorInjectionDependenciesAndInstanceAreTheSame() - { - $di = new Di(); - $b = $di->get('ZendTest\Di\TestAsset\ConstructorInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b->a); - - $b2 = $di->get('ZendTest\Di\TestAsset\ConstructorInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b2); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b2->a); - - $this->assertSame($b, $b2); - $this->assertSame($b->a, $b2->a); - } - - /** - * @group ConstructorInjection - */ - public function testNewInstanceWillResolveConstructorInjectionDependencies() - { - $di = new Di(); - $b = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\A', $b->a); - } - - /** - * @group ConstructorInjection - */ - public function testNewInstanceWillResolveConstructorInjectionDependenciesWithProperties() - { - $di = new Di(); - - $im = $di->instanceManager(); - $im->setParameters('ZendTest\Di\TestAsset\ConstructorInjection\X', ['one' => 1, 'two' => 2]); - - $y = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\Y'); - $this->assertEquals(1, $y->x->one); - $this->assertEquals(2, $y->x->two); - } - - /** - * @group ConstructorInjection - */ - public function testNewInstanceWillThrowExceptionOnConstructorInjectionDependencyWithMissingParameter() - { - $di = new Di(); - - $this->setExpectedException('Zend\Di\Exception\MissingPropertyException', 'Missing instance/object for parameter'); - $b = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\X'); - } - - /** - * @group ConstructorInjection - */ - public function testNewInstanceWillResolveConstructorInjectionDependenciesWithMoreThan2Dependencies() - { - $di = new Di(); - - $im = $di->instanceManager(); - $im->setParameters('ZendTest\Di\TestAsset\ConstructorInjection\X', ['one' => 1, 'two' => 2]); - - $z = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\Z'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\Y', $z->y); - $this->assertInstanceOf('ZendTest\Di\TestAsset\ConstructorInjection\X', $z->y->x); - } - - /** - * @group SetterInjection - */ - public function testGetWillResolveSetterInjectionDependencies() - { - $di = new Di(); - // for setter injection, the dependency is not required, thus it must be forced - $di->instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\SetterInjection\B', - ['a' => new TestAsset\SetterInjection\A] - ); - $b = $di->get('ZendTest\Di\TestAsset\SetterInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); - } - - /** - * @group SetterInjection - */ - public function testGetWillResolveSetterInjectionDependenciesAndInstanceAreTheSame() - { - $di = new Di(); - // for setter injection, the dependency is not required, thus it must be forced - $di->instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\SetterInjection\B', - ['a' => $a = new TestAsset\SetterInjection\A] - ); - - $b = $di->get('ZendTest\Di\TestAsset\SetterInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); - - $b2 = $di->get('ZendTest\Di\TestAsset\SetterInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b2); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b2->a); - - $this->assertSame($b, $b2); - $this->assertSame($b->a, $a); - $this->assertSame($b2->a, $a); - } - - /** - * @group SetterInjection - */ - public function testNewInstanceWillResolveSetterInjectionDependencies() - { - $di = new Di(); - // for setter injection, the dependency is not required, thus it must be forced - $di->instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\SetterInjection\B', - ['a' => new TestAsset\SetterInjection\A] - ); - - $b = $di->newInstance('ZendTest\Di\TestAsset\SetterInjection\B'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\B', $b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); - } - - /** - * @todo Setter Injections is not automatic, find a way to test this logically - * - * @group SetterInjection - */ - public function testNewInstanceWillResolveSetterInjectionDependenciesWithProperties() - { - $di = new Di(); - - $im = $di->instanceManager(); - $im->setParameters('ZendTest\Di\TestAsset\SetterInjection\X', ['one' => 1, 'two' => 2]); - - $x = $di->get('ZendTest\Di\TestAsset\SetterInjection\X'); - $y = $di->newInstance('ZendTest\Di\TestAsset\SetterInjection\Y', ['x' => $x]); - - $this->assertEquals(1, $y->x->one); - $this->assertEquals(2, $y->x->two); - } - - /** - * Test for Circular Dependencies (case 1) - * - * A->B, B->A - * @group CircularDependencyCheck - */ - public function testNewInstanceThrowsExceptionOnBasicCircularDependency() - { - $di = new Di(); - - $this->setExpectedException('Zend\Di\Exception\CircularDependencyException'); - $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\A'); - } - - /** - * Test for Circular Dependencies (case 2) - * - * C->D, D->E, E->C - * @group CircularDependencyCheck - */ - public function testNewInstanceThrowsExceptionOnThreeLevelCircularDependency() - { - $di = new Di(); - - $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\E depends on ZendTest\Di\TestAsset\CircularClasses\C and viceversa' - ); - $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\C'); - } - - /** - * Test for Circular Dependencies (case 2) - * - * C->D, D->E, E->C - * @group CircularDependencyCheck - */ - public function testNewInstanceThrowsExceptionWhenEnteringInMiddleOfCircularDependency() - { - $di = new Di(); - - $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\C depends on ZendTest\Di\TestAsset\CircularClasses\D and viceversa' - ); - $di->newInstance('ZendTest\Di\TestAsset\CircularClasses\D'); - } - - protected function configureNoneCircularDependencyTests() - { - $di = new Di(); - - $di->instanceManager()->addAlias('YA', 'ZendTest\Di\TestAsset\CircularClasses\Y'); - $di->instanceManager()->addAlias('YB', 'ZendTest\Di\TestAsset\CircularClasses\Y', ['y' => 'YA']); - $di->instanceManager()->addAlias('YC', 'ZendTest\Di\TestAsset\CircularClasses\Y', ['y' => 'YB']); - - return $di; - } - - /** - * Test for correctly identifying no Circular Dependencies (case 1) - * - * YC -> YB, YB -> YA - * @group CircularDependencyCheck - */ - public function testNoCircularDependencyDetectedIfWeGetIntermediaryClass() - { - $di = $this->configureNoneCircularDependencyTests(); - - $yb = $di->get('YB'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\CircularClasses\Y', $yb); - $yc = $di->get('YC'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\CircularClasses\Y', $yc); - } - - /** - * Test for correctly identifying no Circular Dependencies (case 2) - * - * YC -> YB, YB -> YA - * @group CircularDependencyCheck - */ - public function testNoCircularDependencyDetectedIfWeDontGetIntermediaryClass() - { - $di = $this->configureNoneCircularDependencyTests(); - - $yc = $di->get('YC'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\CircularClasses\Y', $yc); - } - - /** - * Test for correctly identifying a Circular Dependency in aliases (case 3) - * - * YA -> YB, YB -> YA - * @group CircularDependencyCheck - */ - public function testCircularDependencyDetectedInAliases() - { - $di = new Di(); - - $di->instanceManager()->addAlias('YA', 'ZendTest\Di\TestAsset\CircularClasses\Y', ['y' => 'YC']); - $di->instanceManager()->addAlias('YB', 'ZendTest\Di\TestAsset\CircularClasses\Y', ['y' => 'YA']); - $di->instanceManager()->addAlias('YC', 'ZendTest\Di\TestAsset\CircularClasses\Y', ['y' => 'YB']); - - $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\Y depends on ZendTest\Di\TestAsset\CircularClasses\Y and viceversa (Aliased as YA)' - ); - - $yc = $di->get('YC'); - } - - /** - * Test for correctly identifying a Circular Dependency with a self referencing alias - * - * YA -> YA - * @group CircularDependencyCheck - */ - public function testCircularDependencyDetectedInSelfReferencingAlias() - { - $di = new Di(); - - $di->instanceManager()->addAlias( - 'YA', - 'ZendTest\Di\TestAsset\CircularClasses\Y', - ['y' => 'YA'] - ); - - $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\Y depends on ZendTest\Di\TestAsset\CircularClasses\Y and viceversa (Aliased as YA)' - ); - - $y = $di->get('YA'); - } - - /** - * Test for correctly identifying a Circular Dependency with mixture of classes and aliases - * - * Y -> YA, YA -> Y - * @group CircularDependencyCheck - */ - public function testCircularDependencyDetectedInMixtureOfAliasesAndClasses() - { - $di = new Di(); - - $di->instanceManager()->addAlias( - 'YA', - 'ZendTest\Di\TestAsset\CircularClasses\Y', - ['y' => 'ZendTest\Di\TestAsset\CircularClasses\Y'] - ); - - $this->setExpectedException( - 'Zend\Di\Exception\CircularDependencyException', - 'Circular dependency detected: ZendTest\Di\TestAsset\CircularClasses\Y depends on ZendTest\Di\TestAsset\CircularClasses\Y and viceversa (Aliased as YA)' - ); - - $y = $di->get('ZendTest\Di\TestAsset\CircularClasses\Y', ['y' => 'YA']); - } - - /** - * Fix for PHP bug in is_subclass_of - * - * @see https://bugs.php.net/bug.php?id=53727 - */ - public function testNewInstanceWillUsePreferredClassForInterfaceHints() - { - $definitionList = new DefinitionList([ - $classdef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\PreferredImplClasses\C'), - new Definition\RuntimeDefinition() - ]); - $classdef->addMethod('setA', Di::METHOD_IS_EAGER); - $di = new Di($definitionList); - - $di->instanceManager()->addTypePreference( - 'ZendTest\Di\TestAsset\PreferredImplClasses\A', - 'ZendTest\Di\TestAsset\PreferredImplClasses\BofA' - ); - - $c = $di->get('ZendTest\Di\TestAsset\PreferredImplClasses\C'); - $a = $c->a; - $this->assertInstanceOf('ZendTest\Di\TestAsset\PreferredImplClasses\BofA', $a); - $d = $di->get('ZendTest\Di\TestAsset\PreferredImplClasses\D'); - $this->assertSame($a, $d->a); - } - - public function testNewInstanceWillThrowAnClassNotFoundExceptionWhenClassIsAnInterface() - { - $definitionArray = [ - 'ZendTest\Di\TestAsset\ConstructorInjection\D' => [ - 'supertypes' => [], - 'instantiator' => '__construct', - 'methods' => ['__construct' => 3], - 'parameters' => [ - '__construct' => - [ - 'ZendTest\Di\TestAsset\ConstructorInjection\D::__construct:0' => [ - 0 => 'd', - 1 => 'ZendTest\Di\TestAsset\DummyInterface', - 2 => true, - 3 => null, - ], - ], - ], - ], - 'ZendTest\Di\TestAsset\DummyInterface' => [ - 'supertypes' => [], - 'instantiator' => null, - 'methods' => [], - 'parameters' => [], - ], - ]; - $definitionList = new DefinitionList([ - new Definition\ArrayDefinition($definitionArray) - ]); - $di = new Di($definitionList); - - $this->setExpectedException('Zend\Di\Exception\ClassNotFoundException', 'Cannot instantiate interface'); - $di->get('ZendTest\Di\TestAsset\ConstructorInjection\D'); - } - - public function testMatchPreferredClassWithAwareInterface() - { - $di = new Di(); - - $di->instanceManager()->addTypePreference( - 'ZendTest\Di\TestAsset\PreferredImplClasses\A', - 'ZendTest\Di\TestAsset\PreferredImplClasses\BofA' - ); - - $e = $di->get('ZendTest\Di\TestAsset\PreferredImplClasses\E'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\PreferredImplClasses\BofA', $e->a); - } - - public function testWillNotUsePreferredClassForInterfaceHints() - { - $di = new Di(); - - $di->instanceManager()->addTypePreference( - 'ZendTest\Di\TestAsset\PreferredImplClasses\A', - 'ZendTest\Di\TestAsset\PreferredImplClasses\BofA' - ); - - $c = $di->get('ZendTest\Di\TestAsset\PreferredImplClasses\C'); - $a = $c->a; - $this->assertNull($a); - $d = $di->get('ZendTest\Di\TestAsset\PreferredImplClasses\D'); - $this->assertNull($d->a); - } - - public function testInjectionInstancesCanBeInjectedMultipleTimes() - { - $definitionList = new DefinitionList([ - $classdef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\InjectionClasses\A'), - new Definition\RuntimeDefinition() - ]); - $classdef->addMethod('addB'); - $classdef->addMethodParameter('addB', 'b', ['required' => true, 'type' => 'ZendTest\Di\TestAsset\InjectionClasses\B']); - - $di = new Di($definitionList); - $di->instanceManager()->setInjections( - 'ZendTest\Di\TestAsset\InjectionClasses\A', - [ - 'ZendTest\Di\TestAsset\InjectionClasses\B' - ] - ); - $a = $di->newInstance('ZendTest\Di\TestAsset\InjectionClasses\A'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[0]); - - $di = new Di($definitionList); - $di->instanceManager()->addAlias('my-b1', 'ZendTest\Di\TestAsset\InjectionClasses\B'); - $di->instanceManager()->addAlias('my-b2', 'ZendTest\Di\TestAsset\InjectionClasses\B'); - - $di->instanceManager()->setInjections( - 'ZendTest\Di\TestAsset\InjectionClasses\A', - [ - 'my-b1', - 'my-b2' - ] - ); - $a = $di->newInstance('ZendTest\Di\TestAsset\InjectionClasses\A'); - - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[0]); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[1]); - $this->assertNotSame( - $a->bs[0], - $a->bs[1] - ); - } - - public function testInjectionCanHandleDisambiguationViaPositions() - { - $definitionList = new DefinitionList([ - $classdef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\InjectionClasses\A'), - new Definition\RuntimeDefinition() - ]); - $classdef->addMethod('injectBOnce'); - $classdef->addMethod('injectBTwice'); - $classdef->addMethodParameter('injectBOnce', 'b', ['required' => true, 'type' => 'ZendTest\Di\TestAsset\InjectionClasses\B']); - $classdef->addMethodParameter('injectBTwice', 'b', ['required' => true, 'type' => 'ZendTest\Di\TestAsset\InjectionClasses\B']); - - $di = new Di($definitionList); - $di->instanceManager()->setInjections( - 'ZendTest\Di\TestAsset\InjectionClasses\A', - [ - 'ZendTest\Di\TestAsset\InjectionClasses\A::injectBOnce:0' => new \ZendTest\Di\TestAsset\InjectionClasses\B('once'), - 'ZendTest\Di\TestAsset\InjectionClasses\A::injectBTwice:0' => new \ZendTest\Di\TestAsset\InjectionClasses\B('twice') - ] - ); - $a = $di->newInstance('ZendTest\Di\TestAsset\InjectionClasses\A'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[0]); - $this->assertEquals('once', $a->bs[0]->id); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[1]); - $this->assertEquals('twice', $a->bs[1]->id); - } - - public function testInjectionCanHandleDisambiguationViaNames() - { - $definitionList = new DefinitionList([ - $classdef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\InjectionClasses\A'), - new Definition\RuntimeDefinition() - ]); - $classdef->addMethod('injectBOnce'); - $classdef->addMethod('injectBTwice'); - $classdef->addMethodParameter('injectBOnce', 'b', ['required' => true, 'type' => 'ZendTest\Di\TestAsset\InjectionClasses\B']); - $classdef->addMethodParameter('injectBTwice', 'b', ['required' => true, 'type' => 'ZendTest\Di\TestAsset\InjectionClasses\B']); - - $di = new Di($definitionList); - $di->instanceManager()->setInjections( - 'ZendTest\Di\TestAsset\InjectionClasses\A', - [ - 'ZendTest\Di\TestAsset\InjectionClasses\A::injectBOnce:b' => new \ZendTest\Di\TestAsset\InjectionClasses\B('once'), - 'ZendTest\Di\TestAsset\InjectionClasses\A::injectBTwice:b' => new \ZendTest\Di\TestAsset\InjectionClasses\B('twice') - ] - ); - $a = $di->newInstance('ZendTest\Di\TestAsset\InjectionClasses\A'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[0]); - $this->assertEquals('once', $a->bs[0]->id); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[1]); - $this->assertEquals('twice', $a->bs[1]->id); - } - - public function testInjectionCanHandleMultipleInjectionsWithMultipleArguments() - { - $definitionList = new DefinitionList([ - $classdef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\InjectionClasses\A'), - new Definition\RuntimeDefinition() - ]); - $classdef->addMethod('injectSplitDependency'); - $classdef->addMethodParameter('injectSplitDependency', 'b', ['required' => true, 'type' => 'ZendTest\Di\TestAsset\InjectionClasses\B']); - $classdef->addMethodParameter('injectSplitDependency', 'somestring', ['required' => true, 'type' => null]); - - /** - * First test that this works with a single call - */ - $di = new Di($definitionList); - $di->instanceManager()->setInjections( - 'ZendTest\Di\TestAsset\InjectionClasses\A', - [ - 'injectSplitDependency' => ['b' => 'ZendTest\Di\TestAsset\InjectionClasses\B', 'somestring' => 'bs-id'] - ] - ); - $a = $di->newInstance('ZendTest\Di\TestAsset\InjectionClasses\A'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[0]); - $this->assertEquals('bs-id', $a->bs[0]->id); - - /** - * Next test that this works with multiple calls - */ - $di = new Di($definitionList); - $di->instanceManager()->setInjections( - 'ZendTest\Di\TestAsset\InjectionClasses\A', - [ - 'injectSplitDependency' => [ - ['b' => 'ZendTest\Di\TestAsset\InjectionClasses\B', 'somestring' => 'bs-id'], - ['b' => 'ZendTest\Di\TestAsset\InjectionClasses\C', 'somestring' => 'bs-id-for-c'] - ] - ] - ); - $a = $di->newInstance('ZendTest\Di\TestAsset\InjectionClasses\A'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\B', $a->bs[0]); - $this->assertEquals('bs-id', $a->bs[0]->id); - $this->assertInstanceOf('ZendTest\Di\TestAsset\InjectionClasses\C', $a->bs[1]); - $this->assertEquals('bs-id-for-c', $a->bs[1]->id); - } - - /** - * @group SetterInjection - * @group SupertypeResolution - */ - public function testInjectionForSetterInjectionWillConsultSupertypeDefinitions() - { - $di = new Di(); - // for setter injection, the dependency is not required, thus it must be forced - $di->instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\SetterInjection\C', - ['a' => new TestAsset\SetterInjection\A] - ); - $c = $di->get('ZendTest\Di\TestAsset\SetterInjection\C'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\C', $c); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $c->a); - } - - /** - * @group SetterInjection - * @group SupertypeResolution - */ - public function testInjectionForSetterInjectionWillConsultSupertypeDefinitionInClassDefinition() - { - $di = new Di(); - - // for setter injection, the dependency is not required, thus it must be forced - $classDef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\SetterInjection\B'); - $classDef->addMethod('setA', true); - $di->definitions()->addDefinition($classDef, false); // top of stack b/c Runtime is already there - - $c = $di->get('ZendTest\Di\TestAsset\SetterInjection\C'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\C', $c); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $c->a); - } - - /** - * @group SharedInstance - */ - public function testMarkingClassAsNotSharedInjectsNewInstanceIntoAllRequestersButDependentsAreShared() - { - $di = new Di(); - $di->configure(new Config([ - 'instance' => [ - 'ZendTest\Di\TestAsset\SharedInstance\Lister' => [ - 'shared' => false - ] - ] - ])); - $movie = $di->get('ZendTest\Di\TestAsset\SharedInstance\Movie'); - $venue = $di->get('ZendTest\Di\TestAsset\SharedInstance\Venue'); - - $this->assertNotSame($movie->lister, $venue->lister); - $this->assertSame($movie->lister->sharedLister, $venue->lister->sharedLister); - } - - public function testDiWillInjectDependenciesForInstance() - { - $di = new Di; - - // for setter injection, the dependency is not required, thus it must be forced - $classDef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\SetterInjection\B'); - $classDef->addMethod('setA', true); - $di->definitions()->addDefinition($classDef, false); // top of stack b/c Runtime is already there - - $b = new TestAsset\SetterInjection\B; - $di->injectDependencies($b); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); - } - - public function testDiWillInjectDependenciesForAlias() - { - $di = new Di; - - // for setter injection, the dependency is not required, thus it must be forced - $classDef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\SetterInjection\B'); - $classDef->addMethod('setA', false); - $classDef->addMethodParameter('setA', 'a', ['type' => 'ZendTest\Di\TestAsset\SetterInjection\A', 'required' => false]); - $di->definitions()->addDefinition($classDef, false); - $di->instanceManager()->addAlias('b_alias', 'ZendTest\Di\TestAsset\SetterInjection\B'); - $di->instanceManager()->setInjections('b_alias', ['ZendTest\Di\TestAsset\SetterInjection\A']); - - $b = $di->get('b_alias'); - $this->assertInstanceOf('ZendTest\Di\TestAsset\SetterInjection\A', $b->a); - } - - /* - * @group SetterInjection - * @group SupertypeResolution - */ - public function testInjectionForSetterInjectionWillNotUseSupertypeWhenChildParamIsExplicitlyDefined() - { - $di = new Di(); - // for setter injection, the dependency is not required, thus it must be forced - $di->instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\InheritanceClasses\B', - ['test' => 'b'] - ); - $di->instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\InheritanceClasses\A', - ['test' => 'a'] - ); - - $b = $di->get('ZendTest\Di\TestAsset\InheritanceClasses\B'); - $this->assertEquals('b', $b->test); - - $c = $di->get('ZendTest\Di\TestAsset\InheritanceClasses\C'); - $this->assertEquals('b', $c->test); - } - - /** - * @group ZF2-260 - */ - public function testDiWillInjectClassNameAsStringAtCallTime() - { - $di = new Di; - - $classDef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\SetterInjection\D'); - $classDef->addMethod('setA', true); - $classDef->addMethodParameter('setA', 'a', ['type' => false, 'required' => true]); - $di->definitions()->addDefinition($classDef, false); - - $d = $di->get( - 'ZendTest\Di\TestAsset\SetterInjection\D', - ['a' => 'ZendTest\Di\TestAsset\SetterInjection\A'] - ); - - $this->assertSame($d->a, 'ZendTest\Di\TestAsset\SetterInjection\A'); - } - - /** - * @group ZF2-308 - */ - public function testWillNotCallStaticInjectionMethods() - { - $di = new Di; - $di->definitions()->addDefinition(new Definition\RuntimeDefinition(), false); - $di->newInstance('ZendTest\Di\TestAsset\SetterInjection\StaticSetter', ['name' => 'testName']); - - $this->assertSame(\ZendTest\Di\TestAsset\SetterInjection\StaticSetter::$name, 'originalName'); - } - - /** - * @group ZF2-142 - */ - public function testDiWillInjectDefaultParameters() - { - $di = new Di; - - $classDef = new Definition\ClassDefinition('ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters'); - $classDef->addMethod('__construct', true); - $classDef->addMethodParameter( - '__construct', - 'a', - ['type' => false, 'required' => false, 'default' => null] - ); - $classDef->addMethodParameter( - '__construct', - 'b', - ['type' => false, 'required' => false, 'default' => 'defaultConstruct'] - ); - $classDef->addMethodParameter( - '__construct', - 'c', - ['type' => false, 'required' => false, 'default' => []] - ); - - $di->definitions()->addDefinition($classDef, false); - - $optionalParams = $di->newInstance('ZendTest\Di\TestAsset\ConstructorInjection\OptionalParameters'); - - $this->assertSame(null, $optionalParams->a); - $this->assertSame('defaultConstruct', $optionalParams->b); - $this->assertSame([], $optionalParams->c); - } - - /** - * @group SharedInstance - */ - public function testGetWithParamsWillUseSharedInstance() - { - $di = new Di; - - $sharedInstanceClass = 'ZendTest\Di\TestAsset\ConstructorInjection\A'; - $retrievedInstanceClass = 'ZendTest\Di\TestAsset\ConstructorInjection\C'; - - // Provide definitions for $retrievedInstanceClass, but not for $sharedInstanceClass. - $arrayDefinition = [$retrievedInstanceClass => [ - 'supertypes' => [ ], - 'instantiator' => '__construct', - 'methods' => ['__construct' => true], - 'parameters' => [ '__construct' => [ - "$retrievedInstanceClass::__construct:0" => ['a', $sharedInstanceClass, true, null], - "$retrievedInstanceClass::__construct:1" => ['params', null, false, []], - ]], - ]]; - - // This also disables scanning of class A. - $di->setDefinitionList(new DefinitionList(new Definition\ArrayDefinition($arrayDefinition))); - - $di->instanceManager()->addSharedInstance(new $sharedInstanceClass, $sharedInstanceClass); - $returnedC = $di->get($retrievedInstanceClass, ['params' => ['test']]); - $this->assertInstanceOf($retrievedInstanceClass, $returnedC); - } - - /** - * - * @group 4714 - * @group SharedInstanceWithParameters - */ - public function testGetWithParamsWillUseSharedInstanceWithParameters() - { - $di = new Di; - - $sharedInstanceClass = TestAsset\ConstructorInjection\A::class; - $retrievedInstanceClass = TestAsset\ConstructorInjection\C::class; - - // Provide definitions for $retrievedInstanceClass, but not for $sharedInstanceClass. - $arrayDefinition = [$retrievedInstanceClass => [ - 'supertypes' => [], - 'instantiator' => '__construct', - 'methods' => ['__construct' => true], - 'parameters' => [ '__construct' => [ - "$retrievedInstanceClass::__construct:0" => ['a', $sharedInstanceClass, true, null], - "$retrievedInstanceClass::__construct:1" => ['params', null, false, []], - ]], - ]]; - - // This also disables scanning of class A. - $di->setDefinitionList(new DefinitionList(new Definition\ArrayDefinition($arrayDefinition))); - - // and we can get a new instance with parameters - $di->instanceManager()->addSharedInstanceWithParameters(new $sharedInstanceClass, $sharedInstanceClass, ['params' => ['test']]); - $di->instanceManager()->addSharedInstanceWithParameters(new $sharedInstanceClass, $sharedInstanceClass, ['params' => ['alter']]); - $returnedC = $di->get($retrievedInstanceClass, ['params' => ['test']]); - $returnedAltC = $di->get($retrievedInstanceClass, ['params' => ['alter']]); - $this->assertNotSame($returnedC, $returnedAltC); - $this->assertEquals(['alter'], $returnedAltC->params); - } - - public function testGetInstanceWithParamsHasSameNameAsDependencyParam() - { - $config = new Config([ - 'definition' => [ - 'class' => [ - TestAsset\AggregateClasses\AggregateItems::class => [ - 'addItem' => [ - 'item' => [ - 'type' => TestAsset\AggregateClasses\ItemInterface::class, - 'required' => true, - ], - ], - ], - ], - ], - 'instance' => [ - TestAsset\AggregateClasses\AggregateItems::class => [ - 'injections' => [ - TestAsset\AggregateClasses\Item::class, - ], - ], - TestAsset\AggregatedParamClass::class => [ - 'parameters' => [ - 'item' => TestAsset\AggregateClasses\AggregateItems::class, - ], - ], - ], - ]); - - $di = new Di(null, null, $config); - $this->assertCount(1, $di->get(TestAsset\AggregatedParamClass::class)->aggregator->items); - } - - public function hasInstanceProvider() - { - $config = new Config(['instance' => [ - TestAsset\BasicClassWithParam::class => [ - 'params' => ['foo' => 'bar'], - ], - ]]); - - $classDefB = new Definition\ClassDefinition(TestAsset\CallbackClasses\B::class); - $classDefC = new Definition\ClassDefinition(TestAsset\CallbackClasses\C::class); - $classDefB->setInstantiator(TestAsset\CallbackClasses\B::class . '::factory'); - $classDefB->addMethod('factory', true); - $classDefB->addMethodParameter('factory', 'c', [ - 'type' => TestAsset\CallbackClasses\C::class, - 'required' => true, - ]); - $classDefB->addMethodParameter('factory', 'params', ['type' => 'Array', 'required' => false]); - $definitionList = new DefinitionList([ - $classDefB, - $classDefC, - new Definition\RuntimeDefinition(), - ]); - - $instanceManager = new InstanceManager(); - $instanceManager->setParameters(TestAsset\ConstructorInjection\X::class, ['one' => 1, 'two' => 2]); - - // @codingStandardsIgnoreStart - return [ - 'no-config' => [null, null, null, TestAsset\BasicClass::class], - 'config-instance' => [null, null, $config, TestAsset\BasicClassWithParam::class], - 'definition-list' => [$definitionList, null, null, TestAsset\CallbackClasses\B::class], - 'instance-manager' => [null, $instanceManager, null, TestAsset\ConstructorInjection\X::class], - ]; - // @codingStandardsIgnoreEnd - } - - /** - * @dataProvider hasInstanceProvider - */ - public function testCanQueryToSeeIfContainerHasOrCanCreateAnInstance( - $definitionList, - $instanceManager, - $config, - $testFor - ) { - $di = new Di($definitionList, $instanceManager, $config); - $this->assertTrue($di->has($testFor), sprintf('Failed to find instance for %s', $testFor)); - } - - /** - * test protected method Di::resolveMethodParameters for constructor injection - * - * @dataProvider providesResolveMethodParameters - * @group 4714 - * @group 6388 - */ - public function testResolveMethodParameters($param, $expected) - { - $config = [ - 'instance' => [ - 'preference' => [ - TestAsset\ConstructorInjection\A::class => TestAsset\ConstructorInjection\E::class, - ], - ], - ]; - $di = new Di(null, null, new Config($config)); - $ref = new ReflectionObject($di); - $method = $ref->getMethod('resolveMethodParameters'); - $method->setAccessible(true); - - //user provides alias name - $im = $di->instanceManager(); - $im->addAlias('foo', TestAsset\ConstructorInjection\F::class, ['params' => ['p' => 'vFoo']]); - $im->addAlias('bar', TestAsset\ConstructorInjection\F::class, ['params' => ['p' => 'vBar']]); - - $args = [ - TestAsset\ConstructorInjection\B::class, - '__construct', - $param, //without parameters - null, - Di::METHOD_IS_CONSTRUCTOR, - true - ]; - $res = $method->invokeArgs($di, $args); - $this->assertInstanceOf($expected, $res[0]); - } - - /** - * - * Provides parameters for protected method Di::resolveMethodParameters - * - * @group 4714 - * @group 6388 - */ - public function providesResolveMethodParameters() - { - // @codingStandardsIgnoreStart - return [ - 'resolve as type preferenced class @group 6388' => [[], TestAsset\ConstructorInjection\E::class], - 'resolve class user provided (not E)' => [['a' => TestAsset\ConstructorInjection\F::class], TestAsset\ConstructorInjection\F::class], - 'resolve alias class @group 4714' => [['a' => 'foo'], TestAsset\ConstructorInjection\F::class], - ]; - // @codingStandardsIgnoreEnd - } - - /** - * constructor injection consultation - * - * @group 4714 - */ - public function testAliasesWithDiffrentParams() - { - $config = [ - 'instance' => [ - 'preference' => [ - TestAsset\ConstructorInjection\A::class => TestAsset\ConstructorInjection\E::class, - ], - ], - ]; - $di = new Di(null, null, new Config($config)); - $im = $di->instanceManager(); - $im->addAlias('foo', TestAsset\ConstructorInjection\F::class, ['params' => ['p' => 'vfoo']]); - $im->addAlias('bar', TestAsset\ConstructorInjection\F::class, ['params' => ['p' => 'vbar']]); - - $pref = $di->get(TestAsset\ConstructorInjection\B::class); - $bFoo = $di->get(TestAsset\ConstructorInjection\B::class, ['a' => 'foo']); - $bBar = $di->get(TestAsset\ConstructorInjection\B::class, ['a' => 'bar']); - $this->assertInstanceOf(TestAsset\ConstructorInjection\E::class, $pref->a); - $this->assertNotSame($pref->a, $bFoo->a); - $this->assertInstanceOf(TestAsset\ConstructorInjection\F::class, $bFoo->a); - $this->assertInstanceOf(TestAsset\ConstructorInjection\F::class, $bBar->a); - $this->assertEquals('vfoo', $bFoo->a->params['p']); - $this->assertEquals('vbar', $bBar->a->params['p']); - } -} diff --git a/test/InjectorTest.php b/test/InjectorTest.php new file mode 100644 index 00000000..95be708f --- /dev/null +++ b/test/InjectorTest.php @@ -0,0 +1,346 @@ +getMockForAbstractClass(ContainerInterface::class); + $mock2 = $this->getMockForAbstractClass(ContainerInterface::class); + + $injector = new Injector(null, $mock1); + $injector->setContainer($mock2); + + $this->assertSame($mock2, $injector->getContainer()); + $this->assertNotSame($mock1, $injector->getContainer()); + } + + public function testConstructWithContainerPassesItToResolver() + { + $container = $this->getMockForAbstractClass(ContainerInterface::class); + $resolver = $this->getMockForAbstractClass(DependencyResolverInterface::class); + $resolver->expects($this->once()) + ->method('setContainer') + ->with($this->isIdentical($container)) + ->willReturnSelf(); + + $injector = new Injector(null, $container, null, $resolver); + $this->assertSame($container, $injector->getContainer()); + } + + public function testSetContainerPassesItToResolver() + { + $container = $this->getMockForAbstractClass(ContainerInterface::class); + $resolver = $this->getMockForAbstractClass(DependencyResolverInterface::class); + $injector = new Injector(null, null, null, $resolver); + + $resolver->expects($this->once()) + ->method('setContainer') + ->with($this->isIdentical($container)) + ->willReturnSelf(); + + $injector->setContainer($container); + $this->assertSame($container, $injector->getContainer()); + } + + /** + * @return string[][] + */ + public function provideClassNames() + { + return [ + [TestAsset\A::class], + [TestAsset\B::class], + [TestAsset\Option1ForA::class] + ]; + } + + /** + * @dataProvider provideClassNames + */ + public function testCanCreateReturnsTrueForClasses($className) + { + $this->assertTrue((new Injector())->canCreate($className)); + } + + public function testCanCreateReturnsFalseForInterfaces() + { + $this->assertFalse((new Injector())->canCreate(TestAsset\DummyInterface::class)); + } + + public function testCanCreateReturnsFalseForNonExistingClassOrAlias() + { + $injector = new Injector(); + $this->assertFalse($injector->canCreate('Zend\Di\TestAsset\NoSuchClass')); + $this->assertFalse($injector->canCreate('Some.Alias.Name')); + } + + public function provideValidAliases() + { + return [ + [ 'Foo.Alias', TestAsset\A::class ], + [ 'Bar.alias', TestAsset\B::class ], + [ 'Some.Custom.Name', TestAsset\Constructor\EmptyConstructor::class ] + ]; + } + + /** + * @dataProvider provideValidAliases + */ + public function testCanCreateReturnsTrueWithDefinedAndValidAliases($aliasName, $className) + { + $config = new Config([ + 'types' => [ + $aliasName => [ + 'typeOf' => $className + ] + ] + ]); + + $this->assertTrue((new Injector($config))->canCreate($aliasName)); + } + + public function testCanCreateReturnsFalseWithDefinedInvalidAliases() + { + $config = new Config([ + 'types' => [ + 'Some.Custom.Name' => [ + 'typeOf' => 'ZendTest\Di\TestAsset\NoSuchClassName' + ] + ] + ]); + + $this->assertFalse((new Injector($config))->canCreate('Some.Custom.Name')); + } + + public function testCreateWithoutDependencies() + { + $result = (new Injector())->create(TestAsset\Constructor\EmptyConstructor::class); + $this->assertInstanceOf(TestAsset\Constructor\EmptyConstructor::class, $result); + } + + public function testCreateUsesContainerDependency() + { + $injector = new Injector(); + $expectedA = new TestAsset\A(); + $container = new DefaultContainer($injector); + + $container->setInstance(TestAsset\A::class, $expectedA); + $injector->setContainer($container); + + /** @var \ZendTest\Di\TestAsset\B $result */ + $result = $injector->create(TestAsset\B::class); + + $this->assertInstanceOf(TestAsset\B::class, $result); + $this->assertSame($expectedA, $result->injectedA); + } + + public function testCreateSimpleDependency() + { + /** @var \ZendTest\Di\TestAsset\B $result */ + $result = (new Injector())->create(TestAsset\B::class); + + $this->assertInstanceOf(TestAsset\B::class, $result); + $this->assertInstanceOf(TestAsset\A::class, $result->injectedA); + } + + public function provideCircularClasses() + { + $classes = [ + TestAsset\CircularClasses\A::class, + TestAsset\CircularClasses\B::class, + TestAsset\CircularClasses\C::class, + TestAsset\CircularClasses\D::class, + TestAsset\CircularClasses\E::class, + TestAsset\CircularClasses\X::class, + TestAsset\CircularClasses\Y::class, + ]; + + return array_map(function ($class) { + return [$class]; + }, $classes); + } + + /** + * @dataProvider provideCircularClasses + */ + public function testCircularDependencyThrowsException($class) + { + $this->expectException(Exception\CircularDependencyException::class); + (new Injector())->create($class); + } + + public function testSimpleTreeResolving() + { + /** @var TreeTestAsset\Simple $result */ + $result = (new Injector())->create(TreeTestAsset\Simple::class); + $this->assertInstanceOf(TreeTestAsset\Simple::class, $result); + $this->assertInstanceOf(TreeTestAsset\Level1::class, $result->result); + $this->assertInstanceOf(TreeTestAsset\Level2::class, $result->result->result); + } + + public function testComplexTreeResolving() + { + /** @var TreeTestAsset\Complex $result */ + $result = (new Injector())->create(TreeTestAsset\Complex::class); + $this->assertInstanceOf(TreeTestAsset\Complex::class, $result); + $this->assertInstanceOf(TreeTestAsset\Level1::class, $result->result); + $this->assertInstanceOf(TreeTestAsset\Level2::class, $result->result->result); + $this->assertInstanceOf(TreeTestAsset\AdditionalLevel1::class, $result->result2); + $this->assertInstanceOf(TreeTestAsset\Level2::class, $result->result2->result); + $this->assertSame($result->result->result, $result->result2->result); + } + + public function testDeepDependencyUsesContainer() + { + $injector = new Injector(); + $container = $this->getMockForAbstractClass(ContainerInterface::class); + + // Mocks a container that always creates new instances + $container->method('has')->willReturnCallback(function ($class) use ($injector) { + return $injector->canCreate($class); + }); + $container->method('get')->willReturnCallback(function ($class) use ($injector) { + return $injector->create($class); + }); + + $injector->setContainer($container); + + $result1 = $injector->create(TreeTestAsset\Complex::class); + $result2 = $injector->create(TreeTestAsset\Complex::class); + + /** @var TreeTestAsset\Complex $result */ + /** @var TreeTestAsset\Complex $result1 */ + /** @var TreeTestAsset\Complex $result2 */ + + foreach ([$result1, $result2] as $result) { + $this->assertInstanceOf(TreeTestAsset\Complex::class, $result); + $this->assertInstanceOf(TreeTestAsset\Level1::class, $result->result); + $this->assertInstanceOf(TreeTestAsset\Level2::class, $result->result->result); + $this->assertInstanceOf(TreeTestAsset\AdditionalLevel1::class, $result->result2); + $this->assertInstanceOf(TreeTestAsset\Level2::class, $result->result2->result); + } + + $this->assertNotSame($result1, $result2); + $this->assertNotSame($result1->result, $result2->result); + $this->assertNotSame($result1->result2, $result2->result2); + $this->assertNotSame($result1->result->result, $result2->result->result); + $this->assertNotSame($result1->result2->result, $result2->result2->result); + + $this->assertNotSame($result1->result->result, $result1->result2->result); + $this->assertNotSame($result2->result->result, $result2->result2->result); + } + + public function testDeepDependencyRespectsGlobalTypePreference() + { + $config = new Config([ + 'preferences' => [ + TreeTestAsset\Level2::class => TreeTestAsset\Level2Preference::class + ] + ]); + + /** @var TreeTestAsset\Complex $result */ + $result = (new Injector($config))->create(TreeTestAsset\Complex::class); + $this->assertInstanceOf(TreeTestAsset\Level2Preference::class, $result->result2->result); + $this->assertInstanceOf(TreeTestAsset\Level2Preference::class, $result->result->result); + } + + public function testDeepDependencyRespectsSpecificTypePreference() + { + $config = new Config([ + 'types' => [ + TreeTestAsset\AdditionalLevel1::class => [ + 'preferences' => [ + TreeTestAsset\Level2::class => TreeTestAsset\Level2Preference::class + ] + ] + ] + ]); + + /** @var TreeTestAsset\Complex $result */ + $result = (new Injector($config))->create(TreeTestAsset\Complex::class); + $this->assertInstanceOf(TreeTestAsset\Level2Preference::class, $result->result2->result); + $this->assertNotInstanceOf(TreeTestAsset\Level2Preference::class, $result->result->result); + } + + public function testDeepDependencyUsesConfiguredParameters() + { + $expected = uniqid('InjectValue'); + $config = new Config([ + 'types' => [ + TreeTestAsset\Level2::class => [ + 'parameters' => [ + 'opt' => $expected + ] + ] + ] + ]); + + /** @var TreeTestAsset\Complex $result */ + $result = (new Injector($config))->create(TreeTestAsset\Complex::class); + $this->assertSame($expected, $result->result2->result->optionalResult); + $this->assertSame($expected, $result->result->result->optionalResult); + } + + public function testComplexDeepDependencyConfiguration() + { + $expected1 = uniqid('InjectValueA'); + $expected2 = uniqid('InjectValueB'); + + $config = new Config([ + 'types' => [ + TreeTestAsset\Level2::class => [ + 'parameters' => [ + 'opt' => $expected1 + ] + ], + 'Level2.Alias' => [ + 'typeOf' => TreeTestAsset\Level2::class, + 'parameters' => [ + 'opt' => $expected2 + ] + ], + TreeTestAsset\AdditionalLevel1::class => [ + 'preferences' => [ + TreeTestAsset\Level2::class => 'Level2.Alias' + ] + ] + ] + ]); + + /** @var TreeTestAsset\Complex $result */ + $result = (new Injector($config))->create(TreeTestAsset\Complex::class); + $this->assertSame($expected1, $result->result->result->optionalResult); + $this->assertSame($expected2, $result->result2->result->optionalResult); + } +} diff --git a/test/InstanceManagerTest.php b/test/InstanceManagerTest.php deleted file mode 100644 index a79db5e9..00000000 --- a/test/InstanceManagerTest.php +++ /dev/null @@ -1,112 +0,0 @@ -addSharedInstance($obj, 'ZendTest\Di\TestAsset\BasicClass'); - $this->assertTrue($im->hasSharedInstance('ZendTest\Di\TestAsset\BasicClass')); - $this->assertSame($obj, $im->getSharedInstance('ZendTest\Di\TestAsset\BasicClass')); - } - - public function testInstanceManagerCanPersistInstancesWithParameters() - { - $im = new InstanceManager(); - $obj1 = new TestAsset\BasicClass(); - $obj2 = new TestAsset\BasicClass(); - $obj3 = new TestAsset\BasicClass(); - - $im->addSharedInstance($obj1, 'foo'); - $im->addSharedInstanceWithParameters($obj2, 'foo', ['foo' => 'bar']); - $im->addSharedInstanceWithParameters($obj3, 'foo', ['foo' => 'baz']); - - $this->assertSame($obj1, $im->getSharedInstance('foo')); - $this->assertSame($obj2, $im->getSharedInstanceWithParameters('foo', ['foo' => 'bar'])); - $this->assertSame($obj3, $im->getSharedInstanceWithParameters('foo', ['foo' => 'baz'])); - } - - /** - * @group AliasAlias - */ - public function testInstanceManagerCanResolveRecursiveAliases() - { - $im = new InstanceManager; - $im->addAlias('bar-alias', 'Some\Class'); - $im->addAlias('foo-alias', 'bar-alias'); - $class = $im->getClassFromAlias('foo-alias'); - $this->assertEquals('Some\Class', $class); - } - - /** - * @group AliasAlias - */ - public function testInstanceManagerThrowsExceptionForRecursiveAliases() - { - $im = new InstanceManager; - $im->addAlias('bar-alias', 'foo-alias'); - $im->addAlias('foo-alias', 'bar-alias'); - - $this->setExpectedException('Zend\Di\Exception\RuntimeException', 'recursion'); - $im->getClassFromAlias('foo-alias'); - } - - /** - * @group AliasAlias - */ - public function testInstanceManagerResolvesRecursiveAliasesForConfig() - { - $config = ['parameters' => ['username' => 'my-username']]; - - $im = new InstanceManager; - $im->addAlias('bar-alias', 'Some\Class'); - $im->addAlias('foo-alias', 'bar-alias'); - $im->setConfig('bar-alias', $config); - - $config['injections'] = []; - $config['shared'] = true; - - $this->assertEquals($config, $im->getConfig('foo-alias')); - } - - public function testInstanceManagerCanPersistInstancesWithArrayParameters() - { - $im = new InstanceManager(); - $obj1 = new TestAsset\BasicClass(); - $obj2 = new TestAsset\BasicClass(); - $obj3 = new TestAsset\BasicClass(); - - $im->addSharedInstance($obj1, 'foo'); - $im->addSharedInstanceWithParameters($obj2, 'foo', ['foo' => ['bar']]); - - $this->assertSame($obj1, $im->getSharedInstance('foo')); - $this->assertSame($obj2, $im->getSharedInstanceWithParameters('foo', ['foo' => ['bar']])); - $this->assertFalse($im->hasSharedInstanceWithParameters('foo', ['foo' => []])); - - $im->addSharedInstanceWithParameters($obj3, 'foo', ['foo' => ['baz']]); - - $this->assertSame($obj2, $im->getSharedInstanceWithParameters('foo', ['foo' => ['bar']])); - $this->assertSame($obj3, $im->getSharedInstanceWithParameters('foo', ['foo' => ['baz']])); - } - - public function testInstanceManagerCanPersistInstanceWithArrayWithClosure() - { - $im = new InstanceManager(); - $obj1 = new TestAsset\BasicClass(); - $im->addSharedInstanceWithParameters($obj1, 'foo', ['foo' => [function () {}]]); - $this->assertSame($obj1, $im->getSharedInstanceWithParameters('foo', ['foo' => [function () {}]])); - } -} diff --git a/test/Resolver/DependencyResolverTest.php b/test/Resolver/DependencyResolverTest.php new file mode 100644 index 00000000..ca9fd9a3 --- /dev/null +++ b/test/Resolver/DependencyResolverTest.php @@ -0,0 +1,266 @@ +getMockForAbstractClass(ContainerInterface::class); + $container->expects($this->any())->method('has')->withAnyParameters()->willReturn(false); + $container->expects($this->never())->method('get')->withAnyParameters(); + + return $container; + } + + /** + * @param array $definition + * @return ParameterInterface + */ + private function mockParameter($name, $position, array $options) + { + $definition = array_merge([ + 'default' => null, + 'type' => null, + 'builtin' => false, + 'required' => true + ], $options); + + $mock = $this->getMockForAbstractClass(ParameterInterface::class); + $mock->method('getName')->willReturn($name); + $mock->method('getPosition')->willReturn($position); + $mock->method('getDefault')->willReturn($definition['default']); + $mock->method('getType')->willReturn($definition['type']); + $mock->method('isBuiltin')->willReturn((bool)$definition['builtin']); + $mock->method('isRequired')->willReturn((bool)$definition['required']); + } + + /** + * @return ClassDefinitionInterface + */ + private function mockClassDefinition($name, array $parameters = [], array $interfaces = [], array $supertypes = []) + { + $mock = $this->getMockForAbstractClass(ClassDefinitionInterface::class); + + $mock->method('getInterfaces')->willReturn($interfaces); + $mock->method('getSupertypes')->willReturn($supertypes); + $mock->expects($this->never())->method('getReflection'); + + $position = 0; + $paramMocks = []; + + foreach ($parameters as $name => $options) { + $paramMocks[] = $this->mockParameter($name, $position++, $options); + } + + $mock->method('getParameters')->willReturn($paramMocks); + + return $mock; + } + + /** + * input: + * + * [ + * 'Classname' => [ + * 'interfaces' => [ 'Interface', 'interface2', ... ], + * 'supertypes' => [ 'Supertype1', 'Supertype2', ... ], + * 'parameters' => [ + * 'paramName' => [ + * 'required' => true, + * 'builtin' => true, + * 'type' => 'string', + * 'default' => null + * ] + * ... + * ] + * ], + * ... + * ] + * + * @return DefinitionInterface + */ + private function mockDefintition(array $definition) + { + $mock = $this->getMockForAbstractClass(DefinitionInterface::class); + + $mock->method('getClasses')->willReturn(array_keys($definition)); + foreach ($definition as $class => $options) { + $options = array_merge([ + 'parameters' => [], + 'interfaces' => [], + 'supertypes' => [], + ], $options); + + $mock->method('getClassDefinition') + ->with($class) + ->willReturn($this->mockClassDefinition( + $class, + $options['parameters'], + $options['interfaces'], + $options['supertypes'] + )); + } + + $mock->method('hasClass')->willReturnCallback(function ($class) use ($definition) { + return isset($definition[$class]); + }); + + return $mock; + } + + public function testResolveWithoutConfig() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + + $params = $resolver->resolveParameters(TestAsset\B::class); + $this->assertCount(1, $params); + + $injection = array_shift($params); + $this->assertInstanceOf(TypeInjection::class, $injection); + $this->assertEquals(TestAsset\A::class, $injection->getType()); + + $params = $resolver->resolveParameters(TestAsset\A::class); + $this->assertInternalType('array', $params); + $this->assertCount(0, $params); + } + + public function testResolveWithContainerFailsWhenMissing() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + + $this->expectException(Exception\MissingPropertyException::class); + $resolver->setContainer($this->getEmptyContainerMock()); + $resolver->resolveParameters(TestAsset\RequiresA::class); + } + + public function testResolveSucceedsWithoutContainer() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + $result = $resolver->resolveParameters(TestAsset\RequiresA::class); + + $this->assertCount(1, $result); + $this->assertInternalType('array', $result); + $this->assertSame(TestAsset\A::class, $result['p']->getType()); + } + + public function testResolveFailsForDependenciesWithoutType() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + + $this->expectException(Exception\MissingPropertyException::class); + $resolver->resolveParameters(TestAsset\Constructor\RequiredArguments::class); + } + + public function testResolveFailsForInterfaces() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + + $this->expectException(Exception\ClassNotFoundException::class); + $resolver->resolveParameters(TestAsset\DummyInterface::class); + } + + public function provideClassesWithoutConstructionParams() + { + return [ + [TestAsset\Constructor\EmptyConstructor::class], + [TestAsset\Constructor\NoConstructor::class] + ]; + } + + /** + * @dataProvider provideClassesWithoutConstructionParams + */ + public function testResolveClassWithoutParameters($class) + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + $result = $resolver->resolveParameters($class); + + $this->assertInternalType('array', $result); + $this->assertCount(0, $result); + } + + public function testResolveWithOptionalArgs() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + $result = $resolver->resolveParameters(TestAsset\Constructor\OptionalArguments::class); + + $this->assertInternalType('array', $result); + $this->assertCount(2, $result); + $this->assertContainsOnlyInstancesOf(ValueInjection::class, $result); + $this->assertSame(null, $result['foo']->getValue()); + $this->assertSame('something', $result['bar']->getValue()); + } + + public function testResolvePassedDependenciesWithoutType() + { + $resolver = new DependencyResolver(new RuntimeDefinition(), new Config()); + + $expected = 'Some Value'; + $result = $resolver->resolveParameters(TestAsset\Constructor\RequiredArguments::class, [ + 'anyDep' => $expected + ]); + + $this->assertCount(3, $result); + $this->assertInstanceOf(ValueInjection::class, $result['anyDep']); + $this->assertSame($expected, $result['anyDep']->getValue()); + } + + public function providePreferenceConfigs() + { + $args = []; + + foreach (glob(__DIR__ . '/../_files/preferences/*.php') as $configFile) { + $config = include $configFile; + $configInstance = new Config($config); + + foreach ($config['expect'] as $expectation) { + list($requested, $expectedResult, $context) = $expectation; + $args[] = [ + $configInstance, + $requested, + $context, + $expectedResult + ]; + } + } + + return $args; + } + + /** + * @dataProvider providePreferenceConfigs + */ + public function testResolveConfiguredPreference(ConfigInterface $config, $requestClass, $context, $expectedType) + { + $resolver = new DependencyResolver(new RuntimeDefinition(), $config); + $this->assertSame($expectedType, $resolver->resolvePreference($requestClass, $context)); + } +} diff --git a/test/Resolver/ValueInjectionTest.php b/test/Resolver/ValueInjectionTest.php new file mode 100644 index 00000000..9ac1278f --- /dev/null +++ b/test/Resolver/ValueInjectionTest.php @@ -0,0 +1,131 @@ +streamFixture) { + $this->streamFixture = fopen('php://temp', 'w+'); + } + } + + protected function tearDown() + { + if ($this->streamFixture) { + fclose($this->streamFixture); + $this->streamFixture = null; + } + + parent::tearDown(); + } + + public function provideConstructionValues() + { + return [ + ['Hello World'], + [true], + [7364234], + [new \stdClass()] + ]; + } + + /** + * @dataProvider provideConstructionValues + */ + public function testSetStateConstructsInstance($value) + { + $result = ValueInjection::__set_state(['value' => $value]); + $this->assertInstanceOf(ValueInjection::class, $result); + $this->assertSame($value, $result->getValue()); + } + + public function provideExportableValues() + { + return [ + ['Testvalue'], + [124342], + [uniqid()], + [time()], + [true], + [false], + [null], + [microtime(true)], + [new TestAsset\Resolver\ExportableValue()] + ]; + } + + public function provideUnexportableItems() + { + if (! $this->streamFixture) { + $this->streamFixture = fopen('php://temp', 'w+'); + } + + return [ + [$this->streamFixture], + [new TestAsset\Resolver\UnexportableValue1()], + [new TestAsset\Resolver\UnexportableValue2()], + ]; + } + + /** + * @dataProvider provideUnexportableItems + */ + public function testExportThrowsExceptionForUnexportable($value) + { + $instance = new ValueInjection($value); + + $this->expectException(Exception\RuntimeException::class); + $instance->export(); + } + + /** + * @dataProvider provideUnexportableItems + */ + public function testIsExportableReturnsFalseForUnexportable($value) + { + $instance = new ValueInjection($value); + $this->assertFalse($instance->isExportable()); + } + + /** + * @dataProvider provideExportableValues + */ + public function testIsExportableReturnsTrueForExportableValues($value) + { + $instance = new ValueInjection($value); + $this->assertTrue($instance->isExportable()); + } + + /** + * @dataProvider provideExportableValues + */ + public function testExportWithExportableValues($value) + { + $instance = new ValueInjection($value); + $result = $instance->export(); + + $this->assertInternalType('string', $result, 'Export is expected to return a string value'); + $this->assertNotEquals('', $result, 'The exported value must not be empty'); + } +} diff --git a/test/ServiceLocator/DependencyInjectorProxyTest.php b/test/ServiceLocator/DependencyInjectorProxyTest.php deleted file mode 100644 index b6618486..00000000 --- a/test/ServiceLocator/DependencyInjectorProxyTest.php +++ /dev/null @@ -1,36 +0,0 @@ -instanceManager()->setParameters( - 'ZendTest\Di\TestAsset\SetterInjection\B', - ['a' => $a] - ); - $proxy = new DependencyInjectorProxy($di); - $b = $proxy->get('ZendTest\Di\TestAsset\SetterInjection\B'); - $methods = $b->getMethods(); - $this->assertSame('setA', $methods[0]['method']); - $this->assertSame($a, $methods[0]['params'][0]); - } -} diff --git a/test/ServiceLocator/GeneratorTest.php b/test/ServiceLocator/GeneratorTest.php deleted file mode 100644 index 5e0263bc..00000000 --- a/test/ServiceLocator/GeneratorTest.php +++ /dev/null @@ -1,394 +0,0 @@ -tmpFile = false; - $this->di = new Di; - } - - public function tearDown() - { - if ($this->tmpFile) { - unlink($this->tmpFile); - $this->tmpFile = false; - } - } - - public function getTmpFile() - { - $this->tmpFile = tempnam(sys_get_temp_dir(), 'zdi'); - return $this->tmpFile; - } - - public function createDefinitions() - { - $inspect = new Builder\PhpClass(); - $inspect->setName('ZendTest\Di\TestAsset\InspectedClass'); - $inspectCtor = new Builder\InjectionMethod(); - $inspectCtor->setName('__construct') - ->addParameter('foo', 'composed') - ->addParameter('baz', null); - $inspect->addInjectionMethod($inspectCtor); - - $composed = new Builder\PhpClass(); - $composed->setName('ZendTest\Di\TestAsset\ComposedClass'); - - $struct = new Builder\PhpClass(); - $struct->setName('ZendTest\Di\TestAsset\Struct'); - $structCtor = new Builder\InjectionMethod(); - $structCtor->setName('__construct') - ->addParameter('param1', null) - ->addParameter('param2', 'inspect'); - - $definition = new Definition(); - $definition->addClass($inspect) - ->addClass($composed) - ->addClass($struct); - $this->di->definitions()->unshift($definition); - - $data = [ - 'instance' => [ - 'alias' => [ - 'composed' => 'ZendTest\Di\TestAsset\ComposedClass', - 'inspect' => 'ZendTest\Di\TestAsset\InspectedClass', - 'struct' => 'ZendTest\Di\TestAsset\Struct', - ], - 'preferences' => [ - 'composed' => ['composed'], - 'inspect' => ['inspect'], - 'struct' => ['struct'], - ], - 'ZendTest\Di\TestAsset\InspectedClass' => [ 'parameters' => [ - 'baz' => 'BAZ', - ]], - 'ZendTest\Di\TestAsset\Struct' => [ 'parameters' => [ - 'param1' => 'foo', - ]], - ], - ]; - $configuration = new Config($data); - $configuration->configure($this->di); - } - - public function buildContainerClass($name = 'Application') - { - $this->createDefinitions(); - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass($name); - $builder->getCodeGenerator($this->getTmpFile())->write(); - $this->assertFileExists($this->tmpFile); - } - - /** - * @group one - */ - public function testCreatesContainerClassFromConfiguredDependencyInjector() - { - $this->buildContainerClass(); - - $tokens = token_get_all(file_get_contents($this->tmpFile)); - $count = count($tokens); - $found = false; - $value = false; - for ($i = 0; $i < $count; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - continue; - } - if (T_CLASS == $token[0]) { - $found = true; - $value = false; - do { - $i++; - $token = $tokens[$i]; - if (is_string($token)) { - $id = null; - } else { - list($id, $value) = $token; - } - } while (($i < $count) && ($id != T_STRING)); - break; - } - } - $this->assertTrue($found, "Class token not found"); - $this->assertContains('Application', $value); - } - - public function testCreatesContainerClassWithCasesForEachService() - { - $this->buildContainerClass(); - - $tokens = token_get_all(file_get_contents($this->tmpFile)); - $count = count($tokens); - $services = []; - for ($i = 0; $i < $count; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - continue; - } - if ('T_CASE' == token_name($token[0])) { - do { - $i++; - if ($i >= $count) { - break; - } - $token = $tokens[$i]; - if (is_string($token)) { - $id = $token; - } else { - $id = $token[0]; - } - } while (($i < $count) && ($id != T_CONSTANT_ENCAPSED_STRING)); - if (is_array($token)) { - $services[] = preg_replace('/\\\'/', '', $token[1]); - } - } - } - $expected = [ - 'composed', - 'ZendTest\Di\TestAsset\ComposedClass', - 'inspect', - 'ZendTest\Di\TestAsset\InspectedClass', - 'struct', - 'ZendTest\Di\TestAsset\Struct', - ]; - $this->assertEquals(count($expected), count($services), var_export($services, 1)); - foreach ($expected as $service) { - $this->assertContains($service, $services); - } - } - - public function testCreatesContainerClassWithMethodsForEachServiceAndAlias() - { - $this->buildContainerClass(); - $tokens = token_get_all(file_get_contents($this->tmpFile)); - $count = count($tokens); - $methods = []; - for ($i = 0; $i < $count; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - continue; - } - if ("T_FUNCTION" == token_name($token[0])) { - $value = false; - do { - $i++; - $token = $tokens[$i]; - if (is_string($token)) { - $id = null; - } else { - list($id, $value) = $token; - } - } while (($i < $count) && (token_name($id) != 'T_STRING')); - if ($value) { - $methods[] = $value; - } - } - } - $expected = [ - 'get', - 'getZendTestDiTestAssetComposedClass', - 'getComposed', - 'getZendTestDiTestAssetInspectedClass', - 'getInspect', - 'getZendTestDiTestAssetStruct', - 'getStruct', - ]; - $this->assertEquals(count($expected), count($methods), var_export($methods, 1)); - foreach ($expected as $method) { - $this->assertContains($method, $methods); - } - } - - public function testAllowsRetrievingClassFileCodeGenerationObject() - { - $this->createDefinitions(); - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass('Application'); - $codegen = $builder->getCodeGenerator(); - $this->assertInstanceOf('Zend\Code\Generator\FileGenerator', $codegen); - } - - public function testCanSpecifyNamespaceForGeneratedPhpClassfile() - { - $this->createDefinitions(); - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass('Context') - ->setNamespace('Application'); - $codegen = $builder->getCodeGenerator(); - $this->assertEquals('Application', $codegen->getNamespace()); - } - - /** - * @group nullargs - */ - public function testNullAsOnlyArgumentResultsInEmptyParameterList() - { - $this->markTestIncomplete('Null arguments are currently unsupported'); - $opt = new Builder\PhpClass(); - $opt->setName('ZendTest\Di\TestAsset\OptionalArg'); - $optCtor = new Builder\InjectionMethod(); - $optCtor->setName('__construct') - ->addParameter('param', null); - $opt->addInjectionMethod($optCtor); - $def = new Definition(); - $def->addClass($opt); - $this->di->setDefinition($def); - - $cfg = new Config([ - 'instance' => [ - 'alias' => ['optional' => 'ZendTest\Di\TestAsset\OptionalArg'], - ], - 'properties' => [ - 'ZendTest\Di\TestAsset\OptionalArg' => ['param' => null], - ], - ]); - $cfg->configure($this->di); - - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass('Container'); - $codeGen = $builder->getCodeGenerator(); - $classBody = $codeGen->generate(); - $this->assertNotContains('NULL)', $classBody, $classBody); - } - - /** - * @group nullargs - */ - public function testNullAsLastArgumentsResultsInTruncatedParameterList() - { - $this->markTestIncomplete('Null arguments are currently unsupported'); - $struct = new Builder\PhpClass(); - $struct->setName('ZendTest\Di\TestAsset\Struct'); - $structCtor = new Builder\InjectionMethod(); - $structCtor->setName('__construct') - ->addParameter('param1', null) - ->addParameter('param2', null); - $struct->addInjectionMethod($structCtor); - - $dummy = new Builder\PhpClass(); - $dummy->setName('ZendTest\Di\TestAsset\DummyParams') - ->setInstantiator(['ZendTest\Di\TestAsset\StaticFactory', 'factory']); - - $staticFactory = new Builder\PhpClass(); - $staticFactory->setName('ZendTest\Di\TestAsset\StaticFactory'); - $factory = new Builder\InjectionMethod(); - $factory->setName('factory') - ->addParameter('struct', 'struct') - ->addParameter('params', null); - $staticFactory->addInjectionMethod($factory); - - $def = new Definition(); - $def->addClass($struct) - ->addClass($dummy) - ->addClass($staticFactory); - - $this->di->setDefinition($def); - - $cfg = new Config([ - 'instance' => [ - 'alias' => [ - 'struct' => 'ZendTest\Di\TestAsset\Struct', - 'dummy' => 'ZendTest\Di\TestAsset\DummyParams', - 'factory' => 'ZendTest\Di\TestAsset\StaticFactory', - ], - 'properties' => [ - 'ZendTest\Di\TestAsset\Struct' => [ - 'param1' => 'foo', - 'param2' => 'bar', - ], - 'ZendTest\Di\TestAsset\StaticFactory' => [ - 'params' => null, - ], - ], - ], - ]); - $cfg->configure($this->di); - - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass('Container'); - $codeGen = $builder->getCodeGenerator(); - $classBody = $codeGen->generate(); - $this->assertNotContains('NULL)', $classBody, $classBody); - } - - /** - * @group nullargs - */ - public function testNullArgumentsResultInEmptyMethodParameterList() - { - $this->markTestIncomplete('Null arguments are currently unsupported'); - $opt = new Builder\PhpClass(); - $opt->setName('ZendTest\Di\TestAsset\OptionalArg'); - $optCtor = new Builder\InjectionMethod(); - $optCtor->setName('__construct') - ->addParameter('param', null); - $optInject = new Builder\InjectionMethod(); - $optInject->setName('inject') - ->addParameter('param1', null) - ->addParameter('param2', null); - $opt->addInjectionMethod($optCtor) - ->addInjectionMethod($optInject); - - $def = new Definition(); - $def->addClass($opt); - $this->di->setDefinition($def); - - $cfg = new Config([ - 'instance' => [ - 'alias' => ['optional' => 'ZendTest\Di\TestAsset\OptionalArg'], - ], - 'properties' => [ - 'ZendTest\Di\TestAsset\OptionalArg' => [ - 'param' => null, - 'param1' => null, - 'param2' => null, - ], - ], - ]); - $cfg->configure($this->di); - - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass('Container'); - $codeGen = $builder->getCodeGenerator(); - $classBody = $codeGen->generate(); - $this->assertNotContains('NULL)', $classBody, $classBody); - } - - public function testClassNamesInstantiatedDirectlyShouldBeFullyQualified() - { - $this->createDefinitions(); - $builder = new ContainerGenerator($this->di); - $builder->setContainerClass('Context') - ->setNamespace('Application'); - $content = $builder->getCodeGenerator()->generate(); - $count = substr_count($content, '\ZendTest\Di\TestAsset\\'); - $this->assertEquals(3, $count, $content); - $this->assertNotContains('\\\\', $content); - } -} diff --git a/test/ServiceLocatorTest.php b/test/ServiceLocatorTest.php deleted file mode 100644 index a29bb96e..00000000 --- a/test/ServiceLocatorTest.php +++ /dev/null @@ -1,124 +0,0 @@ -services = new ServiceLocator(); - } - - public function testRetrievingUnknownServiceResultsInNullValue() - { - $this->assertNull($this->services->get('foo')); - } - - public function testCanRetrievePreviouslyRegisteredServices() - { - $s = new \stdClass; - $this->services->set('foo', $s); - $test = $this->services->get('foo'); - $this->assertSame($s, $test); - } - - public function testRegisteringAServiceUnderAnExistingNameOverwrites() - { - $s = new \stdClass(); - $t = new \stdClass(); - $this->services->set('foo', $s); - $this->services->set('foo', $t); - $test = $this->services->get('foo'); - $this->assertSame($t, $test); - } - - public function testRetrievingAServiceMultipleTimesReturnsSameInstance() - { - $s = new \stdClass(); - $this->services->set('foo', $s); - $test1 = $this->services->get('foo'); - $test2 = $this->services->get('foo'); - $this->assertSame($s, $test1); - $this->assertSame($s, $test2); - $this->assertSame($test1, $test2); - } - - public function testRegisteringCallbacksReturnsReturnValueWhenServiceRequested() - { - $this->services->set('foo', function () { - $object = new \stdClass(); - $object->foo = 'FOO'; - return $object; - }); - $test = $this->services->get('foo'); - $this->assertInstanceOf('stdClass', $test); - $this->assertEquals('FOO', $test->foo); - } - - public function testReturnValueOfCallbackIsCachedBetweenRequestsToService() - { - $this->services->set('foo', function () { - $object = new \stdClass(); - $object->foo = 'FOO'; - return $object; - }); - $test1 = $this->services->get('foo'); - $test2 = $this->services->get('foo'); - $this->assertEquals('FOO', $test1->foo); - $this->assertSame($test1, $test2); - } - - public function testParametersArePassedToCallbacks() - { - $this->services->set('foo', function () { - $object = new \stdClass(); - $object->params = func_get_args(); - return $object; - }); - - $params = ['foo', 'bar']; - $test = $this->services->get('foo', $params); - $this->assertEquals($params, $test->params); - } - - public function testGetProxiesToMappedMethods() - { - $sc = new TestAsset\ContainerExtension(); - $sc->foo = 'FOO'; - $this->assertEquals('FOO', $sc->get('foo')); - } - - public function testProxiedMethodsReceiveParametersPassedToGet() - { - $sc = new TestAsset\ContainerExtension(); - $params = ['foo' => 'FOO']; - $test = $sc->get('params', $params); - $this->assertEquals($params, $test); - $this->assertEquals($params, $sc->params); - } - - public function testHasReturnsFalseIfLocatorDoesNotKnowOfService() - { - $this->assertFalse($this->services->has('does-not-exist')); - } - - public function testHasReturnsTrueIfLocatorKnowsOfService() - { - $this->services->set('foo', function () { - // Implementation does not matter for this test - return $this; - }); - - $this->assertTrue($this->services->has('foo')); - } -} diff --git a/test/TestAsset/A.php b/test/TestAsset/A.php new file mode 100644 index 00000000..2e4bd2e1 --- /dev/null +++ b/test/TestAsset/A.php @@ -0,0 +1,7 @@ +items[] = $item; - return $this; - } -} diff --git a/test/TestAsset/AggregatedParamClass.php b/test/TestAsset/AggregatedParamClass.php deleted file mode 100644 index 61c4d3de..00000000 --- a/test/TestAsset/AggregatedParamClass.php +++ /dev/null @@ -1,22 +0,0 @@ -aggregator = $item; - } -} diff --git a/test/TestAsset/AwareClasses/B.php b/test/TestAsset/AwareClasses/B.php deleted file mode 100644 index 430596e2..00000000 --- a/test/TestAsset/AwareClasses/B.php +++ /dev/null @@ -1,27 +0,0 @@ -injectedA = $a; + } +} diff --git a/test/TestAsset/DummyParams.php b/test/TestAsset/BuiltinTypehintParameters.php similarity index 60% rename from test/TestAsset/DummyParams.php rename to test/TestAsset/BuiltinTypehintParameters.php index 11c4490f..3176507a 100644 --- a/test/TestAsset/DummyParams.php +++ b/test/TestAsset/BuiltinTypehintParameters.php @@ -3,18 +3,19 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace ZendTest\Di\TestAsset; -class DummyParams +class BuiltinTypehintParameters { - public $params; + public function callableType(callable $p) + { + } - public function __construct(array $params) + public function arrayType(array $r) { - $this->params = $params; } } diff --git a/test/TestAsset/CallbackClasses/B.php b/test/TestAsset/CallbackClasses/B.php deleted file mode 100644 index c7f00c90..00000000 --- a/test/TestAsset/CallbackClasses/B.php +++ /dev/null @@ -1,28 +0,0 @@ -c = $c; - $b->params = $params; - return $b; - } - - protected function __construct() - { - // no dice - } -} diff --git a/test/TestAsset/CircularClasses/A.php b/test/TestAsset/CircularClasses/A.php index 1b76bb19..2da5854e 100644 --- a/test/TestAsset/CircularClasses/A.php +++ b/test/TestAsset/CircularClasses/A.php @@ -13,6 +13,5 @@ class A { public function __construct(B $b) { - } } diff --git a/test/TestAsset/CircularClasses/B.php b/test/TestAsset/CircularClasses/B.php index 02a9143a..68858dfc 100644 --- a/test/TestAsset/CircularClasses/B.php +++ b/test/TestAsset/CircularClasses/B.php @@ -11,5 +11,7 @@ class B { - public function __construct(A $a) {} + public function __construct(A $a) + { + } } diff --git a/test/TestAsset/CircularClasses/C.php b/test/TestAsset/CircularClasses/C.php index 4faecd4c..7349f38e 100644 --- a/test/TestAsset/CircularClasses/C.php +++ b/test/TestAsset/CircularClasses/C.php @@ -13,6 +13,5 @@ class C { public function __construct(D $d) { - } } diff --git a/test/TestAsset/CircularClasses/D.php b/test/TestAsset/CircularClasses/D.php index 274e485f..5a427855 100644 --- a/test/TestAsset/CircularClasses/D.php +++ b/test/TestAsset/CircularClasses/D.php @@ -13,6 +13,5 @@ class D { public function __construct(E $e) { - } } diff --git a/test/TestAsset/CircularClasses/E.php b/test/TestAsset/CircularClasses/E.php index 7edd78af..6f48afa2 100644 --- a/test/TestAsset/CircularClasses/E.php +++ b/test/TestAsset/CircularClasses/E.php @@ -13,6 +13,5 @@ class E { public function __construct(C $c) { - } } diff --git a/test/TestAsset/CircularClasses/X.php b/test/TestAsset/CircularClasses/X.php index cbc91f68..9b7a42b3 100644 --- a/test/TestAsset/CircularClasses/X.php +++ b/test/TestAsset/CircularClasses/X.php @@ -13,6 +13,5 @@ class X { public function __construct(X $x) { - } } diff --git a/test/TestAsset/CircularClasses/Y.php b/test/TestAsset/CircularClasses/Y.php index 5ffb8c75..ca3e36d8 100644 --- a/test/TestAsset/CircularClasses/Y.php +++ b/test/TestAsset/CircularClasses/Y.php @@ -13,6 +13,5 @@ class Y { public function __construct(Y $y = null) { - } } diff --git a/test/TestAsset/CompilerClasses/A.php b/test/TestAsset/CompilerClasses/A.php deleted file mode 100644 index 98106bac..00000000 --- a/test/TestAsset/CompilerClasses/A.php +++ /dev/null @@ -1,14 +0,0 @@ -a = $a; } } diff --git a/test/TestAsset/ConstructorInjection/B.php b/test/TestAsset/Constructor/RequiredArguments.php similarity index 53% rename from test/TestAsset/ConstructorInjection/B.php rename to test/TestAsset/Constructor/RequiredArguments.php index c9405b57..bf05712b 100644 --- a/test/TestAsset/ConstructorInjection/B.php +++ b/test/TestAsset/Constructor/RequiredArguments.php @@ -3,17 +3,15 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace ZendTest\Di\TestAsset\ConstructorInjection; +namespace ZendTest\Di\TestAsset\Constructor; -class B +class RequiredArguments { - public $a = null; - public function __construct(A $a) + public function __construct(NoConstructor $objectDep, \ArrayAccess $internalClassDep, $anyDep) { - $this->a = $a; } } diff --git a/test/TestAsset/ConstructorInjection/A.php b/test/TestAsset/ConstructorInjection/A.php deleted file mode 100644 index 119cd480..00000000 --- a/test/TestAsset/ConstructorInjection/A.php +++ /dev/null @@ -1,14 +0,0 @@ -a = $a; - $this->params = $params; - } -} diff --git a/test/TestAsset/ConstructorInjection/D.php b/test/TestAsset/ConstructorInjection/D.php deleted file mode 100644 index d59fe801..00000000 --- a/test/TestAsset/ConstructorInjection/D.php +++ /dev/null @@ -1,21 +0,0 @@ -d = $d; - } -} diff --git a/test/TestAsset/ConstructorInjection/E.php b/test/TestAsset/ConstructorInjection/E.php deleted file mode 100644 index 6594f6f8..00000000 --- a/test/TestAsset/ConstructorInjection/E.php +++ /dev/null @@ -1,14 +0,0 @@ -params = $params; - } -} diff --git a/test/TestAsset/ConstructorInjection/OptionalParameters.php b/test/TestAsset/ConstructorInjection/OptionalParameters.php deleted file mode 100644 index 3aae8a87..00000000 --- a/test/TestAsset/ConstructorInjection/OptionalParameters.php +++ /dev/null @@ -1,43 +0,0 @@ -a = $a; - $this->b = $b; - $this->c = $c; - } -} diff --git a/test/TestAsset/ConstructorInjection/X.php b/test/TestAsset/ConstructorInjection/X.php deleted file mode 100644 index 13025c74..00000000 --- a/test/TestAsset/ConstructorInjection/X.php +++ /dev/null @@ -1,21 +0,0 @@ -one = $one; - $this->two = $two; - } -} diff --git a/test/TestAsset/ConstructorInjection/Y.php b/test/TestAsset/ConstructorInjection/Y.php deleted file mode 100644 index 01893bd9..00000000 --- a/test/TestAsset/ConstructorInjection/Y.php +++ /dev/null @@ -1,19 +0,0 @@ -x = $x; - } -} diff --git a/test/TestAsset/ConstructorInjection/Z.php b/test/TestAsset/ConstructorInjection/Z.php deleted file mode 100644 index 263ccf2b..00000000 --- a/test/TestAsset/ConstructorInjection/Z.php +++ /dev/null @@ -1,19 +0,0 @@ -y = $y; - } -} diff --git a/test/TestAsset/ContainerExtension.php b/test/TestAsset/ContainerExtension.php deleted file mode 100644 index 790f82cc..00000000 --- a/test/TestAsset/ContainerExtension.php +++ /dev/null @@ -1,34 +0,0 @@ - 'getFoo', - 'params' => 'getParams', - ); - - public function getFoo() - { - return $this->foo; - } - - public function getParams(array $params) - { - $this->params = $params; - return $this->params; - } -} diff --git a/test/TestAsset/DependencyTree/AdditionalLevel1.php b/test/TestAsset/DependencyTree/AdditionalLevel1.php new file mode 100644 index 00000000..afaa28c2 --- /dev/null +++ b/test/TestAsset/DependencyTree/AdditionalLevel1.php @@ -0,0 +1,23 @@ +result = $dep; + } +} diff --git a/test/TestAsset/DependencyTree/Complex.php b/test/TestAsset/DependencyTree/Complex.php new file mode 100644 index 00000000..ab73ef96 --- /dev/null +++ b/test/TestAsset/DependencyTree/Complex.php @@ -0,0 +1,29 @@ +result = $dep; + $this->result2 = $dep2; + } +} diff --git a/test/TestAsset/BasicClassWithParent.php b/test/TestAsset/DependencyTree/Level1.php similarity index 50% rename from test/TestAsset/BasicClassWithParent.php rename to test/TestAsset/DependencyTree/Level1.php index 6cc147bd..083b7e38 100644 --- a/test/TestAsset/BasicClassWithParent.php +++ b/test/TestAsset/DependencyTree/Level1.php @@ -3,18 +3,21 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace ZendTest\Di\TestAsset; +namespace ZendTest\Di\TestAsset\DependencyTree; -class BasicClassWithParent +class Level1 { - public $parent; + /** + * @var Level2 + */ + public $result; - public function __construct(BasicClass $parent, $foo) + public function __construct(Level2 $dep) { - $this->parent = $parent; + $this->result = $dep; } } diff --git a/test/TestAsset/CallbackClasses/A.php b/test/TestAsset/DependencyTree/Level2.php similarity index 52% rename from test/TestAsset/CallbackClasses/A.php rename to test/TestAsset/DependencyTree/Level2.php index 19990ac3..1d725e77 100644 --- a/test/TestAsset/CallbackClasses/A.php +++ b/test/TestAsset/DependencyTree/Level2.php @@ -3,18 +3,18 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -namespace ZendTest\Di\TestAsset\CallbackClasses; +namespace ZendTest\Di\TestAsset\DependencyTree; -class A +class Level2 { - public static function factory() + public $optionalResult; + + public function __construct($opt = null) { - return new self(); + $this->optionalResult = $opt; } - - private function __construct() {} } diff --git a/test/TestAsset/DependencyTree/Level2Preference.php b/test/TestAsset/DependencyTree/Level2Preference.php new file mode 100644 index 00000000..37ce3f99 --- /dev/null +++ b/test/TestAsset/DependencyTree/Level2Preference.php @@ -0,0 +1,14 @@ +result = $dep; + } +} diff --git a/test/TestAsset/ExtendedB.php b/test/TestAsset/ExtendedB.php new file mode 100644 index 00000000..12d572df --- /dev/null +++ b/test/TestAsset/ExtendedB.php @@ -0,0 +1,8 @@ +test = $test; - return $this; - } -} diff --git a/test/TestAsset/InheritanceClasses/C.php b/test/TestAsset/InheritanceClasses/C.php deleted file mode 100644 index e08c65bc..00000000 --- a/test/TestAsset/InheritanceClasses/C.php +++ /dev/null @@ -1,14 +0,0 @@ -bs[] = $b; - } - - public function injectBOnce(B $b) - { - $this->bs[] = $b; - } - - public function injectBTwice(B $b) - { - $this->bs[] = $b; - } - - public function injectSplitDependency(B $b, $somestring) - { - $b->id = $somestring; - $this->bs[] = $b; - } -} diff --git a/test/TestAsset/InjectionClasses/B.php b/test/TestAsset/InjectionClasses/B.php deleted file mode 100644 index 3aaaf9c7..00000000 --- a/test/TestAsset/InjectionClasses/B.php +++ /dev/null @@ -1,19 +0,0 @@ -id = $id; - } -} diff --git a/test/TestAsset/InjectionClasses/C.php b/test/TestAsset/InjectionClasses/C.php deleted file mode 100644 index b96cbca1..00000000 --- a/test/TestAsset/InjectionClasses/C.php +++ /dev/null @@ -1,14 +0,0 @@ -foo = $foo; - $this->baz = $baz; - } -} diff --git a/test/TestAsset/InvalidCompilerClasses/InvalidClass.php b/test/TestAsset/InvalidCompilerClasses/InvalidClass.php deleted file mode 100644 index f31b13b1..00000000 --- a/test/TestAsset/InvalidCompilerClasses/InvalidClass.php +++ /dev/null @@ -1,19 +0,0 @@ -foo = $foo; - } -} diff --git a/test/TestAsset/BasicClassWithParam.php b/test/TestAsset/IterableDependency.php similarity index 64% rename from test/TestAsset/BasicClassWithParam.php rename to test/TestAsset/IterableDependency.php index 70c8f7c4..fb4136b3 100644 --- a/test/TestAsset/BasicClassWithParam.php +++ b/test/TestAsset/IterableDependency.php @@ -3,13 +3,15 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace ZendTest\Di\TestAsset; -class BasicClassWithParam +class IterableDependency { - public function __construct($foo) {} + public function __construct(iterable $iterator) + { + } } diff --git a/test/TestAsset/Option1ForA.php b/test/TestAsset/Option1ForA.php new file mode 100644 index 00000000..b332851f --- /dev/null +++ b/test/TestAsset/Option1ForA.php @@ -0,0 +1,8 @@ +a = $a; - } -} diff --git a/test/TestAsset/RequiresA.php b/test/TestAsset/RequiresA.php new file mode 100644 index 00000000..97ec5561 --- /dev/null +++ b/test/TestAsset/RequiresA.php @@ -0,0 +1,10 @@ + + * @license LUKA Proprietary + * @copyright Copyright (c) 2017 LUKA netconsult GmbH (www.luka.de) + */ + +namespace ZendTest\Di\TestAsset\Resolver; + +class ExportableValue +{ + public static function __set_state($data) + { + } +} diff --git a/test/TestAsset/Resolver/UnexportableValue1.php b/test/TestAsset/Resolver/UnexportableValue1.php new file mode 100644 index 00000000..eb482fc3 --- /dev/null +++ b/test/TestAsset/Resolver/UnexportableValue1.php @@ -0,0 +1,12 @@ + + * @license LUKA Proprietary + * @copyright Copyright (c) 2017 LUKA netconsult GmbH (www.luka.de) + */ + +namespace ZendTest\Di\TestAsset\Resolver; + +class UnexportableValue1 +{ +} diff --git a/test/TestAsset/Resolver/UnexportableValue2.php b/test/TestAsset/Resolver/UnexportableValue2.php new file mode 100644 index 00000000..30543d08 --- /dev/null +++ b/test/TestAsset/Resolver/UnexportableValue2.php @@ -0,0 +1,15 @@ + + * @license LUKA Proprietary + * @copyright Copyright (c) 2017 LUKA netconsult GmbH (www.luka.de) + */ + +namespace ZendTest\Di\TestAsset\Resolver; + +class UnexportableValue2 +{ + private static function __set_state($data) + { + } +} diff --git a/test/TestAsset/OptionalArg.php b/test/TestAsset/ScalarTypehintParameters.php similarity index 52% rename from test/TestAsset/OptionalArg.php rename to test/TestAsset/ScalarTypehintParameters.php index d6b6a9b8..e1adaa18 100644 --- a/test/TestAsset/OptionalArg.php +++ b/test/TestAsset/ScalarTypehintParameters.php @@ -3,20 +3,27 @@ * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2017 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace ZendTest\Di\TestAsset; -class OptionalArg +class ScalarTypehintParameters { - public function __construct($param = null) + public function intType(int $p) { - $this->param = $param; } - public function inject($param1 = null, $param2 = null) + public function boolType(bool $p) + { + } + + public function stringType(string $p) + { + } + + public function floatType(float $p) { } } diff --git a/test/TestAsset/SetterInjection/A.php b/test/TestAsset/SetterInjection/A.php deleted file mode 100644 index b1915db1..00000000 --- a/test/TestAsset/SetterInjection/A.php +++ /dev/null @@ -1,14 +0,0 @@ -a = $a; - } -} diff --git a/test/TestAsset/SetterInjection/D.php b/test/TestAsset/SetterInjection/D.php deleted file mode 100644 index d8c2f45a..00000000 --- a/test/TestAsset/SetterInjection/D.php +++ /dev/null @@ -1,19 +0,0 @@ -a = $a; - } -} diff --git a/test/TestAsset/SetterInjection/StaticSetter.php b/test/TestAsset/SetterInjection/StaticSetter.php deleted file mode 100644 index ed47cce2..00000000 --- a/test/TestAsset/SetterInjection/StaticSetter.php +++ /dev/null @@ -1,30 +0,0 @@ -one = $one; - } - public function setTwo($two) - { - $this->two = $two; - } -} diff --git a/test/TestAsset/SetterInjection/Y.php b/test/TestAsset/SetterInjection/Y.php deleted file mode 100644 index 39c43a7d..00000000 --- a/test/TestAsset/SetterInjection/Y.php +++ /dev/null @@ -1,19 +0,0 @@ -x = $x; - } -} diff --git a/test/TestAsset/SetterInjection/Z.php b/test/TestAsset/SetterInjection/Z.php deleted file mode 100644 index 5e11f11c..00000000 --- a/test/TestAsset/SetterInjection/Z.php +++ /dev/null @@ -1,19 +0,0 @@ -y = $y; - } -} diff --git a/test/TestAsset/SharedInstance/Lister.php b/test/TestAsset/SharedInstance/Lister.php deleted file mode 100644 index 68cc3d26..00000000 --- a/test/TestAsset/SharedInstance/Lister.php +++ /dev/null @@ -1,20 +0,0 @@ -sharedLister = $lister; - } -} diff --git a/test/TestAsset/SharedInstance/Movie.php b/test/TestAsset/SharedInstance/Movie.php deleted file mode 100644 index 893d1c15..00000000 --- a/test/TestAsset/SharedInstance/Movie.php +++ /dev/null @@ -1,20 +0,0 @@ -lister = $lister; - } -} diff --git a/test/TestAsset/SharedInstance/SharedLister.php b/test/TestAsset/SharedInstance/SharedLister.php deleted file mode 100644 index 4c436ab9..00000000 --- a/test/TestAsset/SharedInstance/SharedLister.php +++ /dev/null @@ -1,14 +0,0 @@ -lister = $lister; - } -} diff --git a/test/TestAsset/StaticFactory.php b/test/TestAsset/StaticFactory.php deleted file mode 100644 index 54cffd9d..00000000 --- a/test/TestAsset/StaticFactory.php +++ /dev/null @@ -1,19 +0,0 @@ -param1 = $param1; - $this->param2 = $param2; - } -} diff --git a/test/_files/config.ini b/test/_files/config.ini deleted file mode 100644 index 4b479281..00000000 --- a/test/_files/config.ini +++ /dev/null @@ -1,27 +0,0 @@ -[testing] -definitions.struct.class = "ZendTest\Di\TestAsset\Struct" -definitions.struct.params.param1 = "foo" -definitions.struct.params.param2 = "bar" -definitions.struct.param_map.param1 = 0 -definitions.struct.param_map.param2 = 1 - -definitions.params.class = "ZendTest\Di\TestAsset\DummyParams" -definitions.params.constructor_callback.class = "ZendTest\Di\TestAsset\StaticFactory" -definitions.params.constructor_callback.method = "factory" -definitions.params.params.struct.__reference = "struct" -definitions.params.params.params.foo = "bar" -definitions.params.param_map.struct = 0 -definitions.params.param_map.params = 1 - -definitions.injected.class = "ZendTest\Di\TestAsset\InjectedMethod" -definitions.injected.methods.set_object.name = "setObject" -definitions.injected.methods.set_object.params.1.__reference = "params" - -definitions.inspected.class = "ZendTest\Di\TestAsset\InspectedClass" -definitions.inspected.params.baz = "BAZ" -definitions.inspected.params.foo = "FOO" - -aliases.struct = "ZendTest\Di\TestAsset\Struct" -aliases.params = "ZendTest\Di\TestAsset\DummyParams" -aliases.injected = "ZendTest\Di\TestAsset\InjectedMethod" -aliases.inspected = "ZendTest\Di\TestAsset\InspectedClass" diff --git a/test/_files/config.json b/test/_files/config.json deleted file mode 100644 index c446b500..00000000 --- a/test/_files/config.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "testing": { - "definitions": [ - { - "class": "ZendTest\\Di\\TestAsset\\Struct", - "params": { - "param1": "foo", - "param2": "bar" - }, - "param_map": { - "param1": 0, - "param2": 1 - } - }, - { - "class": "ZendTest\\Di\\TestAsset\\DummyParams", - "constructor_callback": { - "class": "ZendTest\\Di\\TestAsset\\StaticFactory", - "method": "factory" - }, - "params": { - "struct": { "__reference": "struct" }, - "params": {"foo": "bar"} - }, - "param_map": { - "struct": 0, - "params": 1 - } - }, - { - "class": "ZendTest\\Di\\TestAsset\\InjectedMethod", - "methods": [ - { - "name": "setObject", - "params": [ { "__reference": "params" } ] - } - ] - }, - { - "class": "ZendTest\\Di\\TestAsset\\InspectedClass", - "params": { - "baz": "BAZ", - "foo": "FOO" - } - } - ], - "aliases": { - "struct": "ZendTest\\Di\\TestAsset\\Struct", - "params": "ZendTest\\Di\\TestAsset\\DummyParams", - "injected": "ZendTest\\Di\\TestAsset\\InjectedMethod", - "inspected": "ZendTest\\Di\\TestAsset\\InspectedClass" - } - } -} diff --git a/test/_files/config.xml b/test/_files/config.xml deleted file mode 100644 index a5d779c4..00000000 --- a/test/_files/config.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - ZendTest\Di\TestAsset\Struct - - foo - bar - - - 0 - 1 - - - - ZendTest\Di\TestAsset\DummyParams - - ZendTest\Di\TestAsset\StaticFactory - factory - - - <__reference>struct - bar - - - 0 - 1 - - - - ZendTest\Di\TestAsset\InjectedMethod - - - setObject - <__reference>params - - - - - ZendTest\Di\TestAsset\InspectedClass - - BAZ - FOO - - - - ZendTest\Di\TestAsset\Struct - ZendTest\Di\TestAsset\DummyParams - ZendTest\Di\TestAsset\InjectedMethod - ZendTest\Di\TestAsset\InspectedClass - - - diff --git a/test/_files/config.yml b/test/_files/config.yml deleted file mode 100644 index 861fd80b..00000000 --- a/test/_files/config.yml +++ /dev/null @@ -1,41 +0,0 @@ -testing: - definitions: - - - class: ZendTest\Di\TestAsset\Struct - params: - param1: foo - param2: bar - param_map: - param1: 0 - param2: 1 - - - class: ZendTest\Di\TestAsset\DummyParams - constructor_callback: - class: ZendTest\Di\TestAsset\StaticFactory - method: factory - params: - struct: - __reference: struct - params: - foo: bar - param_map: - struct: 0 - params: 1 - - - class: ZendTest\Di\TestAsset\InjectedMethod - methods: - setObject: - name: setObject - params: - - - __reference: params - - - class: ZendTest\Di\TestAsset\InspectedClass - params: - baz: BAZ - foo: FOO - aliases: - struct: ZendTest\Di\TestAsset\Struct - params: ZendTest\Di\TestAsset\DummyParams - injected: ZendTest\Di\TestAsset\InjectedMethod - inspected: ZendTest\Di\TestAsset\InspectedClass diff --git a/test/_files/definition-array.php b/test/_files/definition-array.php deleted file mode 100644 index 4557e750..00000000 --- a/test/_files/definition-array.php +++ /dev/null @@ -1,67 +0,0 @@ - - array ( - 'superTypes' => - array ( - ), - 'instantiator' => '__construct', - 'methods' => - array ( - '__construct' => - array ( - 'username' => NULL, - 'password' => NULL, - ), - ), - ), - 'My\\EntityA' => - array ( - 'supertypes' => - array ( - ), - 'instantiator' => NULL, - 'methods' => - array ( - ), - ), - 'My\\Mapper' => - array ( - 'supertypes' => - array ( - 0 => 'ArrayObject', - ), - 'instantiator' => '__construct', - 'methods' => - array ( - 'setDbAdapter' => - array ( - 'dbAdapter' => 'My\\DbAdapter', - ), - ), - ), - 'My\\RepositoryA' => - array ( - 'superTypes' => - array ( - ), - 'instantiator' => '__construct', - 'injectionMethods' => - array ( - 'setMapper' => - array ( - 'mapper' => 'My\\Mapper', - ), - ), - ), - 'My\\RepositoryB' => - array ( - 'superTypes' => - array ( - 0 => 'My\\RepositoryA', - ), - 'instantiator' => NULL, - 'Methods' => - array ( - ), - ), -); diff --git a/test/_files/preferences/common.php b/test/_files/preferences/common.php new file mode 100644 index 00000000..214fd2e7 --- /dev/null +++ b/test/_files/preferences/common.php @@ -0,0 +1,78 @@ + [ + // Requested type, expected result, context + [ TestAsset\A::class, TestAsset\Option1ForA::class, null ], + [ TestAsset\A::class, TestAsset\Option2ForA::class, TestAsset\B::class ], + [ TestAsset\A::class, TestAsset\Option1ForA::class, TestAsset\RequiresA::class ], + [ TestAsset\B::class, null, TestAsset\RequiresA::class ], + [ TestAsset\B::class, TestAsset\ExtendedB::class, TestAsset\Parameters::class ], + [ TestAsset\A::class, TestAsset\Option1ForA::class, TestAsset\Parameters::class ], + [ TestAsset\A::class, TestAsset\Option1ForA::class, TestAsset\Constructor\EmptyConstructor::class ], + [ TestAsset\B::class, null, TestAsset\Constructor\EmptyConstructor::class ], + + [ TestAsset\A::class, TestAsset\Option2ForA::class, 'Some.Alias' ], + [ TestAsset\B::class, null, 'Some.Alias' ], + + [ TestAsset\A::class, TestAsset\Option1ForA::class, 'Some.Other.Alias' ], + [ TestAsset\B::class, TestAsset\ExtendedB::class, 'Some.Other.Alias' ], + + [ TestAsset\A::class, 'Alias.ForA', TestAsset\Constructor\OptionalArguments::class ], + [ TestAsset\B::class, null, TestAsset\Constructor\OptionalArguments::class ], + + [ TestAsset\B::class, 'Alias.ForB', 'Alias.ForA' ] + + ], + 'preferences' => [ + TestAsset\A::class => TestAsset\Option1ForA::class + ], + 'types' => [ + TestAsset\B::class => [ + 'preferences' => [ + TestAsset\A::class => TestAsset\Option2ForA::class + ] + ], + TestAsset\RequiresA::class => [ + 'preferences' => [ + TestAsset\A::class => 'Invalid.Class.Name' + ] + ], + TestAsset\Parameters::class => [ + 'preferences' => [ + TestAsset\B::class => TestAsset\ExtendedB::class + ] + ], + + TestAsset\Constructor\OptionalArguments::class => [ + 'preferences' => [ + TestAsset\A::class => 'Alias.ForA', + TestAsset\B::class => 'Alias.ForA', + ] + ], + + 'Some.Alias' => [ + 'typeOf' => TestAsset\Hierarchy\A::class, + 'preferences' => [ + TestAsset\A::class => TestAsset\Option2ForA::class + ] + ], + 'Some.Other.Alias' => [ + 'typeOf' => TestAsset\Hierarchy\A::class, + 'preferences' => [ + TestAsset\B::class => TestAsset\ExtendedB::class + ] + ], + 'Alias.ForA' => [ + 'typeOf' => TestAsset\A::class, + 'preferences' => [ + TestAsset\B::class => 'Alias.ForB' + ] + ], + 'Alias.ForB' => [ + 'typeOf' => TestAsset\B::class, + ] + ] +]; diff --git a/test/_files/sample-config.php b/test/_files/sample-config.php new file mode 100644 index 00000000..21d0d9fd --- /dev/null +++ b/test/_files/sample-config.php @@ -0,0 +1,30 @@ + [ + 'A' => 'GlobalA', + 'B' => 'GlobalB' + ], + 'types' => [ + 'Foo' => [ + 'preferences' => [ + 'A' => 'LocalA', + ], + 'parameters' => [ + 'a' => '*' + ] + ], + 'Bar' => [ + 'typeOf' => 'Foo', + 'preferences' => [ + 'B' => 'LocalB' + ] + ] + ], + + 'arbitaryKey' => 'value', + 'factories' => [ + 'should be' => [ + 'ignored' => 'as well' + ] + ] +]; diff --git a/test/_files/sample-definitions.php b/test/_files/sample-definitions.php deleted file mode 100644 index e3616a68..00000000 --- a/test/_files/sample-definitions.php +++ /dev/null @@ -1,87 +0,0 @@ - [ - 'di' => [ - 'instance' => [ - 'alias' => [ - 'my-repository' => 'My\RepositoryA', - 'my-mapper' => 'My\Mapper', - 'my-dbAdapter' => 'My\DbAdapter', - ], - 'preferences' => [ - 'my-repository' => [ 'my-mapper' ], - 'my-mapper' => [ 'my-dbAdapter' ], - ], - 'My\DbAdapter' => [ - 'parameters' => [ - 'username' => 'readonly', - 'password' => 'mypassword', - ], - ], - 'my-dbAdapter' => [ - 'parameters' => [ - 'username' => 'readwrite', - ], - ], - ], - ], - ], - 'section-b' => [ - 'di' => [ - 'definitions' => [ - 1 => [ - 'class' => Zend\Di\Definition\BuilderDefinition::class, - 'My\DbAdapter' => [ - 'methods' => [ - '__construct' => [ - 'username' => null, - 'password' => null, - ], - ], - ], - 'My\Mapper' => [ - 'methods' => [ - '__construct' => [ - 'dbAdapter' => 'My\DbAdapter', - ], - ], - ], - 'My\Repository' => [ - 'methods' => [ - '__construct' => [ - 'mapper' => 'My\Mapper', - ], - ], - ], - ], - ], - ], - ], - 'section-c' => [ - 'di' => [ - 'definition' => [ - 'runtime' => [ - 'xxx' => 'zzz', - ], - ], - ], - ], - 'section-d' => [ - 'di' => [ - 'definition' => [ - 'runtime' => [ - 'enabled' => false, - ], - ], - ], - ], - 'section-e' => [ - 'di' => [ - 'definition' => [ - 'runtime' => [ - 'use_annotations' => true, - ], - ], - ], - ], -]; diff --git a/test/_files/sample.php b/test/_files/sample.php deleted file mode 100644 index a670f604..00000000 --- a/test/_files/sample.php +++ /dev/null @@ -1,10 +0,0 @@ - array( - 'definition' => array( - 'compiler' => array( - __DIR__ . '/definition-array.php', - ), - ), - ), -); diff --git a/test/bootstrap.php b/test/bootstrap.php deleted file mode 100644 index 64f48ba3..00000000 --- a/test/bootstrap.php +++ /dev/null @@ -1,34 +0,0 @@ -