From d060a98707748686340980eb8bfddbf0c4a6a9ec Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Fri, 31 Aug 2012 14:44:59 -0500 Subject: [PATCH] [zendframework/zf2#2284][ZF2-507] Updated README - Notice about Date header --- .coveralls.yml | 3 + .gitattributes | 6 + .gitignore | 14 + .php_cs | 43 ++ .travis.yml | 35 ++ CONTRIBUTING.md | 229 ++++++++++ LICENSE.txt | 27 ++ README.md | 14 + composer.json | 35 ++ phpunit.xml.dist | 34 ++ phpunit.xml.travis | 34 ++ src/Exception/ExceptionInterface.php | 19 + src/Exception/InvalidArgumentException.php | 22 + src/Exception/RuntimeException.php | 22 + src/Feature/AutoloaderProviderInterface.php | 28 ++ src/Feature/BootstrapListenerInterface.php | 30 ++ src/Feature/ConfigProviderInterface.php | 26 ++ .../ConsoleBannerProviderInterface.php | 34 ++ src/Feature/ConsoleUsageProviderInterface.php | 44 ++ .../ControllerPluginProviderInterface.php | 27 ++ src/Feature/ControllerProviderInterface.php | 27 ++ src/Feature/InitProviderInterface.php | 29 ++ src/Feature/LocatorRegisteredInterface.php | 28 ++ src/Feature/ServiceProviderInterface.php | 27 ++ src/Feature/ViewHelperProviderInterface.php | 27 ++ src/Listener/AbstractListener.php | 76 ++++ src/Listener/AutoloaderListener.php | 42 ++ src/Listener/ConfigListener.php | 403 ++++++++++++++++++ src/Listener/ConfigMergerInterface.php | 37 ++ src/Listener/DefaultListenerAggregate.php | 113 +++++ src/Listener/Exception/ExceptionInterface.php | 24 ++ .../Exception/InvalidArgumentException.php | 24 ++ src/Listener/Exception/RuntimeException.php | 24 ++ src/Listener/InitTrigger.php | 40 ++ src/Listener/ListenerOptions.php | 276 ++++++++++++ src/Listener/LocatorRegistrationListener.php | 138 ++++++ src/Listener/ModuleResolverListener.php | 38 ++ src/Listener/OnBootstrapListener.php | 44 ++ src/Listener/ServiceListener.php | 264 ++++++++++++ src/Listener/ServiceListenerInterface.php | 36 ++ src/ModuleEvent.php | 130 ++++++ src/ModuleManager.php | 278 ++++++++++++ src/ModuleManagerInterface.php | 61 +++ test/Listener/AutoloaderListenerTest.php | 84 ++++ test/Listener/ConfigListenerTest.php | 386 +++++++++++++++++ .../Listener/DefaultListenerAggregateTest.php | 118 +++++ test/Listener/InitTriggerTest.php | 72 ++++ test/Listener/ListenerOptionsTest.php | 96 +++++ .../LocatorRegistrationListenerTest.php | 125 ++++++ test/Listener/ModuleResolverListenerTest.php | 70 +++ test/Listener/OnBootstrapListenerTest.php | 87 ++++ test/Listener/ServiceListenerTest.php | 156 +++++++ .../TestAsset/ServiceInvalidReturnModule.php | 26 ++ .../TestAsset/ServiceProviderModule.php | 31 ++ test/Listener/_files/bad/config.badext | 0 test/Listener/_files/bad/config.php | 3 + test/Listener/_files/good/config.ini | 1 + test/Listener/_files/good/config.json | 3 + test/Listener/_files/good/config.php | 4 + test/Listener/_files/good/config.xml | 4 + test/Listener/_files/good/config.yml | 1 + test/Listener/_files/good/merge1.php | 5 + test/Listener/_files/good/merge2.php | 5 + test/ModuleEventTest.php | 68 +++ test/ModuleManagerTest.php | 146 +++++++ test/TestAsset/BadConfigModule/Module.php | 19 + test/TestAsset/BafModule/Module.php | 21 + test/TestAsset/BafModule/configs/config.php | 4 + test/TestAsset/BamModule/Module.php | 21 + test/TestAsset/BamModule/configs/config.php | 4 + test/TestAsset/BarModule/Module.php | 21 + test/TestAsset/BarModule/configs/config.php | 4 + test/TestAsset/BazModule/Module.php | 21 + test/TestAsset/BazModule/configs/config.php | 4 + test/TestAsset/BooModule/Module.php | 21 + test/TestAsset/BooModule/configs/config.php | 4 + test/TestAsset/BorModule/Module.php | 21 + test/TestAsset/BorModule/configs/config.php | 4 + test/TestAsset/ListenerTestModule/Module.php | 62 +++ .../ListenerTestModule/src/Foo/Bar.php | 26 ++ test/TestAsset/LoadOtherModule/Module.php | 26 ++ test/TestAsset/MockApplication.php | 87 ++++ test/TestAsset/NotAutoloaderModule/Module.php | 28 ++ .../NotAutoloaderModule/src/Foo/Bar.php | 14 + test/TestAsset/SomeModule/Module.php | 21 + test/TestAsset/SomeModule/configs/config.php | 4 + test/_files/config.bad | 0 test/_files/config.ini | 2 + test/_files/config.json | 1 + test/_files/config.php | 6 + test/_files/config.xml | 6 + test/_files/config.yaml | 2 + test/bootstrap.php | 34 ++ 93 files changed, 4891 insertions(+) create mode 100644 .coveralls.yml create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .php_cs create mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 phpunit.xml.travis create mode 100644 src/Exception/ExceptionInterface.php create mode 100644 src/Exception/InvalidArgumentException.php create mode 100644 src/Exception/RuntimeException.php create mode 100644 src/Feature/AutoloaderProviderInterface.php create mode 100644 src/Feature/BootstrapListenerInterface.php create mode 100644 src/Feature/ConfigProviderInterface.php create mode 100644 src/Feature/ConsoleBannerProviderInterface.php create mode 100644 src/Feature/ConsoleUsageProviderInterface.php create mode 100644 src/Feature/ControllerPluginProviderInterface.php create mode 100644 src/Feature/ControllerProviderInterface.php create mode 100644 src/Feature/InitProviderInterface.php create mode 100644 src/Feature/LocatorRegisteredInterface.php create mode 100644 src/Feature/ServiceProviderInterface.php create mode 100644 src/Feature/ViewHelperProviderInterface.php create mode 100644 src/Listener/AbstractListener.php create mode 100644 src/Listener/AutoloaderListener.php create mode 100644 src/Listener/ConfigListener.php create mode 100644 src/Listener/ConfigMergerInterface.php create mode 100644 src/Listener/DefaultListenerAggregate.php create mode 100644 src/Listener/Exception/ExceptionInterface.php create mode 100644 src/Listener/Exception/InvalidArgumentException.php create mode 100644 src/Listener/Exception/RuntimeException.php create mode 100644 src/Listener/InitTrigger.php create mode 100644 src/Listener/ListenerOptions.php create mode 100644 src/Listener/LocatorRegistrationListener.php create mode 100644 src/Listener/ModuleResolverListener.php create mode 100644 src/Listener/OnBootstrapListener.php create mode 100644 src/Listener/ServiceListener.php create mode 100644 src/Listener/ServiceListenerInterface.php create mode 100644 src/ModuleEvent.php create mode 100644 src/ModuleManager.php create mode 100644 src/ModuleManagerInterface.php create mode 100644 test/Listener/AutoloaderListenerTest.php create mode 100644 test/Listener/ConfigListenerTest.php create mode 100644 test/Listener/DefaultListenerAggregateTest.php create mode 100644 test/Listener/InitTriggerTest.php create mode 100644 test/Listener/ListenerOptionsTest.php create mode 100644 test/Listener/LocatorRegistrationListenerTest.php create mode 100644 test/Listener/ModuleResolverListenerTest.php create mode 100644 test/Listener/OnBootstrapListenerTest.php create mode 100644 test/Listener/ServiceListenerTest.php create mode 100644 test/Listener/TestAsset/ServiceInvalidReturnModule.php create mode 100644 test/Listener/TestAsset/ServiceProviderModule.php create mode 100644 test/Listener/_files/bad/config.badext create mode 100644 test/Listener/_files/bad/config.php create mode 100644 test/Listener/_files/good/config.ini create mode 100644 test/Listener/_files/good/config.json create mode 100644 test/Listener/_files/good/config.php create mode 100644 test/Listener/_files/good/config.xml create mode 100644 test/Listener/_files/good/config.yml create mode 100644 test/Listener/_files/good/merge1.php create mode 100644 test/Listener/_files/good/merge2.php create mode 100644 test/ModuleEventTest.php create mode 100644 test/ModuleManagerTest.php create mode 100644 test/TestAsset/BadConfigModule/Module.php create mode 100644 test/TestAsset/BafModule/Module.php create mode 100644 test/TestAsset/BafModule/configs/config.php create mode 100644 test/TestAsset/BamModule/Module.php create mode 100644 test/TestAsset/BamModule/configs/config.php create mode 100644 test/TestAsset/BarModule/Module.php create mode 100644 test/TestAsset/BarModule/configs/config.php create mode 100644 test/TestAsset/BazModule/Module.php create mode 100644 test/TestAsset/BazModule/configs/config.php create mode 100644 test/TestAsset/BooModule/Module.php create mode 100644 test/TestAsset/BooModule/configs/config.php create mode 100644 test/TestAsset/BorModule/Module.php create mode 100644 test/TestAsset/BorModule/configs/config.php create mode 100644 test/TestAsset/ListenerTestModule/Module.php create mode 100644 test/TestAsset/ListenerTestModule/src/Foo/Bar.php create mode 100644 test/TestAsset/LoadOtherModule/Module.php create mode 100644 test/TestAsset/MockApplication.php create mode 100644 test/TestAsset/NotAutoloaderModule/Module.php create mode 100644 test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php create mode 100644 test/TestAsset/SomeModule/Module.php create mode 100644 test/TestAsset/SomeModule/configs/config.php create mode 100644 test/_files/config.bad create mode 100644 test/_files/config.ini create mode 100644 test/_files/config.json create mode 100644 test/_files/config.php create mode 100644 test/_files/config.xml create mode 100644 test/_files/config.yaml create mode 100644 test/bootstrap.php diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..53bda82 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +coverage_clover: clover.xml +json_path: coveralls-upload.json +src_dir: src diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..85dc9a8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/test export-ignore +/vendor export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +.php_cs export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4cac0a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.buildpath +.DS_Store +.idea +.project +.settings/ +.*.sw* +.*.un~ +nbproject +tmp/ + +clover.xml +coveralls-upload.json +phpunit.xml +vendor diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..bf4b799 --- /dev/null +++ b/.php_cs @@ -0,0 +1,43 @@ +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', + 'psr0', + 'remove_lines_between_uses', + '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 new file mode 100644 index 0000000..fe909ec --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +sudo: false + +language: php + +matrix: + fast_finish: true + include: + - php: 5.5 + - php: 5.6 + env: + - EXECUTE_TEST_COVERALLS=true + - EXECUTE_CS_CHECK=true + - php: 7 + - php: hhvm + allow_failures: + - php: 7 + - php: hhvm + +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 + +install: + - composer install --no-interaction --prefer-source + +script: + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit -c phpunit.xml.travis --coverage-clover clover.xml ; fi + - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then ./vendor/bin/phpunit -c phpunit.xml.travis ; fi + - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run --config-file=.php_cs ; fi + +after_script: + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/coveralls ; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b0e4b7e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,229 @@ +# CONTRIBUTING + +## RESOURCES + +If you wish to contribute to Zend Framework, please be sure to +read/subscribe to the following resources: + + - [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards) + - [Contributor's Guide](http://framework.zend.com/participate/contributor-guide) + - ZF Contributor's mailing list: + Archives: http://zend-framework-community.634137.n4.nabble.com/ZF-Contributor-f680267.html + Subscribe: zf-contributors-subscribe@lists.zend.com + - ZF Contributor's IRC channel: + #zftalk.dev on Freenode.net + +If you are working on new features or refactoring [create a proposal](https://github.com/zendframework/zend-module-manager/issues/new). + +## Reporting Potential Security Issues + +If you have encountered a potential security vulnerability, please **DO NOT** report it on the public +issue tracker: send it to us at [zf-security@zend.com](mailto:zf-security@zend.com) instead. +We will work with you to verify the vulnerability and patch it as soon as possible. + +When reporting issues, please provide the following information: + +- Component(s) affected +- A description indicating how to reproduce the issue +- A summary of the security vulnerability and impact + +We request that you contact us via the email address above and give the project +contributors a chance to resolve the vulnerability and issue a new release prior +to any public exposure; this helps protect users and provides them with a chance +to upgrade and/or update in order to protect their applications. + +For sensitive email communications, please use [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc). + +## RUNNING TESTS + +> ### Note: testing versions prior to 2.4 +> +> This component originates with Zend Framework 2. During the lifetime of ZF2, +> testing infrastructure migrated from PHPUnit 3 to PHPUnit 4. In most cases, no +> changes were necessary. However, due to the migration, tests may not run on +> versions < 2.4. As such, you may need to change the PHPUnit dependency if +> attempting a fix on such a version. + +To run tests: + +- Clone the repository: + + ```console + $ git clone git@github.com:zendframework/zend-module-manager.git + $ cd + ``` + +- Install dependencies via composer: + + ```console + $ curl -sS https://getcomposer.org/installer | php -- + $ ./composer.phar install + ``` + + If you don't have `curl` installed, you can also download `composer.phar` from https://getcomposer.org/ + +- Run the tests via `phpunit` and the provided PHPUnit config, like in this example: + + ```console + $ ./vendor/bin/phpunit + ``` + +You can turn on conditional tests with the phpunit.xml file. +To do so: + + - Copy `phpunit.xml.dist` file to `phpunit.xml` + - Edit `phpunit.xml` to enable any specific functionality you + want to test, as well as to provide test values to utilize. + +## Running Coding Standards Checks + +This component uses [php-cs-fixer](http://cs.sensiolabs.org/) for coding +standards checks, and provides configuration for our selected checks. +`php-cs-fixer` is installed by default via Composer. + +To run checks only: + +```console +$ ./vendor/bin/php-cs-fixer fix . -v --diff --dry-run --config-file=.php_cs +``` + +To have `php-cs-fixer` attempt to fix problems for you, omit the `--dry-run` +flag: + +```console +$ ./vendor/bin/php-cs-fixer fix . -v --diff --config-file=.php_cs +``` + +If you allow php-cs-fixer to fix CS issues, please re-run the tests to ensure +they pass, and make sure you add and commit the changes after verification. + +## Recommended Workflow for Contributions + +Your first step is to establish a public repository from which we can +pull your work into the master repository. We recommend using +[GitHub](https://github.com), as that is where the component is already hosted. + +1. Setup a [GitHub account](http://github.com/), if you haven't yet +2. Fork the repository (http://github.com/zendframework/zend-module-manager) +3. Clone the canonical repository locally and enter it. + + ```console + $ git clone git://github.com:zendframework/zend-module-manager.git + $ cd zend-module-manager + ``` + +4. Add a remote to your fork; substitute your GitHub username in the command + below. + + ```console + $ git remote add {username} git@github.com:{username}/zend-module-manager.git + $ git fetch {username} + ``` + +### Keeping Up-to-Date + +Periodically, you should update your fork or personal repository to +match the canonical ZF repository. Assuming you have setup your local repository +per the instructions above, you can do the following: + + +```console +$ git checkout master +$ git fetch origin +$ git rebase origin/master +# OPTIONALLY, to keep your remote up-to-date - +$ git push {username} master:master +``` + +If you're tracking other branches -- for example, the "develop" branch, where +new feature development occurs -- you'll want to do the same operations for that +branch; simply substitute "develop" for "master". + +### Working on a patch + +We recommend you do each new feature or bugfix in a new branch. This simplifies +the task of code review as well as the task of merging your changes into the +canonical repository. + +A typical workflow will then consist of the following: + +1. Create a new local branch based off either your master or develop branch. +2. Switch to your new local branch. (This step can be combined with the + previous step with the use of `git checkout -b`.) +3. Do some work, commit, repeat as necessary. +4. Push the local branch to your remote repository. +5. Send a pull request. + +The mechanics of this process are actually quite trivial. Below, we will +create a branch for fixing an issue in the tracker. + +```console +$ git checkout -b hotfix/9295 +Switched to a new branch 'hotfix/9295' +``` + +... do some work ... + + +```console +$ git commit +``` + +... write your log message ... + + +```console +$ git push {username} hotfix/9295:hotfix/9295 +Counting objects: 38, done. +Delta compression using up to 2 threads. +Compression objects: 100% (18/18), done. +Writing objects: 100% (20/20), 8.19KiB, done. +Total 20 (delta 12), reused 0 (delta 0) +To ssh://git@github.com/{username}/zend-module-manager.git + b5583aa..4f51698 HEAD -> master +``` + +To send a pull request, you have two options. + +If using GitHub, you can do the pull request from there. Navigate to +your repository, select the branch you just created, and then select the +"Pull Request" button in the upper right. Select the user/organization +"zendframework" as the recipient. + +If using your own repository - or even if using GitHub - you can use `git +format-patch` to create a patchset for us to apply; in fact, this is +**recommended** for security-related patches. If you use `format-patch`, please +send the patches as attachments to: + +- zf-devteam@zend.com for patches without security implications +- zf-security@zend.com for security patches + +#### What branch to issue the pull request against? + +Which branch should you issue a pull request against? + +- For fixes against the stable release, issue the pull request against the + "master" branch. +- For new features, or fixes that introduce new elements to the public API (such + as new public methods or properties), issue the pull request against the + "develop" branch. + +### Branch Cleanup + +As you might imagine, if you are a frequent contributor, you'll start to +get a ton of branches both locally and on your remote. + +Once you know that your changes have been accepted to the master +repository, we suggest doing some cleanup of these branches. + +- Local branch cleanup + + ```console + $ git branch -d + ``` + +- Remote branch removal + + ```console + $ git push {username} : + ``` diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6eab5aa --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2005-2015, Zend Technologies USA, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Zend Technologies USA, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed21806 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# zend-modulemanager + +Zend Framework 2.0 introduces a new and powerful approach to modules. This new +module system is designed with flexibility, simplicity, and re-usability in mind. +A module may contain just about anything: PHP code, including MVC functionality; +library code; view scripts; and/or public assets such as images, CSS, and +JavaScript. The possibilities are endless. + +`Zend\ModuleManager` is the component that enables the design of a module +architecture for PHP applcations. + + +- File issues at https://github.com/zendframework/zend-modulemanager/issues +- Documentation is at http://framework.zend.com/manual/current/en/index.html#zend-modulemanager diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8445827 --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "zendframework/zend-modulemanager", + "description": " ", + "license": "BSD-3-Clause", + "keywords": [ + "zf2", + "modulemanager" + ], + "autoload": { + "psr-4": { + "Zend\\ModuleManager": "src/" + } + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "suggest": { + "zendframework/zend-config": "Zend\\Config component", + "zendframework/zend-loader": "Zend\\Loader component", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + }, + "homepage": "https://github.com/zendframework/zend-module-manager", + "autoload-dev": { + "psr-4": { + "ZendTest\\ModuleManager\\": "test/" + } + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "satooshi/php-coveralls": "dev-master", + "phpunit/PHPUnit": "~4.0" + } +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..c24282a --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,34 @@ + + + + + ./test/ + + + + + + disable + + + + + + ./src + + + + + + + + + + + diff --git a/phpunit.xml.travis b/phpunit.xml.travis new file mode 100644 index 0000000..c24282a --- /dev/null +++ b/phpunit.xml.travis @@ -0,0 +1,34 @@ + + + + + ./test/ + + + + + + disable + + + + + + ./src + + + + + + + + + + + diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..202ea31 --- /dev/null +++ b/src/Exception/ExceptionInterface.php @@ -0,0 +1,19 @@ + 'A short description of that parameter', + * '-another-parameter' => 'A short description of another parameter', + * ... + * ) + * + * @param AdapterInterface $console + * @return array|string|null + */ + public function getConsoleUsage(AdapterInterface $console); +} diff --git a/src/Feature/ControllerPluginProviderInterface.php b/src/Feature/ControllerPluginProviderInterface.php new file mode 100644 index 0000000..f0cbc5d --- /dev/null +++ b/src/Feature/ControllerPluginProviderInterface.php @@ -0,0 +1,27 @@ +setOptions(new ListenerOptions); + } else { + $this->setOptions($options); + } + } + + /** + * Get options. + * + * @return ListenerOptions + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set options. + * + * @param ListenerOptions $options the value to be set + * @return AbstractListener + */ + public function setOptions(ListenerOptions $options) + { + $this->options = $options; + return $this; + } + + /** + * Write a simple array of scalars to a file + * + * @param string $filePath + * @param array $array + * @return AbstractListener + */ + protected function writeArrayToFile($filePath, $array) + { + $content = "getModule(); + if (!$module instanceof AutoloaderProviderInterface + && !method_exists($module, 'getAutoloaderConfig') + ) { + return; + } + $autoloaderConfig = $module->getAutoloaderConfig(); + AutoloaderFactory::factory($autoloaderConfig); + } +} diff --git a/src/Listener/ConfigListener.php b/src/Listener/ConfigListener.php new file mode 100644 index 0000000..632c7ca --- /dev/null +++ b/src/Listener/ConfigListener.php @@ -0,0 +1,403 @@ +hasCachedConfig()) { + $this->skipConfig = true; + $this->setMergedConfig($this->getCachedConfig()); + } else { + $this->addConfigGlobPaths($this->getOptions()->getConfigGlobPaths()); + $this->addConfigStaticPaths($this->getOptions()->getConfigStaticPaths()); + } + } + + /** + * __invoke proxy to loadModule for easier attaching + * + * @param ModuleEvent $e + * @return ConfigListener + */ + public function __invoke(ModuleEvent $e) + { + return $this->loadModule($e); + } + + /** + * Attach one or more listeners + * + * @param EventManagerInterface $events + * @return ConfigListener + */ + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onloadModulesPre'), 1000); + + if ($this->skipConfig) { + // We already have the config from cache, no need to collect or merge. + return $this; + } + + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, array($this, 'onLoadModule')); + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModulesPost'), -1000); + + return $this; + } + + /** + * Pass self to the ModuleEvent object early so everyone has access. + * + * @param ModuleEvent $e + * @return ConfigListener + */ + public function onloadModulesPre(ModuleEvent $e) + { + $e->setConfigListener($this); + + return $this; + } + + /** + * Merge the config for each module + * + * @param ModuleEvent $e + * @return ConfigListener + */ + public function onLoadModule(ModuleEvent $e) + { + $module = $e->getModule(); + + if (!$module instanceof ConfigProviderInterface + && !is_callable(array($module, 'getConfig')) + ) { + return $this; + } + + $config = $module->getConfig(); + $this->addConfig($e->getModuleName(), $config); + + return $this; + } + + /** + * Merge all config files matched by the given glob()s + * + * This is only attached if config is not cached. + * + * @param ModuleEvent $e + * @return ConfigListener + */ + public function onLoadModulesPost(ModuleEvent $e) + { + // Load the config files + foreach ($this->paths as $path) { + $this->addConfigByPath($path['path'], $path['type']); + } + + // Merge all of the collected configs + $this->mergedConfig = $this->getOptions()->getExtraConfig() ?: array(); + foreach ($this->configs as $key => $config) { + $this->mergedConfig = ArrayUtils::merge($this->mergedConfig, $config); + } + + // If enabled, update the config cache + if ($this->getOptions()->getConfigCacheEnabled()) { + $this->updateCache(); + } + + return $this; + } + + /** + * Detach all previously attached listeners + * + * @param EventManagerInterface $events + * @return ConfigListener + */ + public function detach(EventManagerInterface $events) + { + foreach ($this->listeners as $key => $listener) { + $events->detach($listener); + unset($this->listeners[$key]); + } + $this->listeners = array(); + return $this; + } + + /** + * getMergedConfig + * + * @param bool $returnConfigAsObject + * @return mixed + */ + public function getMergedConfig($returnConfigAsObject = true) + { + if ($returnConfigAsObject === true) { + if ($this->mergedConfigObject === null) { + $this->mergedConfigObject = new Config($this->mergedConfig); + } + return $this->mergedConfigObject; + } else { + return $this->mergedConfig; + } + } + + /** + * setMergedConfig + * + * @param array $config + * @return ConfigListener + */ + public function setMergedConfig(array $config) + { + $this->mergedConfig = $config; + $this->mergedConfigObject = null; + return $this; + } + + /** + * Add an array of glob paths of config files to merge after loading modules + * + * @param array|Traversable $globPaths + * @return ConfigListener + */ + public function addConfigGlobPaths($globPaths) + { + $this->addConfigPaths($globPaths, self::GLOB_PATH); + return $this; + } + + /** + * Add a glob path of config files to merge after loading modules + * + * @param string $globPath + * @return ConfigListener + */ + public function addConfigGlobPath($globPath) + { + $this->addConfigPath($globPath, self::GLOB_PATH); + return $this; + } + + /** + * Add an array of static paths of config files to merge after loading modules + * + * @param array|Traversable $staticPaths + * @return ConfigListener + */ + public function addConfigStaticPaths($staticPaths) + { + $this->addConfigPaths($staticPaths, self::STATIC_PATH); + return $this; + } + + /** + * Add a static path of config files to merge after loading modules + * + * @param string $globPath + * @return ConfigListener + */ + public function addConfigStaticPath($staticPath) + { + $this->addConfigPath($staticPath, self::STATIC_PATH); + return $this; + } + + /** + * Add an array of paths of config files to merge after loading modules + * + * @param mixed $paths + * @return ConfigListener + */ + protected function addConfigPaths($paths, $type) + { + if ($paths instanceof Traversable) { + $paths = ArrayUtils::iteratorToArray($paths); + } + + if (!is_array($paths)) { + throw new Exception\InvalidArgumentException( + sprintf('Argument passed to %::%s() must be an array, ' + . 'implement the \Traversable interface, or be an ' + . 'instance of Zend\Config\Config. %s given.', + __CLASS__, __METHOD__, gettype($paths)) + ); + } + + foreach ($paths as $path) { + $this->addConfigPath($path, $type); + } + } + + /** + * Add a path of config files to load and merge after loading modules + * + * @param string $path + * @param string $type + * @return ConfigListener + */ + protected function addConfigPath($path, $type) + { + if (!is_string($path)) { + throw new Exception\InvalidArgumentException( + sprintf('Parameter to %s::%s() must be a string; %s given.', + __CLASS__, __METHOD__, gettype($path)) + ); + } + $this->paths[] = array('type' => $type, 'path' => $path); + return $this; + } + + + + protected function addConfig($key, $config) + { + if ($config instanceof Traversable) { + $config = ArrayUtils::iteratorToArray($config); + } + + if (!is_array($config)) { + throw new Exception\InvalidArgumentException( + sprintf('Config being merged must be an array, ' + . 'implement the \Traversable interface, or be an ' + . 'instance of Zend\Config\Config. %s given.', gettype($config)) + ); + } + + $this->configs[$key] = $config; + + return $this; + } + + /** + * Given a path (glob or static), fetch the config and add it to the array + * of configs to merge. + * + * @param string $path + * @param string $type + * @return ConfigListener + */ + protected function addConfigByPath($path, $type) + { + switch ($type) { + case self::STATIC_PATH: + $this->addConfig($path, ConfigFactory::fromFile($path)); + break; + + case self::GLOB_PATH: + // We want to keep track of where each value came from so we don't + // use ConfigFactory::fromFiles() since it does merging internally. + foreach (Glob::glob($path, Glob::GLOB_BRACE) as $file) { + $this->addConfig($file, ConfigFactory::fromFile($file)); + } + break; + } + + return $this; + } + + /** + * @return bool + */ + protected function hasCachedConfig() + { + if (($this->getOptions()->getConfigCacheEnabled()) + && (file_exists($this->getOptions()->getConfigCacheFile())) + ) { + return true; + } + return false; + } + + /** + * @return mixed + */ + protected function getCachedConfig() + { + return include $this->getOptions()->getConfigCacheFile(); + } + + /** + * @return ConfigListener + */ + protected function updateCache() + { + if (($this->getOptions()->getConfigCacheEnabled()) + && (false === $this->skipConfig) + ) { + $configFile = $this->getOptions()->getConfigCacheFile(); + $this->writeArrayToFile($configFile, $this->getMergedConfig(false)); + } + return $this; + } +} diff --git a/src/Listener/ConfigMergerInterface.php b/src/Listener/ConfigMergerInterface.php new file mode 100644 index 0000000..2c1319b --- /dev/null +++ b/src/Listener/ConfigMergerInterface.php @@ -0,0 +1,37 @@ +getOptions(); + $configListener = $this->getConfigListener(); + $locatorRegistrationListener = new LocatorRegistrationListener($options); + $moduleAutoloader = new ModuleAutoloader($options->getModulePaths()); + + // High priority, we assume module autoloading (for FooNamespace\Module classes) should be available before anything else + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($moduleAutoloader, 'register'), 9000); + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE_RESOLVE, new ModuleResolverListener); + // High priority, because most other loadModule listeners will assume the module's classes are available via autoloading + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new AutoloaderListener($options), 9000); + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new InitTrigger($options)); + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, new OnBootstrapListener($options)); + $this->listeners[] = $events->attach($locatorRegistrationListener); + $this->listeners[] = $events->attach($configListener); + return $this; + } + + /** + * Detach all previously attached listeners + * + * @param EventManagerInterface $events + * @return void + */ + public function detach(EventManagerInterface $events) + { + foreach ($this->listeners as $key => $listener) { + $detached = false; + if ($listener === $this) { + continue; + } + if ($listener instanceof ListenerAggregateInterface) { + $detached = $listener->detach($events); + } elseif ($listener instanceof CallbackHandler) { + $detached = $events->detach($listener); + } + + if ($detached) { + unset($this->listeners[$key]); + } + } + } + + /** + * Get the config merger. + * + * @return ConfigMergerInterface + */ + public function getConfigListener() + { + if (!$this->configListener instanceof ConfigMergerInterface) { + $this->setConfigListener(new ConfigListener($this->getOptions())); + } + return $this->configListener; + } + + /** + * Set the config merger to use. + * + * @param ConfigMergerInterface $configListener + * @return DefaultListenerAggregate + */ + public function setConfigListener(ConfigMergerInterface $configListener) + { + $this->configListener = $configListener; + return $this; + } +} diff --git a/src/Listener/Exception/ExceptionInterface.php b/src/Listener/Exception/ExceptionInterface.php new file mode 100644 index 0000000..4714b4b --- /dev/null +++ b/src/Listener/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ +getModule(); + if (!$module instanceof InitProviderInterface + && !method_exists($module, 'init') + ) { + return; + } + + $module->init($e->getTarget()); + } +} diff --git a/src/Listener/ListenerOptions.php b/src/Listener/ListenerOptions.php new file mode 100644 index 0000000..761fbb8 --- /dev/null +++ b/src/Listener/ListenerOptions.php @@ -0,0 +1,276 @@ +modulePaths; + } + + /** + * Set an array of paths where modules reside + * + * @param array|Traversable $modulePaths + * @return ListenerOptions + */ + public function setModulePaths($modulePaths) + { + if (!is_array($modulePaths) && !$modulePaths instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf('Argument passed to %s::%s() must be an array, ' + . 'implement the \Traversable interface, or be an ' + . 'instance of Zend\Config\Config. %s given.', + __CLASS__, __METHOD__, gettype($modulePaths)) + ); + } + $this->modulePaths = $modulePaths; + return $this; + } + + /** + * Get the glob patterns to load additional config files + * + * @return array + */ + public function getConfigGlobPaths() + { + return $this->configGlobPaths; + } + + /** + * Get the static paths to load additional config files + * + * @return array + */ + public function getConfigStaticPaths() + { + return $this->configStaticPaths; + } + + /** + * Set the glob patterns to use for loading additional config files + * + * @param array|Traversable $configGlobPaths + * @return ListenerOptions + */ + public function setConfigGlobPaths($configGlobPaths) + { + if (!is_array($configGlobPaths) && !$configGlobPaths instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf('Argument passed to %s::%s() must be an array, ' + . 'implement the \Traversable interface, or be an ' + . 'instance of Zend\Config\Config. %s given.', + __CLASS__, __METHOD__, gettype($configGlobPaths)) + ); + } + $this->configGlobPaths = $configGlobPaths; + return $this; + } + + /** + * Set the static paths to use for loading additional config files + * + * @param array|Traversable $configStaticPaths + * @return ListenerOptions + */ + public function setConfigStaticPaths($configStaticPaths) + { + if (!is_array($configStaticPaths) && !$configStaticPaths instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf('Argument passed to %s::%s() must be an array, ' + . 'implement the \Traversable interface, or be an ' + . 'instance of Zend\Config\Config. %s given.', + __CLASS__, __METHOD__, gettype($configStaticPaths)) + ); + } + $this->configStaticPaths = $configStaticPaths; + return $this; + } + + /** + * Get any extra config to merge in. + * + * @return array|Traversable + */ + public function getExtraConfig() + { + return $this->extraConfig; + } + + /** + * Add some extra config array to the main config. This is mainly useful + * for unit testing purposes. + * + * @param array|Traversable $extraConfig + * @return ListenerOptions + */ + public function setExtraConfig($extraConfig) + { + if (!is_array($extraConfig) && !$extraConfig instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf('Argument passed to %s::%s() must be an array, ' + . 'implement the \Traversable interface, or be an ' + . 'instance of Zend\Config\Config. %s given.', + __CLASS__, __METHOD__, gettype($extraConfig)) + ); + } + $this->extraConfig = $extraConfig; + return $this; + } + + /** + * Check if the config cache is enabled + * + * @return bool + */ + public function getConfigCacheEnabled() + { + return $this->configCacheEnabled; + } + + /** + * Set if the config cache should be enabled or not + * + * @param bool $enabled + * @return ListenerOptions + */ + public function setConfigCacheEnabled($enabled) + { + $this->configCacheEnabled = (bool) $enabled; + return $this; + } + + /** + * Get key used to create the cache file name + * + * @return string + */ + public function getConfigCacheKey() + { + return (string) $this->configCacheKey; + } + + /** + * Set key used to create the cache file name + * + * @param string $configCacheKey the value to be set + * @return ListenerOptions + */ + public function setConfigCacheKey($configCacheKey) + { + $this->configCacheKey = $configCacheKey; + return $this; + } + + /** + * Get the path to the config cache + * + * Should this be an option, or should the dir option include the + * filename, or should it simply remain hard-coded? Thoughts? + * + * @return string + */ + public function getConfigCacheFile() + { + return $this->getCacheDir() . '/module-config-cache.'.$this->getConfigCacheKey().'.php'; + } + + /** + * Get the path where cache file(s) are stored + * + * @return string + */ + public function getCacheDir() + { + return $this->cacheDir; + } + + /** + * Set the path where cache files can be stored + * + * @param string $cacheDir the value to be set + * @return ListenerOptions + */ + public function setCacheDir($cacheDir) + { + if (null === $cacheDir) { + $this->cacheDir = $cacheDir; + } else { + $this->cacheDir = static::normalizePath($cacheDir); + } + return $this; + } + + /** + * Normalize a path for insertion in the stack + * + * @param string $path + * @return string + */ + public static function normalizePath($path) + { + $path = rtrim($path, '/'); + $path = rtrim($path, '\\'); + return $path; + } +} diff --git a/src/Listener/LocatorRegistrationListener.php b/src/Listener/LocatorRegistrationListener.php new file mode 100644 index 0000000..def065c --- /dev/null +++ b/src/Listener/LocatorRegistrationListener.php @@ -0,0 +1,138 @@ +getModule() instanceof LocatorRegisteredInterface) { + return; + } + $this->modules[] = $e->getModule(); + } + + /** + * loadModulesPost + * + * Once all the modules are loaded, loop + * + * @param Event $e + * @return void + */ + public function onLoadModulesPost(Event $e) + { + $moduleManager = $e->getTarget(); + $events = $moduleManager->getEventManager()->getSharedManager(); + + // Shared instance for module manager + $events->attach('Zend\Mvc\Application', 'bootstrap', function ($e) use ($moduleManager) { + $moduleClassName = get_class($moduleManager); + $application = $e->getApplication(); + $services = $application->getServiceManager(); + if (!$services->has($moduleClassName)) { + $services->setService($moduleClassName, $moduleManager); + } + }, 1000); + + if (0 === count($this->modules)) { + return; + } + + // Attach to the bootstrap event if there are modules we need to process + $events->attach('Zend\Mvc\Application', 'bootstrap', array($this, 'onBootstrap'), 1000); + } + + /** + * Bootstrap listener + * + * This is ran during the MVC bootstrap event because it requires access to + * the DI container. + * + * @TODO: Check the application / locator / etc a bit better to make sure + * the env looks how we're expecting it to? + * @param Event $e + * @return void + */ + public function onBootstrap(Event $e) + { + $application = $e->getApplication(); + $services = $application->getServiceManager(); + + foreach ($this->modules as $module) { + $moduleClassName = get_class($module); + if (!$services->has($moduleClassName)) { + $services->setService($moduleClassName, $module); + } + } + } + + /** + * Attach one or more listeners + * + * @param EventManagerInterface $events + * @return LocatorRegistrationListener + */ + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, array($this, 'onLoadModule')); + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModulesPost'), -1000); + return $this; + } + + /** + * Detach all previously attached listeners + * + * @param EventManagerInterface $events + * @return void + */ + public function detach(EventManagerInterface $events) + { + foreach ($this->listeners as $key => $listener) { + if ($events->detach($listener)) { + unset($this->listeners[$key]); + } + } + } +} diff --git a/src/Listener/ModuleResolverListener.php b/src/Listener/ModuleResolverListener.php new file mode 100644 index 0000000..3a18625 --- /dev/null +++ b/src/Listener/ModuleResolverListener.php @@ -0,0 +1,38 @@ +getModuleName(); + $class = $moduleName . '\Module'; + + if (!class_exists($class)) { + return false; + } + + $module = new $class; + return $module; + } +} diff --git a/src/Listener/OnBootstrapListener.php b/src/Listener/OnBootstrapListener.php new file mode 100644 index 0000000..4e96b33 --- /dev/null +++ b/src/Listener/OnBootstrapListener.php @@ -0,0 +1,44 @@ +getModule(); + if (!$module instanceof BootstrapListenerInterface + && !method_exists($module, 'onBootstrap') + ) { + return; + } + + $moduleManager = $e->getTarget(); + $events = $moduleManager->getEventManager(); + $sharedEvents = $events->getSharedManager(); + $sharedEvents->attach('Zend\Mvc\Application', 'bootstrap', array($module, 'onBootstrap')); + } +} diff --git a/src/Listener/ServiceListener.php b/src/Listener/ServiceListener.php new file mode 100644 index 0000000..d2529dd --- /dev/null +++ b/src/Listener/ServiceListener.php @@ -0,0 +1,264 @@ +defaultServiceManager = $serviceManager; + + if ($configuration !== null) { + $this->setDefaultServiceConfig($configuration); + } + } + + /** + * @param array $configuration + * @return ServiceListener + */ + public function setDefaultServiceConfig($configuration) + { + $this->defaultServiceConfig = $configuration; + + return $this; + } + + /** + * @param ServiceManager|string $serviceManager Service Manager instance or name + * @param string $key Configuration key + * @param string $moduleInterface FQCN as string + * @param string $method Method name + * @return ServiceListener + */ + public function addServiceManager($serviceManager, $key, $moduleInterface, $method) + { + if (is_string($serviceManager)) { + $smKey = $serviceManager; + } elseif ($serviceManager instanceof ServiceManager) { + $smKey = spl_object_hash($serviceManager); + } else { + throw new Exception\RuntimeException(sprintf( + 'Invalid service manager provided, expected ServiceManager or string, %s provided', + (string) $serviceManager + )); + } + + $this->serviceManagers[$smKey] = array( + 'service_manager' => $serviceManager, + 'config_key' => $key, + 'module_class_interface' => $moduleInterface, + 'module_class_method' => $method, + 'configuration' => array(), + ); + + if ($key === 'service_manager' && $this->defaultServiceConfig) { + $this->serviceManagers[$smKey]['configuration']['default_config'] = $this->defaultServiceConfig; + } + + return $this; + } + + /** + * @param EventManagerInterface $events + * @return ServiceListener + */ + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULE, array($this, 'onLoadModule')); + $this->listeners[] = $events->attach(ModuleEvent::EVENT_LOAD_MODULES_POST, array($this, 'onLoadModulesPost')); + return $this; + } + + /** + * @param EventManagerInterface $events + * @return void + */ + public function detach(EventManagerInterface $events) + { + foreach ($this->listeners as $key => $listener) { + if ($events->detach($listener)) { + unset($this->listeners[$key]); + } + } + } + + /** + * Retrieve service manager configuration from module, and + * configure the service manager. + * + * If the module does not implement a specific interface and does not + * implement a specific method, does nothing. Also, if the return value + * of that method is not a ServiceConfig object, or not an array or + * Traversable that can seed one, does nothing. + * + * The interface and method name can be set by adding a new service manager + * via the addServiceManager() method. + * + * @param ModuleEvent $e + * @return void + */ + public function onLoadModule(ModuleEvent $e) + { + $module = $e->getModule(); + + foreach ($this->serviceManagers as $key => $sm) { + + if (!$module instanceof $sm['module_class_interface'] + && !method_exists($module, $sm['module_class_method']) + ) { + continue; + } + + $config = $module->{$sm['module_class_method']}(); + + if ($config instanceof ServiceConfig) { + $config = $this->serviceConfigToArray($config); + } + + if ($config instanceof Traversable) { + $config = ArrayUtils::iteratorToArray($config); + } + + if (!is_array($config)) { + // If we don't have an array by this point, nothing left to do. + continue; + } + + // We're keeping track of which modules provided which configuration to which service managers. + // The actual merging takes place later. Doing it this way will enable us to provide more powerful + // debugging tools for showing which modules overrode what. + $fullname = $e->getModuleName() . '::' . $sm['module_class_method'] . '()'; + $this->serviceManagers[$key]['configuration'][$fullname] = $config; + } + } + + /** + * Use merged configuration to configure service manager + * + * If the merged configuration has a non-empty, array 'service_manager' + * key, it will be passed to a ServiceManager Config object, and + * used to configure the service manager. + * + * @param ModuleEvent $e + * @return void + */ + public function onLoadModulesPost(ModuleEvent $e) + { + $configListener = $e->getConfigListener(); + $config = $configListener->getMergedConfig(false); + + foreach ($this->serviceManagers as $key => $sm) { + if (isset($config[$sm['config_key']]) + && is_array($config[$sm['config_key']]) + && !empty($config[$sm['config_key']]) + ) { + $this->serviceManagers[$key]['configuration']['merged_config'] = $config[$sm['config_key']]; + } + + // Merge all of the things! + $smConfig = array(); + foreach ($this->serviceManagers[$key]['configuration'] as $configs) { + if (isset($configs['configuration_classes'])) { + foreach ($configs['configuration_classes'] as $class) { + $configs = ArrayUtils::merge($configs, $this->serviceConfigToArray($class)); + } + } + $smConfig = ArrayUtils::merge($smConfig, $configs); + } + + if (!$sm['service_manager'] instanceof ServiceManager) { + $instance = $this->defaultServiceManager->get($sm['service_manager']); + if (!$instance instanceof ServiceManager) { + throw new Exception\RuntimeException(sprintf( + 'Could not find a valid ServiceManager for %s', + $sm['service_manager'] + )); + } + $sm['service_manager'] = $instance; + } + $serviceConfig = new ServiceConfig($smConfig); + $serviceConfig->configureServiceManager($sm['service_manager']); + } + } + + /** + * Merge a service configuration container + * + * Extracts the various service configuration arrays, and then merges with + * the internal service configuration. + * + * @param ServiceConfig|string $config Instance of ServiceConfig or class name + * @return array + */ + protected function serviceConfigToArray($config) + { + if (is_string($config) && class_exists($config)) { + $class = $config; + $config = new $class; + } + + if (!$config instanceof ServiceConfig) { + throw new Exception\RuntimeException(sprintf( + 'Invalid service manager configuration class provided; received "%s", expected an instance of Zend\ServiceManager\Config', + $class + )); + } + + return array( + 'abstract_factories' => $config->getAbstractFactories(), + 'aliases' => $config->getAliases(), + 'initializers' => $config->getInitializers(), + 'factories' => $config->getFactories(), + 'invokables' => $config->getInvokables(), + 'services' => $config->getServices(), + 'shared' => $config->getShared(), + ); + } +} diff --git a/src/Listener/ServiceListenerInterface.php b/src/Listener/ServiceListenerInterface.php new file mode 100644 index 0000000..3ded20d --- /dev/null +++ b/src/Listener/ServiceListenerInterface.php @@ -0,0 +1,36 @@ +moduleName; + } + + /** + * Set the name of a given module + * + * @param string $moduleName + * @return ModuleEvent + */ + public function setModuleName($moduleName) + { + if (!is_string($moduleName)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string as an argument; %s provided' + ,__METHOD__, gettype($moduleName) + )); + } + // Performance tweak, don't add it as param. + $this->moduleName = $moduleName; + + return $this; + } + + /** + * Get module object + * + * @return null|object + */ + public function getModule() + { + return $this->module; + } + + /** + * Set module object to compose in this event + * + * @param object $module + * @return ModuleEvent + */ + public function setModule($module) + { + if (!is_object($module)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a module object as an argument; %s provided' + ,__METHOD__, gettype($module) + )); + } + // Performance tweak, don't add it as param. + $this->module = $module; + + return $this; + } + + /** + * Get the config listener + * + * @return null|Listener\ConfigMergerInterface + */ + public function getConfigListener() + { + return $this->configListener; + } + + /** + * Set module object to compose in this event + * + * @param Listener\ConfigMergerInterface $configListener + * @return ModuleEvent + */ + public function setConfigListener(Listener\ConfigMergerInterface $configListener) + { + $this->setParam('configListener', $configListener); + $this->configListener = $configListener; + + return $this; + } +} diff --git a/src/ModuleManager.php b/src/ModuleManager.php new file mode 100644 index 0000000..1b3ddf6 --- /dev/null +++ b/src/ModuleManager.php @@ -0,0 +1,278 @@ +setModules($modules); + if ($eventManager instanceof EventManagerInterface) { + $this->setEventManager($eventManager); + } + } + + public function onLoadModules() + { + if (true === $this->modulesAreLoaded) { + return $this; + } + + foreach ($this->getModules() as $moduleName) { + $this->loadModule($moduleName); + } + + $this->modulesAreLoaded = true; + } + + /** + * Load the provided modules. + * + * @triggers loadModules.pre + * @triggers loadModules.post + * @return ModuleManager + */ + public function loadModules() + { + if (true === $this->modulesAreLoaded) { + return $this; + } + + $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES, $this, $this->getEvent()); + + /** + * Having a dedicated .post event abstracts the complexity of priorities from the user. + * Users can attach to the .post event and be sure that important + * things like config merging are complete without having to worry if + * they set a low enough priority. + */ + $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES_POST, $this, $this->getEvent()); + + return $this; + } + + /** + * Load a specific module by name. + * + * @param string $moduleName + * @triggers loadModule.resolve + * @triggers loadModule + * @return mixed Module's Module class + */ + public function loadModule($moduleName) + { + if (isset($this->loadedModules[$moduleName])) { + return $this->loadedModules[$moduleName]; + } + + $event = ($this->loadFinished === false) ? clone $this->getEvent() : $this->getEvent(); + $event->setModuleName($moduleName); + + $this->loadFinished = false; + + $result = $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULE_RESOLVE, $this, $event, function ($r) { + return (is_object($r)); + }); + + $module = $result->last(); + + if (!is_object($module)) { + throw new Exception\RuntimeException(sprintf( + 'Module (%s) could not be initialized.', + $moduleName + )); + } + $event->setModule($module); + + $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULE, $this, $event); + $this->loadedModules[$moduleName] = $module; + + $this->loadFinished = true; + + return $module; + } + + /** + * Get an array of the loaded modules. + * + * @param bool $loadModules If true, load modules if they're not already + * @return array An array of Module objects, keyed by module name + */ + public function getLoadedModules($loadModules = false) + { + if (true === $loadModules) { + $this->loadModules(); + } + return $this->loadedModules; + } + + /** + * Get an instance of a module class by the module name + * + * @param string $moduleName + * @return mixed + */ + public function getModule($moduleName) + { + if (!isset($this->loadedModules[$moduleName])) { + return null; + } + return $this->loadedModules[$moduleName]; + } + + /** + * Get the array of module names that this manager should load. + * + * @return array + */ + public function getModules() + { + return $this->modules; + } + + /** + * Set an array or Traversable of module names that this module manager should load. + * + * @param mixed $modules array or Traversable of module names + * @return ModuleManager + */ + public function setModules($modules) + { + if (is_array($modules) || $modules instanceof Traversable) { + $this->modules = $modules; + } else { + throw new Exception\InvalidArgumentException(sprintf( + 'Parameter to %s\'s %s method must be an array or implement the \\Traversable interface', + __CLASS__, __METHOD__ + )); + } + return $this; + } + + /** + * Get the module event + * + * @return ModuleEvent + */ + public function getEvent() + { + if (!$this->event instanceof ModuleEvent) { + $this->setEvent(new ModuleEvent); + } + return $this->event; + } + + /** + * Set the module event + * + * @param ModuleEvent $event + * @return ModuleManager + */ + public function setEvent(ModuleEvent $event) + { + $this->event = $event; + return $this; + } + + /** + * Set the event manager instance used by this module manager. + * + * @param EventManagerInterface $events + * @return ModuleManager + */ + public function setEventManager(EventManagerInterface $events) + { + $events->setIdentifiers(array( + __CLASS__, + get_called_class(), + 'module_manager', + )); + $this->events = $events; + $this->attachDefaultListeners(); + return $this; + } + + /** + * Retrieve the event manager + * + * Lazy-loads an EventManager instance if none registered. + * + * @return EventManagerInterface + */ + public function getEventManager() + { + if (!$this->events instanceof EventManagerInterface) { + $this->setEventManager(new EventManager()); + } + return $this->events; + } + + /** + * Register the default event listeners + * + * @return ModuleManager + */ + protected function attachDefaultListeners() + { + $events = $this->getEventManager(); + $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModules')); + } +} diff --git a/src/ModuleManagerInterface.php b/src/ModuleManagerInterface.php new file mode 100644 index 0000000..ec3fe7b --- /dev/null +++ b/src/ModuleManagerInterface.php @@ -0,0 +1,61 @@ +loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $autoloader = new ModuleAutoloader(array( + dirname(__DIR__) . '/TestAsset', + )); + $autoloader->register(); + + $this->moduleManager = new ModuleManager(array()); + $this->moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + $this->moduleManager->getEventManager()->attach('loadModule', new AutoloaderListener, 2000); + } + + public function tearDown() + { + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testAutoloadersRegisteredByAutoloaderListener() + { + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('ListenerTestModule')); + $moduleManager->loadModules(); + $modules = $moduleManager->getLoadedModules(); + $this->assertTrue($modules['ListenerTestModule']->getAutoloaderConfigCalled); + $this->assertTrue(class_exists('Foo\Bar')); + } + + public function testAutoloadersRegisteredIfModuleDoesNotInheritAutoloaderProviderInterfaceButDefinesGetAutoloaderConfigMethod() + { + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('NotAutoloaderModule')); + $moduleManager->loadModules(); + $modules = $moduleManager->getLoadedModules(); + $this->assertTrue($modules['NotAutoloaderModule']->getAutoloaderConfigCalled); + $this->assertTrue(class_exists('Foo\Bar')); + } +} diff --git a/test/Listener/ConfigListenerTest.php b/test/Listener/ConfigListenerTest.php new file mode 100644 index 0000000..a36d308 --- /dev/null +++ b/test/Listener/ConfigListenerTest.php @@ -0,0 +1,386 @@ +tmpdir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'zend_module_cache_dir'; + @mkdir($this->tmpdir); + $this->configCache = $this->tmpdir . DIRECTORY_SEPARATOR . 'config.cache.php'; + // Store original autoloaders + $this->loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $autoloader = new ModuleAutoloader(array( + dirname(__DIR__) . '/TestAsset', + )); + $autoloader->register(); + + $this->moduleManager = new ModuleManager(array()); + $this->moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + } + + public function tearDown() + { + $file = glob($this->tmpdir . DIRECTORY_SEPARATOR . '*'); + @unlink($file[0]); // change this if there's ever > 1 file + @rmdir($this->tmpdir); + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testMultipleConfigsAreMerged() + { + $configListener = new ConfigListener; + + $moduleManager = $this->moduleManager; + $configListener->attach($moduleManager->getEventManager()); + $moduleManager->setModules(array('SomeModule', 'ListenerTestModule')); + $moduleManager->loadModules(); + + $config = $configListener->getMergedConfig(false); + $this->assertSame(2, count($config)); + $this->assertSame('test', $config['listener']); + $this->assertSame('thing', $config['some']); + $configObject = $configListener->getMergedConfig(); + $this->assertInstanceOf('Zend\Config\Config', $configObject); + } + + public function testCanCacheMergedConfig() + { + $options = new ListenerOptions(array( + 'cache_dir' => $this->tmpdir, + 'config_cache_enabled' => true, + )); + $configListener = new ConfigListener($options); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule', 'ListenerTestModule')); + $configListener->attach($moduleManager->getEventManager()); + $moduleManager->loadModules(); // This should cache the config + + $modules = $moduleManager->getLoadedModules(); + $this->assertTrue($modules['ListenerTestModule']->getConfigCalled); + + // Now we check to make sure it uses the config and doesn't hit + // the module objects getConfig() method(s) + $moduleManager = new ModuleManager(array('SomeModule', 'ListenerTestModule')); + $moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + $configListener = new ConfigListener($options); + $configListener->attach($moduleManager->getEventManager()); + $moduleManager->loadModules(); + $modules = $moduleManager->getLoadedModules(); + $this->assertFalse($modules['ListenerTestModule']->getConfigCalled); + } + + public function testBadConfigValueThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + + $configListener = new ConfigListener; + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('BadConfigModule', 'SomeModule')); + $configListener->attach($moduleManager->getEventManager()); + $moduleManager->loadModules(); + } + + public function testBadGlobPathTrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $configListener = new ConfigListener; + $configListener->addConfigGlobPath(array('asd')); + } + + public function testBadGlobPathArrayTrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $configListener = new ConfigListener; + $configListener->addConfigGlobPaths('asd'); + } + + public function testBadStaticPathArrayTrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $configListener = new ConfigListener; + $configListener->addConfigStaticPaths('asd'); + } + + public function testCanMergeConfigFromGlob() + { + $configListener = new ConfigListener; + $configListener->addConfigGlobPath(__DIR__ . '/_files/good/*.{ini,php,xml}'); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $configListener->attach($moduleManager->getEventManager()); + + $moduleManager->loadModules(); + $configObjectCheck = $configListener->getMergedConfig(); + + // Test as object + $configObject = $configListener->getMergedConfig(); + $this->assertSame(spl_object_hash($configObjectCheck), spl_object_hash($configObject)); + $this->assertSame('loaded', $configObject->ini); + $this->assertSame('loaded', $configObject->php); + $this->assertSame('loaded', $configObject->xml); + // Test as array + $config = $configListener->getMergedConfig(false); + $this->assertSame('loaded', $config['ini']); + $this->assertSame('loaded', $config['php']); + $this->assertSame('loaded', $config['xml']); + } + + public function testCanMergeConfigFromStaticPath() + { + $configListener = new ConfigListener; + $configListener->addConfigStaticPath(__DIR__ . '/_files/good/config.ini'); + $configListener->addConfigStaticPath(__DIR__ . '/_files/good/config.php'); + $configListener->addConfigStaticPath(__DIR__ . '/_files/good/config.xml'); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $moduleManager->getEventManager()->attachAggregate($configListener); + + $moduleManager->loadModules(); + $configObjectCheck = $configListener->getMergedConfig(); + + // Test as object + $configObject = $configListener->getMergedConfig(); + $this->assertSame(spl_object_hash($configObjectCheck), spl_object_hash($configObject)); + $this->assertSame('loaded', $configObject->ini); + $this->assertSame('loaded', $configObject->php); + $this->assertSame('loaded', $configObject->xml); + // Test as array + $config = $configListener->getMergedConfig(false); + $this->assertSame('loaded', $config['ini']); + $this->assertSame('loaded', $config['php']); + $this->assertSame('loaded', $config['xml']); + } + + public function testCanMergeConfigFromStaticPaths() + { + $configListener = new ConfigListener; + $configListener->addConfigStaticPaths(array( + __DIR__ . '/_files/good/config.ini', + __DIR__ . '/_files/good/config.php', + __DIR__ . '/_files/good/config.xml') + ); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $moduleManager->getEventManager()->attachAggregate($configListener); + + $moduleManager->loadModules(); + $configObjectCheck = $configListener->getMergedConfig(); + + // Test as object + $configObject = $configListener->getMergedConfig(); + $this->assertSame(spl_object_hash($configObjectCheck), spl_object_hash($configObject)); + $this->assertSame('loaded', $configObject->ini); + $this->assertSame('loaded', $configObject->php); + $this->assertSame('loaded', $configObject->xml); + // Test as array + $config = $configListener->getMergedConfig(false); + $this->assertSame('loaded', $config['ini']); + $this->assertSame('loaded', $config['php']); + $this->assertSame('loaded', $config['xml']); + } + + public function testCanCacheMergedConfigFromGlob() + { + $options = new ListenerOptions(array( + 'cache_dir' => $this->tmpdir, + 'config_cache_enabled' => true, + )); + $configListener = new ConfigListener($options); + $configListener->addConfigGlobPath(__DIR__ . '/_files/good/*.{ini,php,xml}'); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $moduleManager->getEventManager()->attachAggregate($configListener); + + $moduleManager->loadModules(); + $configObjectFromGlob = $configListener->getMergedConfig(); + + // This time, don't add the glob path + $configListener = new ConfigListener($options); + $moduleManager = new ModuleManager(array('SomeModule')); + $moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + + $moduleManager->getEventManager()->attachAggregate($configListener); + + $moduleManager->loadModules(); + + // Check if values from glob object and cache object are the same + $configObjectFromCache = $configListener->getMergedConfig(); + $this->assertNotNull($configObjectFromGlob->ini); + $this->assertSame($configObjectFromGlob->ini, $configObjectFromCache->ini); + $this->assertNotNull($configObjectFromGlob->php); + $this->assertSame($configObjectFromGlob->php, $configObjectFromCache->php); + $this->assertNotNull($configObjectFromGlob->xml); + $this->assertSame($configObjectFromGlob->xml, $configObjectFromCache->xml); + } + + public function testCanCacheMergedConfigFromStatic() + { + $options = new ListenerOptions(array( + 'cache_dir' => $this->tmpdir, + 'config_cache_enabled' => true, + )); + $configListener = new ConfigListener($options); + $configListener->addConfigStaticPaths(array( + __DIR__ . '/_files/good/config.ini', + __DIR__ . '/_files/good/config.php', + __DIR__ . '/_files/good/config.xml') + ); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $moduleManager->getEventManager()->attachAggregate($configListener); + + $moduleManager->loadModules(); + $configObjectFromGlob = $configListener->getMergedConfig(); + + // This time, don't add the glob path + $configListener = new ConfigListener($options); + $moduleManager = new ModuleManager(array('SomeModule')); + $moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + + $moduleManager->getEventManager()->attachAggregate($configListener); + + $moduleManager->loadModules(); + + // Check if values from glob object and cache object are the same + $configObjectFromCache = $configListener->getMergedConfig(); + $this->assertNotNull($configObjectFromGlob->ini); + $this->assertSame($configObjectFromGlob->ini, $configObjectFromCache->ini); + $this->assertNotNull($configObjectFromGlob->php); + $this->assertSame($configObjectFromGlob->php, $configObjectFromCache->php); + $this->assertNotNull($configObjectFromGlob->xml); + $this->assertSame($configObjectFromGlob->xml, $configObjectFromCache->xml); + } + + public function testCanMergeConfigFromArrayOfGlobs() + { + $configListener = new ConfigListener; + $configListener->addConfigGlobPaths(new ArrayObject(array( + __DIR__ . '/_files/good/*.ini', + __DIR__ . '/_files/good/*.php', + __DIR__ . '/_files/good/*.xml', + ))); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $moduleManager->getEventManager()->attachAggregate($configListener); + $moduleManager->loadModules(); + + // Test as object + $configObject = $configListener->getMergedConfig(); + $this->assertSame('loaded', $configObject->ini); + $this->assertSame('loaded', $configObject->php); + $this->assertSame('loaded', $configObject->xml); + } + + public function testCanMergeConfigFromArrayOfStatic() + { + $configListener = new ConfigListener; + $configListener->addConfigStaticPaths(new ArrayObject(array( + __DIR__ . '/_files/good/config.ini', + __DIR__ . '/_files/good/config.php', + __DIR__ . '/_files/good/config.xml', + ))); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $moduleManager->getEventManager()->attachAggregate($configListener); + $moduleManager->loadModules(); + + // Test as object + $configObject = $configListener->getMergedConfig(); + $this->assertSame('loaded', $configObject->ini); + $this->assertSame('loaded', $configObject->php); + $this->assertSame('loaded', $configObject->xml); + } + + public function testMergesWithMergeAndReplaceBehavior() + { + $configListener = new ConfigListener(); + + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('SomeModule')); + + $configListener->addConfigStaticPaths(array( + __DIR__ . '/_files/good/merge1.php', + __DIR__ . '/_files/good/merge2.php', + )); + + $moduleManager->getEventManager()->attachAggregate($configListener); + $moduleManager->loadModules(); + + $mergedConfig = $configListener->getMergedConfig(false); + $this->assertSame(array('foo', 'bar'), $mergedConfig['indexed']); + $this->assertSame('bar', $mergedConfig['keyed']); + } + + public function testConfigListenerFunctionsAsAggregateListener() + { + $configListener = new ConfigListener; + + $moduleManager = $this->moduleManager; + $this->assertEquals(2, count($moduleManager->getEventManager()->getEvents())); + + $configListener->attach($moduleManager->getEventManager()); + $this->assertEquals(3, count($moduleManager->getEventManager()->getEvents())); + + $configListener->detach($moduleManager->getEventManager()); + $this->assertEquals(2, count($moduleManager->getEventManager()->getEvents())); + } +} diff --git a/test/Listener/DefaultListenerAggregateTest.php b/test/Listener/DefaultListenerAggregateTest.php new file mode 100644 index 0000000..eb90158 --- /dev/null +++ b/test/Listener/DefaultListenerAggregateTest.php @@ -0,0 +1,118 @@ +loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $this->defaultListeners = new DefaultListenerAggregate( + new ListenerOptions(array( + 'module_paths' => array( + realpath(__DIR__ . '/TestAsset'), + ), + )) + ); + } + + public function tearDown() + { + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testDefaultListenerAggregateCanAttachItself() + { + $moduleManager = new ModuleManager(array('ListenerTestModule')); + $moduleManager->getEventManager()->attachAggregate(new DefaultListenerAggregate); + + $events = $moduleManager->getEventManager()->getEvents(); + $expectedEvents = array( + 'loadModules' => array( + 'Zend\Loader\ModuleAutoloader', + 'config-pre' => 'Zend\ModuleManager\Listener\ConfigListener', + 'config-post' => 'Zend\ModuleManager\Listener\ConfigListener', + 'Zend\ModuleManager\Listener\LocatorRegistrationListener', + 'Zend\ModuleManager\ModuleManager', + ), + 'loadModule.resolve' => array( + 'Zend\ModuleManager\Listener\ModuleResolverListener', + ), + 'loadModule' => array( + 'Zend\ModuleManager\Listener\AutoloaderListener', + 'Zend\ModuleManager\Listener\InitTrigger', + 'Zend\ModuleManager\Listener\OnBootstrapListener', + 'Zend\ModuleManager\Listener\ConfigListener', + 'Zend\ModuleManager\Listener\LocatorRegistrationListener', + ), + ); + foreach ($expectedEvents as $event => $expectedListeners) { + $this->assertContains($event, $events); + $listeners = $moduleManager->getEventManager()->getListeners($event); + $this->assertSame(count($expectedListeners), count($listeners)); + foreach ($listeners as $listener) { + $callback = $listener->getCallback(); + if (is_array($callback)) { + $callback = $callback[0]; + } + $listenerClass = get_class($callback); + $this->assertContains($listenerClass, $expectedListeners); + } + } + } + + public function testDefaultListenerAggregateCanDetachItself() + { + $listenerAggregate = new DefaultListenerAggregate; + $moduleManager = new ModuleManager(array('ListenerTestModule')); + + $this->assertEquals(1, count($moduleManager->getEventManager()->getEvents())); + + $listenerAggregate->attach($moduleManager->getEventManager()); + $this->assertEquals(3, count($moduleManager->getEventManager()->getEvents())); + + $listenerAggregate->detach($moduleManager->getEventManager()); + $this->assertEquals(1, count($moduleManager->getEventManager()->getEvents())); + } +} diff --git a/test/Listener/InitTriggerTest.php b/test/Listener/InitTriggerTest.php new file mode 100644 index 0000000..df2992f --- /dev/null +++ b/test/Listener/InitTriggerTest.php @@ -0,0 +1,72 @@ +loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $autoloader = new ModuleAutoloader(array( + dirname(__DIR__) . '/TestAsset', + )); + $autoloader->register(); + + $this->moduleManager = new ModuleManager(array()); + $this->moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + $this->moduleManager->getEventManager()->attach('loadModule', new InitTrigger, 2000); + } + + public function tearDown() + { + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testInitMethodCalledByInitTriggerListener() + { + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('ListenerTestModule')); + $moduleManager->loadModules(); + $modules = $moduleManager->getLoadedModules(); + $this->assertTrue($modules['ListenerTestModule']->initCalled); + } +} diff --git a/test/Listener/ListenerOptionsTest.php b/test/Listener/ListenerOptionsTest.php new file mode 100644 index 0000000..3727786 --- /dev/null +++ b/test/Listener/ListenerOptionsTest.php @@ -0,0 +1,96 @@ + __DIR__, + 'config_cache_enabled' => true, + 'config_cache_key' => 'foo', + 'module_paths' => array('module','paths'), + 'config_glob_paths' => array('glob','paths'), + 'config_static_paths' => array('static','custom_paths'), + )); + $this->assertSame($options->getCacheDir(), __DIR__); + $this->assertTrue($options->getConfigCacheEnabled()); + $this->assertNotNull(strstr($options->getConfigCacheFile(), __DIR__)); + $this->assertNotNull(strstr($options->getConfigCacheFile(), '.php')); + $this->assertSame('foo', $options->getConfigCacheKey()); + $this->assertSame(array('module', 'paths'), $options->getModulePaths()); + $this->assertSame(array('glob', 'paths'), $options->getConfigGlobPaths()); + $this->assertSame(array('static', 'custom_paths'), $options->getConfigStaticPaths()); + } + + public function testCanAccessKeysAsProperties() + { + $options = new ListenerOptions(array( + 'cache_dir' => __DIR__, + 'config_cache_enabled' => true, + 'config_cache_key' => 'foo', + 'module_paths' => array('module','paths'), + 'config_glob_paths' => array('glob','paths'), + 'config_static_paths' => array('static','custom_paths'), + )); + $this->assertSame($options->cache_dir, __DIR__); + $options->cache_dir = 'foo'; + $this->assertSame($options->cache_dir, 'foo'); + $this->assertTrue(isset($options->cache_dir)); + unset($options->cache_dir); + $this->assertFalse(isset($options->cache_dir)); + + $this->assertTrue($options->config_cache_enabled); + $options->config_cache_enabled = false; + $this->assertFalse($options->config_cache_enabled); + $this->assertEquals('foo', $options->config_cache_key); + $this->assertSame(array('module', 'paths'), $options->module_paths); + $this->assertSame(array('glob', 'paths'), $options->config_glob_paths); + $this->assertSame(array('static', 'custom_paths'), $options->config_static_paths); + } + + public function testSetModulePathsAcceptsConfigOrTraverable() + { + $config = new Config(array(__DIR__)); + $options = new ListenerOptions; + $options->setModulePaths($config); + $this->assertSame($config, $options->getModulePaths()); + } + + public function testSetModulePathsThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $options = new ListenerOptions; + $options->setModulePaths('asd'); + } + + public function testSetConfigGlobPathsAcceptsConfigOrTraverable() + { + $config = new Config(array(__DIR__)); + $options = new ListenerOptions; + $options->setConfigGlobPaths($config); + $this->assertSame($config, $options->getConfigGlobPaths()); + } + + public function testSetConfigGlobPathsThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $options = new ListenerOptions; + $options->setConfigGlobPaths('asd'); + } +} diff --git a/test/Listener/LocatorRegistrationListenerTest.php b/test/Listener/LocatorRegistrationListenerTest.php new file mode 100644 index 0000000..78f6c01 --- /dev/null +++ b/test/Listener/LocatorRegistrationListenerTest.php @@ -0,0 +1,125 @@ +loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $autoloader = new ModuleAutoloader(array( + dirname(__DIR__) . '/TestAsset', + )); + $autoloader->register(); + + $this->sharedEvents = new SharedEventManager(); + + $this->moduleManager = new ModuleManager(array('ListenerTestModule')); + $this->moduleManager->getEventManager()->setSharedManager($this->sharedEvents); + $this->moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + + $this->application = new MockApplication; + $events = new EventManager(array('Zend\Mvc\Application', 'ZendTest\Module\TestAsset\MockApplication', 'application')); + $events->setSharedManager($this->sharedEvents); + $this->application->setEventManager($events); + + $this->serviceManager = new ServiceManager(); + $this->serviceManager->setService('ModuleManager', $this->moduleManager); + $this->application->setServiceManager($this->serviceManager); + } + + public function tearDown() + { + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testModuleClassIsRegisteredWithDiAndInjectedWithSharedInstances() + { + $locator = $this->serviceManager; + $locator->setFactory('Foo\Bar', function($s) { + $module = $s->get('ListenerTestModule\Module'); + $manager = $s->get('Zend\ModuleManager\ModuleManager'); + $instance = new \Foo\Bar($module, $manager); + return $instance; + }); + + $locatorRegistrationListener = new LocatorRegistrationListener; + $this->moduleManager->getEventManager()->attachAggregate($locatorRegistrationListener); + $test = $this; + $this->moduleManager->getEventManager()->attach('loadModule', function ($e) use ($test) { + $test->module = $e->getModule(); + }, -1000); + $this->moduleManager->loadModules(); + + $this->application->bootstrap(); + $sharedInstance1 = $locator->get('ListenerTestModule\Module'); + $sharedInstance2 = $locator->get('Zend\ModuleManager\ModuleManager'); + + $this->assertInstanceOf('ListenerTestModule\Module', $sharedInstance1); + $foo = false; + $message = ''; + try { + $foo = $locator->get('Foo\Bar'); + } catch (\Exception $e) { + $message = $e->getMessage(); + while ($e = $e->getPrevious()) { + $message .= "\n" . $e->getMessage(); + } + } + if (!$foo) { + $this->fail($message); + } + $this->assertSame($this->module, $foo->module); + + $this->assertInstanceOf('Zend\ModuleManager\ModuleManager', $sharedInstance2); + $this->assertSame($this->moduleManager, $locator->get('Foo\Bar')->moduleManager); + } +} diff --git a/test/Listener/ModuleResolverListenerTest.php b/test/Listener/ModuleResolverListenerTest.php new file mode 100644 index 0000000..3cb24f8 --- /dev/null +++ b/test/Listener/ModuleResolverListenerTest.php @@ -0,0 +1,70 @@ +loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $autoloader = new ModuleAutoloader(array( + dirname(__DIR__) . '/TestAsset', + )); + $autoloader->register(); + } + + public function tearDown() + { + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testModuleResolverListenerCanResolveModuleClasses() + { + $moduleResolver = new ModuleResolverListener; + $e = new ModuleEvent; + + $e->setModuleName('ListenerTestModule'); + $this->assertInstanceOf('ListenerTestModule\Module', $moduleResolver($e)); + + $e->setModuleName('DoesNotExist'); + $this->assertFalse($moduleResolver($e)); + } +} diff --git a/test/Listener/OnBootstrapListenerTest.php b/test/Listener/OnBootstrapListenerTest.php new file mode 100644 index 0000000..2f26c2b --- /dev/null +++ b/test/Listener/OnBootstrapListenerTest.php @@ -0,0 +1,87 @@ +loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $autoloader = new ModuleAutoloader(array( + dirname(__DIR__) . '/TestAsset', + )); + $autoloader->register(); + + $sharedEvents = new SharedEventManager(); + $this->moduleManager = new ModuleManager(array()); + $this->moduleManager->getEventManager()->setSharedManager($sharedEvents); + $this->moduleManager->getEventManager()->attach('loadModule.resolve', new ModuleResolverListener, 1000); + $this->moduleManager->getEventManager()->attach('loadModule', new OnBootstrapListener, 1000); + + $this->application = new MockApplication; + $events = new EventManager(array('Zend\Mvc\Application', 'ZendTest\Module\TestAsset\MockApplication', 'application')); + $events->setSharedManager($sharedEvents); + $this->application->setEventManager($events); + } + + public function tearDown() + { + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testOnBootstrapMethodCalledByOnBootstrapListener() + { + $moduleManager = $this->moduleManager; + $moduleManager->setModules(array('ListenerTestModule')); + $moduleManager->loadModules(); + $this->application->bootstrap(); + $modules = $moduleManager->getLoadedModules(); + $this->assertTrue($modules['ListenerTestModule']->onBootstrapCalled); + } +} diff --git a/test/Listener/ServiceListenerTest.php b/test/Listener/ServiceListenerTest.php new file mode 100644 index 0000000..ba752b9 --- /dev/null +++ b/test/Listener/ServiceListenerTest.php @@ -0,0 +1,156 @@ +services = new ServiceManager(); + $this->listener = new ServiceListener($this->services); + $this->listener->addServiceManager($this->services, 'service_manager', 'Zend\ModuleManager\Feature\ServiceProviderInterface', 'getServiceConfig'); + $this->event = new ModuleEvent(); + $this->configListener = new ConfigListener(); + $this->event->setConfigListener($this->configListener); + } + + public function testPassingInvalidModuleDoesNothing() + { + $module = new stdClass(); + $this->event->setModule($module); + $this->listener->onLoadModule($this->event); + + foreach ($this->serviceManagerProps as $prop) { + $this->assertAttributeEquals(array(), $prop, $this->services); + } + } + + public function testInvalidReturnFromModuleDoesNothing() + { + $module = new TestAsset\ServiceInvalidReturnModule(); + $this->event->setModule($module); + $this->listener->onLoadModule($this->event); + + foreach ($this->serviceManagerProps as $prop) { + $this->assertAttributeEquals(array(), $prop, $this->services); + } + } + + public function getServiceConfig() + { + return array( + 'invokables' => array(__CLASS__ => __CLASS__), + 'factories' => array( + 'foo' => function($sm) { }, + ), + 'abstract_factories' => array( + new \Zend\ServiceManager\Di\DiAbstractServiceFactory(new \Zend\Di\Di()), + ), + 'shared' => array( + 'foo' => false, + 'zendtestmodulemanagerlistenerservicelistenertest' => true, + ), + 'aliases' => array( + 'bar' => 'foo', + ), + ); + } + + public function assertServiceManagerIsConfigured() + { + $this->listener->onLoadModulesPost($this->event); + foreach ($this->getServiceConfig() as $prop => $expected) { + if ($prop == 'invokables') { + $prop = 'invokableClasses'; + foreach ($expected as $key => $value) { + $normalized = strtolower($key); + $normalized = str_replace(array('\\', '_'), '', $normalized); + unset($expected[$key]); + $expected[$normalized] = $value; + } + } + if ($prop == 'abstract_factories') { + $prop = 'abstractFactories'; + } + $this->assertAttributeEquals($expected, $prop, $this->services, "$prop assertion failed"); + } + } + + public function testModuleReturningArrayConfiguresServiceManager() + { + $config = $this->getServiceConfig(); + $module = new TestAsset\ServiceProviderModule($config); + $this->event->setModule($module); + $this->listener->onLoadModule($this->event); + $this->assertServiceManagerIsConfigured(); + } + + public function testModuleReturningTraversableConfiguresServiceManager() + { + $config = $this->getServiceConfig(); + $config = new ArrayObject($config); + $module = new TestAsset\ServiceProviderModule($config); + $this->event->setModule($module); + $this->listener->onLoadModule($this->event); + $this->assertServiceManagerIsConfigured(); + } + + public function testModuleServiceConfigOverridesGlobalConfig() + { + $this->listener = new ServiceListener($this->services, array('aliases' => array('foo' => 'bar'))); + $this->listener->addServiceManager($this->services, 'service_manager', 'Zend\ModuleManager\Feature\ServiceProviderInterface', 'getServiceConfig'); + $config = array('aliases' => array('foo' => 'baz')); + $module = new TestAsset\ServiceProviderModule($config); + $this->event->setModule($module); + $this->listener->onLoadModule($this->event); + $this->listener->onLoadModulesPost($this->event); + $this->assertAttributeEquals($config['aliases'], 'aliases', $this->services, "aliases assertion failed - module config did not override main config"); + } + + public function testModuleReturningServiceConfigConfiguresServiceManager() + { + $config = $this->getServiceConfig(); + $config = new ServiceConfig($config); + $module = new TestAsset\ServiceProviderModule($config); + $this->event->setModule($module); + $this->listener->onLoadModule($this->event); + $this->assertServiceManagerIsConfigured(); + } + + public function testMergedConfigContainingServiceManagerKeyWillConfigureServiceManagerPostLoadModules() + { + $config = array('service_manager' => $this->getServiceConfig()); + $configListener = new ConfigListener(); + $configListener->setMergedConfig($config); + $this->event->setConfigListener($configListener); + $this->assertServiceManagerIsConfigured(); + } +} diff --git a/test/Listener/TestAsset/ServiceInvalidReturnModule.php b/test/Listener/TestAsset/ServiceInvalidReturnModule.php new file mode 100644 index 0000000..3fc49af --- /dev/null +++ b/test/Listener/TestAsset/ServiceInvalidReturnModule.php @@ -0,0 +1,26 @@ +config = $config; + } + + public function getServiceConfig() + { + return $this->config; + } +} diff --git a/test/Listener/_files/bad/config.badext b/test/Listener/_files/bad/config.badext new file mode 100644 index 0000000..e69de29 diff --git a/test/Listener/_files/bad/config.php b/test/Listener/_files/bad/config.php new file mode 100644 index 0000000..fc3f376 --- /dev/null +++ b/test/Listener/_files/bad/config.php @@ -0,0 +1,3 @@ + 'loaded', +); diff --git a/test/Listener/_files/good/config.xml b/test/Listener/_files/good/config.xml new file mode 100644 index 0000000..fbdf865 --- /dev/null +++ b/test/Listener/_files/good/config.xml @@ -0,0 +1,4 @@ + + + loaded + diff --git a/test/Listener/_files/good/config.yml b/test/Listener/_files/good/config.yml new file mode 100644 index 0000000..d2da9b6 --- /dev/null +++ b/test/Listener/_files/good/config.yml @@ -0,0 +1 @@ +yml: loaded diff --git a/test/Listener/_files/good/merge1.php b/test/Listener/_files/good/merge1.php new file mode 100644 index 0000000..70bc649 --- /dev/null +++ b/test/Listener/_files/good/merge1.php @@ -0,0 +1,5 @@ + array('foo'), + 'keyed' => 'foo', +); diff --git a/test/Listener/_files/good/merge2.php b/test/Listener/_files/good/merge2.php new file mode 100644 index 0000000..2d032ca --- /dev/null +++ b/test/Listener/_files/good/merge2.php @@ -0,0 +1,5 @@ + array('bar'), + 'keyed' => 'bar', +); diff --git a/test/ModuleEventTest.php b/test/ModuleEventTest.php new file mode 100644 index 0000000..6e4470f --- /dev/null +++ b/test/ModuleEventTest.php @@ -0,0 +1,68 @@ +event = new ModuleEvent(); + } + + public function testCanRetrieveModuleViaGetter() + { + $module = new stdClass; + $this->event->setModule($module); + $test = $this->event->getModule(); + $this->assertSame($module, $test); + } + + public function testPassingNonObjectToSetModuleRaisesException() + { + $this->setExpectedException('Zend\ModuleManager\Exception\InvalidArgumentException'); + $this->event->setModule('foo'); + } + + public function testCanRetrieveModuleNameViaGetter() + { + $moduleName = 'MyModule'; + $this->event->setModuleName($moduleName); + $test = $this->event->getModuleName(); + $this->assertSame($moduleName, $test); + } + + public function testPassingNonStringToSetModuleNameRaisesException() + { + $this->setExpectedException('Zend\ModuleManager\Exception\InvalidArgumentException'); + $this->event->setModuleName(new StdClass); + } + + public function testSettingConfigListenerProxiesToParameters() + { + $configListener = new ConfigListener; + $this->event->setConfigListener($configListener); + $test = $this->event->getParam('configListener'); + $this->assertSame($configListener, $test); + } + + public function testCanRetrieveConfigListenerViaGetter() + { + $configListener = new ConfigListener; + $this->event->setConfigListener($configListener); + $test = $this->event->getConfigListener(); + $this->assertSame($configListener, $test); + } +} diff --git a/test/ModuleManagerTest.php b/test/ModuleManagerTest.php new file mode 100644 index 0000000..e4cfc01 --- /dev/null +++ b/test/ModuleManagerTest.php @@ -0,0 +1,146 @@ +tmpdir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'zend_module_cache_dir'; + @mkdir($this->tmpdir); + $this->configCache = $this->tmpdir . DIRECTORY_SEPARATOR . 'config.cache.php'; + // Store original autoloaders + $this->loaders = spl_autoload_functions(); + if (!is_array($this->loaders)) { + // spl_autoload_functions does not return empty array when no + // autoloaders registered... + $this->loaders = array(); + } + + // Store original include_path + $this->includePath = get_include_path(); + + $this->defaultListeners = new DefaultListenerAggregate( + new ListenerOptions(array( + 'module_paths' => array( + realpath(__DIR__ . '/TestAsset'), + ), + )) + ); + } + + public function tearDown() + { + $file = glob($this->tmpdir . DIRECTORY_SEPARATOR . '*'); + @unlink($file[0]); // change this if there's ever > 1 file + @rmdir($this->tmpdir); + // Restore original autoloaders + AutoloaderFactory::unregisterAutoloaders(); + $loaders = spl_autoload_functions(); + if (is_array($loaders)) { + foreach ($loaders as $loader) { + spl_autoload_unregister($loader); + } + } + + foreach ($this->loaders as $loader) { + spl_autoload_register($loader); + } + + // Restore original include_path + set_include_path($this->includePath); + } + + public function testEventManagerIdentifiers() + { + $moduleManager = new ModuleManager(array()); + $identifiers = $moduleManager->getEventManager()->getIdentifiers(); + $expected = array('Zend\ModuleManager\ModuleManager', 'module_manager'); + $this->assertEquals($expected, array_values($identifiers)); + } + + public function testCanLoadSomeModule() + { + $configListener = $this->defaultListeners->getConfigListener(); + $moduleManager = new ModuleManager(array('SomeModule'), new EventManager); + $moduleManager->getEventManager()->attachAggregate($this->defaultListeners); + $moduleManager->loadModules(); + $loadedModules = $moduleManager->getLoadedModules(); + $this->assertInstanceOf('SomeModule\Module', $loadedModules['SomeModule']); + $config = $configListener->getMergedConfig(); + $this->assertSame($config->some, 'thing'); + } + + public function testCanLoadMultipleModules() + { + $configListener = $this->defaultListeners->getConfigListener(); + $moduleManager = new ModuleManager(array('BarModule', 'BazModule')); + $moduleManager->getEventManager()->attachAggregate($this->defaultListeners); + $moduleManager->loadModules(); + $loadedModules = $moduleManager->getLoadedModules(); + $this->assertInstanceOf('BarModule\Module', $loadedModules['BarModule']); + $this->assertInstanceOf('BazModule\Module', $loadedModules['BazModule']); + $this->assertInstanceOf('BarModule\Module', $moduleManager->getModule('BarModule')); + $this->assertInstanceOf('BazModule\Module', $moduleManager->getModule('BazModule')); + $this->assertNull($moduleManager->getModule('NotLoaded')); + $config = $configListener->getMergedConfig(); + $this->assertSame('foo', $config->bar); + $this->assertSame('bar', $config->baz); + } + + public function testModuleLoadingBehavior() + { + $moduleManager = new ModuleManager(array('BarModule')); + $moduleManager->getEventManager()->attachAggregate($this->defaultListeners); + $modules = $moduleManager->getLoadedModules(); + $this->assertSame(0, count($modules)); + $modules = $moduleManager->getLoadedModules(true); + $this->assertSame(1, count($modules)); + $moduleManager->loadModules(); // should not cause any problems + $moduleManager->loadModule('BarModule'); // should not cause any problems + $modules = $moduleManager->getLoadedModules(true); // BarModule already loaded so nothing happens + $this->assertSame(1, count($modules)); + } + + public function testConstructorThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $moduleManager = new ModuleManager('stringShouldBeArray'); + } + + public function testNotFoundModuleThrowsRuntimeException() + { + $this->setExpectedException('RuntimeException'); + $moduleManager = new ModuleManager(array('NotFoundModule')); + $moduleManager->loadModules(); + } + + public function testCanLoadModuleDuringTheLoadModuleEvent() + { + $configListener = $this->defaultListeners->getConfigListener(); + $moduleManager = new ModuleManager(array('LoadOtherModule', 'BarModule')); + $moduleManager->getEventManager()->attachAggregate($this->defaultListeners); + $moduleManager->loadModules(); + + $config = $configListener->getMergedConfig(); + $this->assertTrue(isset($config['loaded'])); + $this->assertSame('oh, yeah baby!', $config['loaded']); + } +} diff --git a/test/TestAsset/BadConfigModule/Module.php b/test/TestAsset/BadConfigModule/Module.php new file mode 100644 index 0000000..c6936a9 --- /dev/null +++ b/test/TestAsset/BadConfigModule/Module.php @@ -0,0 +1,19 @@ + 'foo', +); diff --git a/test/TestAsset/BamModule/Module.php b/test/TestAsset/BamModule/Module.php new file mode 100644 index 0000000..d19da5f --- /dev/null +++ b/test/TestAsset/BamModule/Module.php @@ -0,0 +1,21 @@ + 'foo', +); diff --git a/test/TestAsset/BarModule/Module.php b/test/TestAsset/BarModule/Module.php new file mode 100644 index 0000000..429ef6e --- /dev/null +++ b/test/TestAsset/BarModule/Module.php @@ -0,0 +1,21 @@ + 'foo', +); diff --git a/test/TestAsset/BazModule/Module.php b/test/TestAsset/BazModule/Module.php new file mode 100644 index 0000000..25e8352 --- /dev/null +++ b/test/TestAsset/BazModule/Module.php @@ -0,0 +1,21 @@ + 'bar', +); diff --git a/test/TestAsset/BooModule/Module.php b/test/TestAsset/BooModule/Module.php new file mode 100644 index 0000000..49e7f03 --- /dev/null +++ b/test/TestAsset/BooModule/Module.php @@ -0,0 +1,21 @@ + 'foo', +); diff --git a/test/TestAsset/BorModule/Module.php b/test/TestAsset/BorModule/Module.php new file mode 100644 index 0000000..31d8e4a --- /dev/null +++ b/test/TestAsset/BorModule/Module.php @@ -0,0 +1,21 @@ + 'foo', +); diff --git a/test/TestAsset/ListenerTestModule/Module.php b/test/TestAsset/ListenerTestModule/Module.php new file mode 100644 index 0000000..ffb0e96 --- /dev/null +++ b/test/TestAsset/ListenerTestModule/Module.php @@ -0,0 +1,62 @@ +initCalled = true; + } + + public function getConfig() + { + $this->getConfigCalled = true; + return array( + 'listener' => 'test' + ); + } + + public function getAutoloaderConfig() + { + $this->getAutoloaderConfigCalled = true; + return array( + 'Zend\Loader\StandardAutoloader' => array( + 'namespaces' => array( + 'Foo' => __DIR__ . '/src/Foo', + ), + ), + ); + } + + public function onBootstrap(EventInterface $e) + { + $this->onBootstrapCalled = true; + } +} diff --git a/test/TestAsset/ListenerTestModule/src/Foo/Bar.php b/test/TestAsset/ListenerTestModule/src/Foo/Bar.php new file mode 100644 index 0000000..acc24eb --- /dev/null +++ b/test/TestAsset/ListenerTestModule/src/Foo/Bar.php @@ -0,0 +1,26 @@ +module = $module; + $this->moduleManager = $moduleManager; + } +} diff --git a/test/TestAsset/LoadOtherModule/Module.php b/test/TestAsset/LoadOtherModule/Module.php new file mode 100644 index 0000000..bc5c447 --- /dev/null +++ b/test/TestAsset/LoadOtherModule/Module.php @@ -0,0 +1,26 @@ +loadModule('BarModule'); + } + + public function getConfig() + { + return array('loaded' => 'oh, yeah baby!'); + } +} diff --git a/test/TestAsset/MockApplication.php b/test/TestAsset/MockApplication.php new file mode 100644 index 0000000..3f6ebc5 --- /dev/null +++ b/test/TestAsset/MockApplication.php @@ -0,0 +1,87 @@ +events = $events; + } + + public function getEventManager() + { + return $this->events; + } + + /** + * Get the locator object + * + * @return \Zend\ServiceManager\ServiceLocatorInterface + */ + public function getServiceManager() + { + return $this->serviceManager; + } + + public function setServiceManager($serviceManager) + { + $this->serviceManager = $serviceManager; + return $this; + } + + /** + * Get the request object + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the response object + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Run the application + * + * @return \Zend\Http\Response + */ + public function run() + { + return $this->response; + } + + public function bootstrap() + { + $event = new MvcEvent(); + $event->setApplication($this); + $event->setTarget($this); + $this->getEventManager()->trigger('bootstrap', $event); + } +} diff --git a/test/TestAsset/NotAutoloaderModule/Module.php b/test/TestAsset/NotAutoloaderModule/Module.php new file mode 100644 index 0000000..1389fbc --- /dev/null +++ b/test/TestAsset/NotAutoloaderModule/Module.php @@ -0,0 +1,28 @@ +getAutoloaderConfigCalled = true; + return array( + 'Zend\Loader\StandardAutoloader' => array( + 'namespaces' => array( + 'Foo' => __DIR__ . '/src/Foo', + ), + ), + ); + } +} diff --git a/test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php b/test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php new file mode 100644 index 0000000..2d464de --- /dev/null +++ b/test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php @@ -0,0 +1,14 @@ + 'thing', +); diff --git a/test/_files/config.bad b/test/_files/config.bad new file mode 100644 index 0000000..e69de29 diff --git a/test/_files/config.ini b/test/_files/config.ini new file mode 100644 index 0000000..9eb2b99 --- /dev/null +++ b/test/_files/config.ini @@ -0,0 +1,2 @@ +[all] +ini = "yes" diff --git a/test/_files/config.json b/test/_files/config.json new file mode 100644 index 0000000..3aff3a9 --- /dev/null +++ b/test/_files/config.json @@ -0,0 +1 @@ +{"all":{"json":"yes"}} diff --git a/test/_files/config.php b/test/_files/config.php new file mode 100644 index 0000000..6e754c4 --- /dev/null +++ b/test/_files/config.php @@ -0,0 +1,6 @@ + array( + 'php' => 'yes' + ), +); diff --git a/test/_files/config.xml b/test/_files/config.xml new file mode 100644 index 0000000..1026da5 --- /dev/null +++ b/test/_files/config.xml @@ -0,0 +1,6 @@ + + + + yes + + diff --git a/test/_files/config.yaml b/test/_files/config.yaml new file mode 100644 index 0000000..eb36052 --- /dev/null +++ b/test/_files/config.yaml @@ -0,0 +1,2 @@ +all: + yaml: true diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 0000000..d535dca --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,34 @@ +