diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d09582d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,31 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# https://make.wordpress.org/core/handbook/coding-standards/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 + +[{src/**/*,config/**/*}] +insert_final_newline = true + +[views/**/*] +insert_final_newline = false + +[tests/php/Fixture/views/**/*] +insert_final_newline = false + +[{.jshintrc,*.json,*.yml}] +indent_style = space +indent_size = 2 +insert_final_newline = true + +[{*.txt,wp-config-sample.php}] +end_of_line = crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/asmp-wordpress-integration.php b/asmp-wordpress-integration.php new file mode 100644 index 0000000..cf1fc32 --- /dev/null +++ b/asmp-wordpress-integration.php @@ -0,0 +1,119 @@ +add_namespace( __NAMESPACE__, __DIR__ . '/src' ) + ->register(); +} + + + +/* + * ----------------------------------------------------------------------------- + * -- 3. Instantiate and kick off our "composition root" (our 'Plugin' class) -- + * ----------------------------------------------------------------------------- + */ + +/* + * We use a factory to instantiate the actual plugin. + * The factory keeps the object as a shared instance, so that you can also + * get outside access to that same plugin instance through the factory. + * This is similar to a Singleton, but without all the drawbacks the Singleton + * anti-pattern brings along. + * For more information on why to avoid a Singleton, read: + * https://www.alainschlesser.com/singletons-shared-instances/ + */ +$plugin = PluginFactory::create(); + +/* + * We register activation and deactivation hooks by using closures, as these + * need static access to work correctly. + */ +\register_activation_hook( __FILE__, function () use ( $plugin ) { + $plugin->activate(); +} ); + +\register_deactivation_hook( __FILE__, function () use ( $plugin ) { + $plugin->deactivate(); +} ); + +/* + * Finally, we run the plugin's register method to Hook the plugin into the + * WordPress request lifecycle. + */ +$plugin->register(); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a3ad65f --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "asmp/wordpress-integration", + "description": "WordPress integration plugin for ASMP", + "type": "wordpress-plugin", + "license": "MIT", + "require": {}, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "autoload": { + "psr-4": { + "ASMP\\WordPressIntegration\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "ASMP\\WordPressIntegration\\Tests\\": "tests/php/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2c11bfa --- /dev/null +++ b/composer.lock @@ -0,0 +1,1540 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "47ac1a06746f5a35e85bebd704981117", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2018-06-11T23:09:50+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-30T07:14:17+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-08-05T17:53:17+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:36:58+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-02-01T05:22:47+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "backendtea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-12-25T11:19:39+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..a2f4111 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + tests/php/Unit + + + tests/php/Integration + + + + + src + + + diff --git a/src/Exception/BasicScaffoldException.php b/src/Exception/BasicScaffoldException.php new file mode 100644 index 0000000..ba16a78 --- /dev/null +++ b/src/Exception/BasicScaffoldException.php @@ -0,0 +1,24 @@ +getMessage() + ); + + return new static( $message, $exception->getCode(), $exception ); + } +} diff --git a/src/Exception/FailedToMakeInstance.php b/src/Exception/FailedToMakeInstance.php new file mode 100644 index 0000000..f4b8756 --- /dev/null +++ b/src/Exception/FailedToMakeInstance.php @@ -0,0 +1,98 @@ +unregister(); + } + + /** + * Registers the autoload callback with the SPL autoload system. + * + * @return void + * @throws Exception If the autoloader could not be registered. + */ + public function register(): void { + \spl_autoload_register( [ $this, self::AUTOLOAD_METHOD ] ); + } + + /** + * Unregisters the autoload callback with the SPL autoload system. + * + * @return void + */ + public function unregister(): void { + \spl_autoload_unregister( [ $this, self::AUTOLOAD_METHOD ] ); + } + + /** + * Add a specific namespace structure with our custom autoloader. + * + * @param string $root Root namespace name. + * @param string $base_dir Directory containing the class files. + * @param string $prefix Optional. Prefix to be added before the + * class. Defaults to an empty string. + * @param string $suffix Optional. Suffix to be added after the + * class. Defaults to '.php'. + * @param boolean $lowercase Optional. Whether the class should be + * changed to lowercase. Defaults to false. + * @param boolean $underscores Optional. Whether the underscores should be + * changed to hyphens. Defaults to false. + * + * @return self + */ + public function add_namespace( + string $root, + string $base_dir, + string $prefix = self::DEFAULT_PREFIX, + string $suffix = self::DEFAULT_SUFFIX, + bool $lowercase = false, + bool $underscores = false + ): self { + $this->namespaces[] = [ + self::ROOT => $this->normalize_root( $root ), + self::BASE_DIR => $this->ensure_trailing_slash( $base_dir ), + self::PREFIX => $prefix, + self::SUFFIX => $suffix, + self::LOWERCASE => $lowercase, + self::UNDERSCORES => $underscores, + ]; + + return $this; + } + + /** + * The autoload function that gets registered with the SPL Autoloader + * system. + * + * @param string $class The class that got requested by the spl_autoloader. + */ + public function autoload( string $class ): void { + + // Iterate over namespaces to find a match. + foreach ( $this->namespaces as $namespace ) { + + // Move on if the object does not belong to the current namespace. + if ( 0 !== \strpos( $class, $namespace[ self::ROOT ] ) ) { + continue; + } + + // Remove namespace root level to correspond with root filesystem. + $filename = \str_replace( + $namespace[ self::ROOT ], '', + $class + ); + + // Remove a leading backslash from the class name. + $filename = $this->remove_leading_backslash( $filename ); + + // Replace the namespace separator "\" by the system-dependent + // directory separator. + $filename = \str_replace( + '\\', DIRECTORY_SEPARATOR, + $filename + ); + + // Change to lower case if requested. + if ( true === $namespace[ self::LOWERCASE ] ) { + $filename = \strtolower( $filename ); + } + + // Change underscores into hyphens if requested. + if ( true === $namespace[ self::UNDERSCORES ] ) { + $filename = \str_replace( '_', '-', $filename ); + } + + // Add base_dir, prefix and suffix. + $filepath = $namespace[ self::BASE_DIR ] + . $namespace[ self::PREFIX ] + . $filename + . $namespace[ self::SUFFIX ]; + + // Require the file if it exists and is readable. + if ( \is_readable( $filepath ) ) { + require $filepath; + } + } + } + + /** + * Normalize a namespace root. + * + * @param string $root Namespace root that needs to be normalized. + * + * @return string Normalized namespace root. + */ + private function normalize_root( string $root ): string { + $root = $this->remove_leading_backslash( $root ); + $root = $this->ensure_trailing_backslash( $root ); + + return $root; + } + + /** + * Remove a leading backslash from a namespace. + * + * @param string $namespace Namespace to remove the leading backslash from. + * + * @return string Modified namespace. + */ + private function remove_leading_backslash( string $namespace ): string { + return \ltrim( $namespace, '\\' ); + } + + /** + * Make sure a namespace ends with a trailing backslash. + * + * @param string $namespace Namespace to check the trailing backslash of. + * + * @return string Modified namespace. + */ + private function ensure_trailing_backslash( string $namespace ): string { + return \rtrim( $namespace, '\\' ) . '\\'; + } + + /** + * Make sure a path ends with a trailing slash. + * + * @param string $path Path to check the trailing slash of. + * + * @return string Modified path. + */ + private function ensure_trailing_slash( string $path ): string { + return \rtrim( $path, '/' ) . '/'; + } +} diff --git a/src/Infrastructure/Deactivateable.php b/src/Infrastructure/Deactivateable.php new file mode 100644 index 0000000..a8ffe09 --- /dev/null +++ b/src/Infrastructure/Deactivateable.php @@ -0,0 +1,30 @@ + */ + private $chain = []; + + /** @var array */ + private $resolutions = []; + + /** + * Add class to injection chain. + * + * @param string $class Class to add to injection chain. + * @return self Modified injection chain. + */ + public function add_to_chain( string $class ): self { + $this->chain[] = $class; + + return $this; + } + + /** + * Add resolution for circular reference detection. + * + * @param string $resolution Resolution to add. + * @return self Modified injection chain. + */ + public function add_resolution( string $resolution ): self { + $this->resolutions[ $resolution ] = true; + + return $this; + } + + /** + * Get the last class that was pushed to the injection chain. + * + * @return string Last class pushed to the injection chain. + */ + public function get_class(): string { + if ( empty( $this->chain ) ) { + throw new LogicException( + 'Access to injection chain before any resolution was made.' + ); + } + + return \end( $this->chain ); + } + + /** + * Get the injection chain. + * + * @return array Chain of injections. + */ + public function get_chain(): array { + return \array_reverse( $this->chain ); + } + + /** + * Check whether the injection chain already has a given resolution. + * + * @param string $resolution Resolution to check for + * @return bool Whether the resolution was found. + */ + public function has_resolution( string $resolution ): bool { + return \array_key_exists( $resolution, $this->resolutions ); + } +} diff --git a/src/Infrastructure/Injector/SimpleInjector.php b/src/Infrastructure/Injector/SimpleInjector.php new file mode 100644 index 0000000..1ffda05 --- /dev/null +++ b/src/Infrastructure/Injector/SimpleInjector.php @@ -0,0 +1,413 @@ + [], + ]; + + /** @var Instantiator */ + private $instantiator; + + /** + * Instantiate a SimpleInjector object. + * + * @param Instantiator|null $instantiator Optional. Instantiator to use. + */ + public function __construct( ?Instantiator $instantiator = null ) { + $this->instantiator = $instantiator ?? $this->get_fallback_instantiator(); + } + + /** + * Make an object instance out of an interface or class. + * + * @param string $interface_or_class Interface or class to make an object + * instance out of. + * @param array $arguments Optional. Additional arguments to pass + * to the constructor. Defaults to an + * empty array. + * @return object Instantiated object. + */ + public function make( string $interface_or_class, array $arguments = [] ): object { + $injection_chain = $this->resolve( + new InjectionChain(), + $interface_or_class + ); + + $class = $injection_chain->get_class(); + + if ( $this->has_shared_instance( $class ) ) { + return $this->get_shared_instance( $class ); + } + + $reflection = $this->get_class_reflection( $class ); + $this->ensure_is_instantiable( $reflection ); + + $dependencies = $this->get_dependencies_for( + $injection_chain, + $reflection, + $arguments + ); + + $object = $this->instantiator->instantiate( $class, $dependencies ); + + if ( \array_key_exists( $class, $this->shared_instances ) ) { + $this->shared_instances[ $class ] = $object; + } + + return $object; + } + + /** + * Make an object instance out of an interface or class. + * + * @param InjectionChain $injection_chain Injection chain to track + * resolutions. + * @param string $interface_or_class Interface or class to make an + * object instance out of. + * @return object Instantiated object. + */ + private function make_dependency( + InjectionChain $injection_chain, + string $interface_or_class + ): object { + $injection_chain = $this->resolve( + $injection_chain, + $interface_or_class + ); + + $class = $injection_chain->get_class(); + + if ( $this->has_shared_instance( $class ) ) { + return $this->get_shared_instance( $class ); + } + + $reflection = $this->get_class_reflection( $class ); + $this->ensure_is_instantiable( $reflection ); + + $dependencies = $this->get_dependencies_for( + $injection_chain, + $reflection + ); + + $object = $this->instantiator->instantiate( $class, $dependencies ); + + if ( \array_key_exists( $class, $this->shared_instances ) ) { + $this->shared_instances[ $class ] = $object; + } + + return $object; + } + + /** + * Bind a given interface or class to an implementation. + * + * Note: The implementation can be an interface as well, as long as it can + * be resolved to an instantiatable class at runtime. + * + * @param string $from Interface or class to bind an implementation to. + * @param string $to Interface or class that provides the implementation. + * @return Injector + */ + public function bind( string $from, string $to ): Injector { + $this->mappings[ $from ] = $to; + + return $this; + } + + /** + * Bind an argument for a class to a specific value. + * + * @param string $interface_or_class Interface or class to bind an argument + * for. + * @param string $argument_name Argument name to bind a value to. + * @param mixed $value Value to bind the argument to. + */ + public function bind_argument( + string $interface_or_class, + string $argument_name, + $value + ) { + $this->argument_mappings[ $interface_or_class ][ $argument_name ] = $value; + } + + /** + * Always reuse and share the same instance for the provided interface or + * class. + * + * @param string $interface_or_class Interface or class to reuse. + * @return Injector + */ + public function share( string $interface_or_class ): Injector { + $this->shared_instances[ $interface_or_class ] = null; + + return $this; + } + + /** + * Recursively resolve an interface to the class it should be bound to. + * + * @param InjectionChain $injection_chain Injection chain to track + * resolutions. + * @param string $interface_or_class Interface or class to resolve. + * @return InjectionChain Modified Injection chain + */ + private function resolve( + InjectionChain $injection_chain, + string $interface_or_class + ): InjectionChain { + if ( $injection_chain->has_resolution( $interface_or_class ) ) { + // Circular reference detected, aborting. + throw FailedToMakeInstance::for_circular_reference( + $interface_or_class + ); + } + + $injection_chain = $injection_chain->add_resolution( $interface_or_class ); + + if ( \array_key_exists( $interface_or_class, $this->mappings ) ) { + return $this->resolve( + $injection_chain, + $this->mappings[ $interface_or_class ] + ); + } + + return $injection_chain->add_to_chain( $interface_or_class ); + } + + /** + * Get the array of constructor dependencies for a given reflected class. + * + * @param InjectionChain $injection_chain Injection chain to track + * resolutions. + * @param ReflectionClass $reflection Reflected class to get the + * dependencies for. + * @param array $arguments Associative array of directly + * provided arguments. + * @return array Array of dependencies that represent the arguments for the + * class' constructor. + */ + private function get_dependencies_for( + InjectionChain $injection_chain, + ReflectionClass $reflection, + array $arguments = [] + ): array { + $constructor = $reflection->getConstructor(); + $class = $reflection->getName(); + + if ( null === $constructor ) { + return []; + } + + return \array_map( function ( $parameter ) use ( $injection_chain, $class, $arguments ) { + return $this->resolve_argument( + $injection_chain, + $class, + $parameter, + $arguments + ); + }, $constructor->getParameters() ); + } + + /** + * Ensure that a given reflected class is instantiable. + * + * @param ReflectionClass $reflection Reflected class to check. + * @return void + * @throws FailedToMakeInstance If the interface could not be resolved. + */ + private function ensure_is_instantiable( ReflectionClass $reflection ): void { + if ( ! $reflection->isInstantiable() ) { + throw FailedToMakeInstance::for_unresolved_interface( $reflection->getName() ); + } + } + + /** + * Resolve a given reflected argument. + * + * @param InjectionChain $injection_chain Injection chain to track + * resolutions. + * @param string $class Name of the class to + * resolve the arguments for. + * @param ReflectionParameter $parameter Parameter to resolve. + * @param array $arguments Associative array of + * directly provided + * arguments. + * @return mixed Resolved value of the argument. + */ + private function resolve_argument( + InjectionChain $injection_chain, + string $class, + ReflectionParameter $parameter, + array $arguments + ) { + if ( ! $parameter->hasType() ) { + return $this->resolve_argument_by_name( + $class, + $parameter, + $arguments + ); + } + + $type = $parameter->getType(); + + if ( null === $type || $type->isBuiltin() ) { + return $this->resolve_argument_by_name( + $class, + $parameter, + $arguments + ); + } + + $type = $type instanceof ReflectionNamedType + ? $type->getName() + : (string) $type; + + return $this->make_dependency( $injection_chain, $type ); + } + + /** + * Resolve a given reflected argument by its name. + * + * @param string $class Class to resolve the argument for. + * @param ReflectionParameter $parameter Argument to resolve by name. + * @param array $arguments Associative array of directly + * provided arguments. + * @return mixed Resolved value of the argument. + */ + private function resolve_argument_by_name( + string $class, + ReflectionParameter $parameter, + array $arguments + ) { + $name = $parameter->getName(); + + // The argument was directly provided to the make() call. + if ( \array_key_exists( $name, $arguments ) ) { + return $arguments[ $name ]; + } + + $type = $parameter->getClass() + ? $parameter->getClass()->getName() + : null; + + // Check if we have mapped this argument for the specific class. + if ( null !== $type + && \array_key_exists( $type, $this->argument_mappings ) + && \array_key_exists( $name, + $this->argument_mappings[ $type ] ) ) { + return $this->argument_mappings[ $type ][ $name ]; + } + + // No argument found for the class, check if we have a global value. + if ( \array_key_exists( $name, + $this->argument_mappings[ self::GLOBAL_ARGUMENTS ] ) ) { + return $this->argument_mappings[ self::GLOBAL_ARGUMENTS ][ $name ]; + } + + // No provided argument found, check if it has a default value. + try { + if ( $parameter->isDefaultValueAvailable() ) { + return $parameter->getDefaultValue(); + } + } catch ( Throwable $exception ) { + // Just fall through into the FailedToMakeInstance exception. + } + + // Out of options, fail with an exception. + throw FailedToMakeInstance::for_unresolved_argument( $name, $class ); + } + + /** + * Check whether a shared instance exists for a given class. + * + * @param string $class Class to check for a shared instance. + * @return bool Whether a shared instance exists. + */ + private function has_shared_instance( string $class ): bool { + return \array_key_exists( $class, $this->shared_instances ) + && null !== $this->shared_instances[ $class ]; + } + + /** + * Get the shared instance for a given class. + * + * @param string $class Class to get the shared instance for. + * @return object Shared instance. + */ + private function get_shared_instance( string $class ): object { + return $this->shared_instances[ $class ]; + } + + /** + * Get the reflection for a class or throw an exception. + * + * @param string $class Class to get the reflection for. + * @return ReflectionClass Class reflection. + * @throws FailedToMakeInstance If the class could not be reflected. + */ + private function get_class_reflection( string $class ): ReflectionClass { + try { + return new ReflectionClass( $class ); + } catch ( Throwable $exception ) { + throw FailedToMakeInstance::for_unreflectable_class( $class ); + } + } + + /** + * Get a fallback instantiator in case none was provided. + * + * @return Instantiator Simplistic fallback instantiator. + */ + private function get_fallback_instantiator(): Instantiator { + return new class implements Instantiator { + + /** + * Make an object instance out of an interface or class. + * + * @param string $class Class to make an object instance out of. + * @param array $dependencies Optional. Dependencies of the class. + * @return object Instantiated object. + */ + public function instantiate( string $class, array $dependencies = [] ): object { + return new $class( ...$dependencies ); + } + }; + } +} diff --git a/src/Infrastructure/Instantiator.php b/src/Infrastructure/Instantiator.php new file mode 100644 index 0000000..759f82a --- /dev/null +++ b/src/Infrastructure/Instantiator.php @@ -0,0 +1,29 @@ +path = $this->validate( $path ); + $this->view_factory = $view_factory; + } + + /** + * Render the current view with a given context. + * + * @param array $context Context in which to render. + * + * @return string Rendered HTML. + * @throws FailedToLoadView If the View path could not be loaded. + */ + public function render( array $context = [] ): string { + + // Add context to the current instance to make it available within the + // rendered view. + foreach ( $context as $key => $value ) { + $this->$key = $value; + } + + // Add entire context as array to the current instance to pass onto + // partial views. + $this->_context_ = $context; + + // Save current buffering level so we can backtrack in case of an error. + // This is needed because the view itself might also add an unknown + // number of output buffering levels. + $buffer_level = \ob_get_level(); + \ob_start(); + + try { + include $this->path; + } catch ( \Exception $exception ) { + // Remove whatever levels were added up until now. + while ( \ob_get_level() > $buffer_level ) { + \ob_end_clean(); + } + throw FailedToLoadView::from_view_exception( + $this->path, + $exception + ); + } + + return \ob_get_clean(); + } + + /** + * Render a partial view. + * + * This can be used from within a currently rendered view, to include + * nested partials. + * + * The passed-in context is optional, and will fall back to the parent's + * context if omitted. + * + * @param string $path Path of the partial to render. + * @param array|null $context Context in which to render the partial. + * + * @return string Rendered HTML. + * @throws InvalidPath If the provided path was not valid. + * @throws FailedToLoadView If the view could not be loaded. + */ + public function render_partial( string $path, array $context = null ): string { + $view = $this->view_factory->create( $path ); + + return $view->render( $context ?: $this->_context_ ); + } + + /** + * Validate a path. + * + * @param string $path Path to validate. + * + * @return string Validated path. + * @throws InvalidPath If an invalid path was passed into the View. + */ + protected function validate( string $path ): string { + $path = $this->check_extension( $path, static::VIEW_EXTENSION ); + $path = $this->ensure_trailing_slash( \dirname( __DIR__, 3 ) ) . $path; + + if ( ! \is_readable( $path ) ) { + throw InvalidPath::from_path( $path ); + } + + return $path; + } + + /** + * Check that the path has the correct extension. + * + * Optionally adds the extension if none was detected. + * + * @param string $path Path to check the extension of. + * @param string $extension Extension to use. + * + * @return string Path with correct extension. + */ + protected function check_extension( string $path, string $extension ): string { + $detected_extension = \pathinfo( $path, PATHINFO_EXTENSION ); + + if ( $extension !== $detected_extension ) { + $path .= '.' . $extension; + } + + return $path; + } + + /** + * Ensure the path has a trailing slash. + * + * @param string $path Path to maybe add a trailing slash. + * @return string Path with trailing slash. + */ + protected function ensure_trailing_slash( string $path ): string { + return \rtrim( $path, '/\\' ) . '/'; + } +} diff --git a/src/Infrastructure/View/SimpleViewFactory.php b/src/Infrastructure/View/SimpleViewFactory.php new file mode 100644 index 0000000..18f7c38 --- /dev/null +++ b/src/Infrastructure/View/SimpleViewFactory.php @@ -0,0 +1,31 @@ + */ + private $locations = []; + + /** + * Instantiate a TemplatedView object. + * + * @param string $path Path to the view file to render. + * @param ViewFactory $view_factory View factory instance to use. + * @param array $locations Optional. Array of locations to use. + */ + public function __construct( + string $path, + ViewFactory $view_factory, + array $locations = [] + ) { + $this->set_locations( $locations ); + parent::__construct( $path, $view_factory ); + } + + /** + * Set the locations for the templated view. + * + * @param array $locations Array of locations. + * @return self Modified templated view. + */ + public function set_locations( array $locations ): self { + $this->locations = array_map( function ( $location ) { + return $this->ensure_trailing_slash( $location ); + }, $locations ); + + return $this; + } + + /** + * Add a location to the templated view. + * + * @param string $location Location to add. + * @return self Modified templated view. + */ + public function add_location( string $location ): self { + $this->locations[] = $this->ensure_trailing_slash( $location ); + + return $this; + } + + /** + * Validate a path. + * + * @param string $path Path to validate. + * + * @return string Validated Path. + * @throws InvalidPath If an invalid path was passed into the View. + */ + protected function validate( string $path ): string { + $path = $this->check_extension( $path, static::VIEW_EXTENSION ); + + foreach ( $this->get_locations( $path ) as $location ) { + if ( \is_readable( $location ) ) { + return $location; + } + } + + if ( ! \is_readable( $path ) ) { + throw InvalidPath::from_path( $path ); + } + + return $path; + } + + /** + * Get the possible locations for the view. + * + * @param string $path Path of the view to get the locations for. + * + * @return array Array of possible locations. + */ + private function get_locations( string $path ): array { + if ( empty( $this->locations ) ) { + $this->set_default_locations(); + } + + return array_map( function ( $location ) use ( $path ) { + return "{$location}{$path}"; + }, $this->locations ); + } + + /** + * Set the default locations for the templated view. + * + * @return self Modified templated view. + */ + private function set_default_locations(): self { + return $this->set_locations( [ + STYLESHEETPATH, + TEMPLATEPATH, + \dirname( __DIR__, 2 ), + ] ); + } +} diff --git a/src/Infrastructure/View/TemplatedViewFactory.php b/src/Infrastructure/View/TemplatedViewFactory.php new file mode 100644 index 0000000..9b1a4f9 --- /dev/null +++ b/src/Infrastructure/View/TemplatedViewFactory.php @@ -0,0 +1,49 @@ + */ + private $locations; + + /** + * Instantiate a TemplatedViewFactory object. + * + * @param array $locations Array of locations to use. + */ + public function __construct( array $locations = [] ) { + $this->locations = $locations; + } + + /** + * Create a new view object for a given relative path. + * + * @param string $relative_path Relative path to create the view for. + * @return View Instantiated view object. + */ + public function create( string $relative_path ): View { + return new TemplatedView( $relative_path, $this, $this->locations ); + } +} diff --git a/src/Infrastructure/ViewFactory.php b/src/Infrastructure/ViewFactory.php new file mode 100644 index 0000000..385bba9 --- /dev/null +++ b/src/Infrastructure/ViewFactory.php @@ -0,0 +1,31 @@ +injector = $injector ?? new SimpleInjector(); + $this->injector = $this->configure_injector( $this->injector ); + } + + /** + * Activate the plugin. + * + * @return void + */ + public function activate(): void { + $this->register_services(); + + foreach ( $this->services as $service ) { + if ( $service instanceof Activateable ) { + $service->activate(); + } + } + + \flush_rewrite_rules(); + } + + /** + * Deactivate the plugin. + * + * @return void + */ + public function deactivate(): void { + $this->register_services(); + + foreach ( $this->services as $service ) { + if ( $service instanceof Deactivateable ) { + $service->deactivate(); + } + } + + \flush_rewrite_rules(); + } + + /** + * Register the plugin with the WordPress system. + * + * @return void + * @throws Exception\InvalidService If a service is not valid. + */ + public function register(): void { + \add_action( 'plugins_loaded', [ $this, 'register_services' ] ); + } + + /** + * Register the individual services of this plugin. + * + * @throws Exception\InvalidService If a service is not valid. + */ + public function register_services() { + // Bail early so we don't instantiate services twice. + if ( ! empty( $this->services ) ) { + return; + } + + $this->services[ Injector::class ] = $this->injector; + + foreach ( $this->get_service_classes() as $class ) { + $this->services[ $class ] = $this->instantiate_service( $class ); + } + + foreach ( $this->services as $service ) { + if ( $service instanceof Registerable ) { + $service->register(); + } + } + } + + /** + * Instantiate a single service. + * + * @param string $class Service class to instantiate. + * + * @return Service + * @throws Exception\InvalidService If the service is not valid. + */ + private function instantiate_service( $class ): Service { + $service = $this->injector->share( $class ) + ->make( $class ); + + if ( ! $service instanceof Service ) { + throw Exception\InvalidService::from_service( $service ); + } + + return $service; + } + + /** + * Configure the provided injector. + * + * @param Injector $injector Injector instance to configure. + * @return Injector Configured injector instance. + */ + private function configure_injector( Injector $injector ): Injector { + $bindings = \apply_filters( self::BINDINGS_FILTER, [ + ViewFactory::class => TemplatedViewFactory::class, + ] ); + + foreach ( $bindings as $from => $to ) { + $injector = $injector->bind( $from, $to ); + } + + $shared_instances = \apply_filters( self::SHARED_INSTANCES_FILTER, [ + Injector::class, + ] ); + + foreach ( $shared_instances as $shared_instance ) { + $injector = $injector->share( $shared_instance ); + } + + return $injector; + } + + /** + * Get the list of services to register. + * + * @return array Array of fully qualified class names. + */ + private function get_service_classes(): array { + return \apply_filters( self::SERVICES_FILTER, [ + // Add services as FQCNs here. + ViewFactory::class, + SampleService::class, + ] ); + } +} diff --git a/src/PluginFactory.php b/src/PluginFactory.php new file mode 100644 index 0000000..c425dd1 --- /dev/null +++ b/src/PluginFactory.php @@ -0,0 +1,32 @@ +view_factory = $view_factory; + } + + /** + * Register the service. + * + * @return void + */ + public function register(): void { + \add_action( 'admin_notices', [ $this, 'render_notice' ] ); + } + + /** + * Render the admin notice. + * + * @return void + */ + public function render_notice(): void { + echo $this->view_factory->create( 'views/test-service' ) + ->render(); + } +} diff --git a/tests/php/Fixture/DummyClass.php b/tests/php/Fixture/DummyClass.php new file mode 100644 index 0000000..59af4c7 --- /dev/null +++ b/tests/php/Fixture/DummyClass.php @@ -0,0 +1,15 @@ +dummy = $dummy; + } + + public function get_dummy(): DummyClass { + return $this->dummy; + } +} diff --git a/tests/php/Fixture/DummyClassWithNamedArguments.php b/tests/php/Fixture/DummyClassWithNamedArguments.php new file mode 100644 index 0000000..1872207 --- /dev/null +++ b/tests/php/Fixture/DummyClassWithNamedArguments.php @@ -0,0 +1,33 @@ +argument_a = $argument_a; + $this->argument_b = $argument_b; + } + + public function get_argument_a(): int { + return $this->argument_a; + } + + public function get_argument_b(): string { + return $this->argument_b; + } +} diff --git a/tests/php/Fixture/DummyInterface.php b/tests/php/Fixture/DummyInterface.php new file mode 100644 index 0000000..68bfcc5 --- /dev/null +++ b/tests/php/Fixture/DummyInterface.php @@ -0,0 +1,16 @@ +render_partial( 'partial-d' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/child_theme/view-c.php b/tests/php/Fixture/views/child_theme/view-c.php new file mode 100644 index 0000000..23674e9 --- /dev/null +++ b/tests/php/Fixture/views/child_theme/view-c.php @@ -0,0 +1 @@ +

View C comes from child theme.

\ No newline at end of file diff --git a/tests/php/Fixture/views/dynamic-view.php b/tests/php/Fixture/views/dynamic-view.php new file mode 100644 index 0000000..abc8359 --- /dev/null +++ b/tests/php/Fixture/views/dynamic-view.php @@ -0,0 +1 @@ +

Rendering works with context: some_value ?>.

\ No newline at end of file diff --git a/tests/php/Fixture/views/parent_theme/partial-b.php b/tests/php/Fixture/views/parent_theme/partial-b.php new file mode 100644 index 0000000..15b407e --- /dev/null +++ b/tests/php/Fixture/views/parent_theme/partial-b.php @@ -0,0 +1 @@ +partial B from parent theme - render_partial( 'partial-c' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/parent_theme/partial-c.php b/tests/php/Fixture/views/parent_theme/partial-c.php new file mode 100644 index 0000000..eef22e7 --- /dev/null +++ b/tests/php/Fixture/views/parent_theme/partial-c.php @@ -0,0 +1 @@ +partial C from parent theme - render_partial( 'partial-d' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/parent_theme/partial-d.php b/tests/php/Fixture/views/parent_theme/partial-d.php new file mode 100644 index 0000000..7d2283a --- /dev/null +++ b/tests/php/Fixture/views/parent_theme/partial-d.php @@ -0,0 +1 @@ +partial D from parent theme - render_partial( 'partial-e' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/parent_theme/view-b.php b/tests/php/Fixture/views/parent_theme/view-b.php new file mode 100644 index 0000000..f68ff30 --- /dev/null +++ b/tests/php/Fixture/views/parent_theme/view-b.php @@ -0,0 +1 @@ +

View B comes from parent theme.

\ No newline at end of file diff --git a/tests/php/Fixture/views/parent_theme/view-c.php b/tests/php/Fixture/views/parent_theme/view-c.php new file mode 100644 index 0000000..746216c --- /dev/null +++ b/tests/php/Fixture/views/parent_theme/view-c.php @@ -0,0 +1 @@ +

View C comes from parent theme.

\ No newline at end of file diff --git a/tests/php/Fixture/views/partial.php b/tests/php/Fixture/views/partial.php new file mode 100644 index 0000000..f0ffa7a --- /dev/null +++ b/tests/php/Fixture/views/partial.php @@ -0,0 +1 @@ +some_value ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/dynamic-view.php b/tests/php/Fixture/views/plugin/dynamic-view.php new file mode 100644 index 0000000..abc8359 --- /dev/null +++ b/tests/php/Fixture/views/plugin/dynamic-view.php @@ -0,0 +1 @@ +

Rendering works with context: some_value ?>.

\ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/partial-a.php b/tests/php/Fixture/views/plugin/partial-a.php new file mode 100644 index 0000000..7737513 --- /dev/null +++ b/tests/php/Fixture/views/plugin/partial-a.php @@ -0,0 +1 @@ +partial A from plugin - render_partial( 'partial-b' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/partial-b.php b/tests/php/Fixture/views/plugin/partial-b.php new file mode 100644 index 0000000..ab64dac --- /dev/null +++ b/tests/php/Fixture/views/plugin/partial-b.php @@ -0,0 +1 @@ +partial B from plugin - render_partial( 'partial-c' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/partial-c.php b/tests/php/Fixture/views/plugin/partial-c.php new file mode 100644 index 0000000..488957b --- /dev/null +++ b/tests/php/Fixture/views/plugin/partial-c.php @@ -0,0 +1 @@ +partial C from plugin - render_partial( 'partial-d' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/partial-d.php b/tests/php/Fixture/views/plugin/partial-d.php new file mode 100644 index 0000000..9207827 --- /dev/null +++ b/tests/php/Fixture/views/plugin/partial-d.php @@ -0,0 +1 @@ +partial D from plugin - render_partial( 'partial-e' ) ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/partial-e.php b/tests/php/Fixture/views/plugin/partial-e.php new file mode 100644 index 0000000..74cd26a --- /dev/null +++ b/tests/php/Fixture/views/plugin/partial-e.php @@ -0,0 +1 @@ +partial E from plugin \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/partial.php b/tests/php/Fixture/views/plugin/partial.php new file mode 100644 index 0000000..f0ffa7a --- /dev/null +++ b/tests/php/Fixture/views/plugin/partial.php @@ -0,0 +1 @@ +some_value ?> \ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/static-view.php b/tests/php/Fixture/views/plugin/static-view.php new file mode 100644 index 0000000..942f557 --- /dev/null +++ b/tests/php/Fixture/views/plugin/static-view.php @@ -0,0 +1 @@ +

Rendering works.

diff --git a/tests/php/Fixture/views/plugin/view-a.php b/tests/php/Fixture/views/plugin/view-a.php new file mode 100644 index 0000000..0fdd1f0 --- /dev/null +++ b/tests/php/Fixture/views/plugin/view-a.php @@ -0,0 +1 @@ +

View A comes from plugin.

\ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/view-b.php b/tests/php/Fixture/views/plugin/view-b.php new file mode 100644 index 0000000..fed079b --- /dev/null +++ b/tests/php/Fixture/views/plugin/view-b.php @@ -0,0 +1 @@ +

View B comes from plugin.

\ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/view-c.php b/tests/php/Fixture/views/plugin/view-c.php new file mode 100644 index 0000000..5b3f140 --- /dev/null +++ b/tests/php/Fixture/views/plugin/view-c.php @@ -0,0 +1 @@ +

View C comes from plugin.

\ No newline at end of file diff --git a/tests/php/Fixture/views/plugin/view-with-partial.php b/tests/php/Fixture/views/plugin/view-with-partial.php new file mode 100644 index 0000000..9c16fc2 --- /dev/null +++ b/tests/php/Fixture/views/plugin/view-with-partial.php @@ -0,0 +1 @@ +

Rendering works with partials: render_partial( 'partial' ) ?>.

\ No newline at end of file diff --git a/tests/php/Fixture/views/static-view.php b/tests/php/Fixture/views/static-view.php new file mode 100644 index 0000000..942f557 --- /dev/null +++ b/tests/php/Fixture/views/static-view.php @@ -0,0 +1 @@ +

Rendering works.

diff --git a/tests/php/Fixture/views/view-with-partial.php b/tests/php/Fixture/views/view-with-partial.php new file mode 100644 index 0000000..ee7a8cb --- /dev/null +++ b/tests/php/Fixture/views/view-with-partial.php @@ -0,0 +1 @@ +

Rendering works with partials: render_partial( 'tests/php/Fixture/views/partial' ) ?>.

\ No newline at end of file diff --git a/tests/php/Integration/SimpleViewFactoryTest.php b/tests/php/Integration/SimpleViewFactoryTest.php new file mode 100644 index 0000000..a1dbc32 --- /dev/null +++ b/tests/php/Integration/SimpleViewFactoryTest.php @@ -0,0 +1,25 @@ +create( ViewHelper::VIEWS_FOLDER . 'static-view' ); + $this->assertInstanceOf( SimpleView::class, $view ); + } + + public function test_created_views_implement_the_interface(): void { + $factory = new SimpleViewFactory(); + + $view = $factory->create( ViewHelper::VIEWS_FOLDER . 'static-view' ); + $this->assertInstanceOf( View::class, $view ); + } +} diff --git a/tests/php/Integration/SimpleViewTest.php b/tests/php/Integration/SimpleViewTest.php new file mode 100644 index 0000000..29e4ecc --- /dev/null +++ b/tests/php/Integration/SimpleViewTest.php @@ -0,0 +1,22 @@ +assertStringStartsWith( + '

Rendering works.

', + $view->render() + ); + } +} diff --git a/tests/php/Integration/TemplatedViewFactoryTest.php b/tests/php/Integration/TemplatedViewFactoryTest.php new file mode 100644 index 0000000..e95d985 --- /dev/null +++ b/tests/php/Integration/TemplatedViewFactoryTest.php @@ -0,0 +1,25 @@ +create( 'static-view' ); + $this->assertInstanceOf( TemplatedView::class, $view ); + } + + public function test_created_views_implement_the_interface(): void { + $factory = new TemplatedViewFactory( ViewHelper::LOCATIONS ); + + $view = $factory->create( 'static-view' ); + $this->assertInstanceOf( View::class, $view ); + } +} diff --git a/tests/php/Integration/TemplatedViewTest.php b/tests/php/Integration/TemplatedViewTest.php new file mode 100644 index 0000000..c02105b --- /dev/null +++ b/tests/php/Integration/TemplatedViewTest.php @@ -0,0 +1,23 @@ +assertStringStartsWith( + 'partial A from plugin - partial B from parent theme - partial C from child theme - partial D from parent theme - partial E from plugin', + $partials->render() + ); + } +} diff --git a/tests/php/Integration/TestCase.php b/tests/php/Integration/TestCase.php new file mode 100644 index 0000000..e9c96cf --- /dev/null +++ b/tests/php/Integration/TestCase.php @@ -0,0 +1,17 @@ +assertInstanceOf( InjectionChain::class, $chain ); + } + + public function test_it_accepts_new_resolutions(): void { + $chain = ( new InjectionChain() ) + ->add_resolution( 'something' ); + + $this->assertTrue( $chain->has_resolution( 'something' ) ); + $this->assertFalse( $chain->has_resolution( 'something_else' ) ); + } + + public function test_it_accepts_new_chain_entries(): void { + $chain = ( new InjectionChain() ) + ->add_to_chain( 'something' ); + + $this->assertEquals( 'something', $chain->get_class() ); + } + + public function test_it_returns_the_last_class_in_the_chain(): void { + $chain = ( new InjectionChain() ) + ->add_to_chain( 'first' ) + ->add_to_chain( 'second' ) + ->add_to_chain( 'third' ); + + $this->assertEquals( 'third', $chain->get_class() ); + } + + public function test_it_retains_all_elements_in_the_chain(): void { + $chain = ( new InjectionChain() ) + ->add_to_chain( 'first' ) + ->add_to_chain( 'second' ) + ->add_to_chain( 'third' ); + + $this->assertEquals( [ 'third', 'second', 'first' ], $chain->get_chain() ); + } +} diff --git a/tests/php/Unit/SimpleInjectorTest.php b/tests/php/Unit/SimpleInjectorTest.php new file mode 100644 index 0000000..5679755 --- /dev/null +++ b/tests/php/Unit/SimpleInjectorTest.php @@ -0,0 +1,111 @@ +assertInstanceOf( SimpleInjector::class, $injector ); + } + + public function test_it_implements_the_interface(): void { + $injector = new SimpleInjector(); + + $this->assertInstanceOf( Injector::class, $injector ); + } + + public function test_it_can_instantiate_a_concrete_class(): void { + $object = ( new SimpleInjector() ) + ->make( Fixture\DummyClass::class ); + + $this->assertInstanceOf( Fixture\DummyClass::class, $object ); + } + + public function test_it_can_autowire_a_class_with_a_dependency(): void { + $object = ( new SimpleInjector() ) + ->make( Fixture\DummyClassWithDependency::class ); + + $this->assertInstanceOf( Fixture\DummyClassWithDependency::class, $object ); + $this->assertInstanceOf( Fixture\DummyClass::class, $object->get_dummy() ); + } + + public function test_it_can_instantiate_a_bound_interface(): void { + $injector = ( new SimpleInjector() ) + ->bind( + Fixture\DummyInterface::class, + Fixture\DummyClassWithDependency::class + ); + $object = $injector->make( Fixture\DummyInterface::class ); + + $this->assertInstanceOf( Fixture\DummyInterface::class, $object ); + $this->assertInstanceOf( Fixture\DummyClassWithDependency::class, $object ); + $this->assertInstanceOf( Fixture\DummyClass::class, $object->get_dummy() ); + } + + public function test_it_returns_separate_instances_by_default(): void { + $injector = new SimpleInjector(); + $object_a = $injector->make( Fixture\DummyClass::class ); + $object_b = $injector->make( Fixture\DummyClass::class ); + + $this->assertNotSame( $object_a, $object_b ); + } + + public function test_it_returns_same_instances_if_shared(): void { + $injector = ( new SimpleInjector() ) + ->share( Fixture\DummyClass::class ); + $object_a = $injector->make( Fixture\DummyClass::class ); + $object_b = $injector->make( Fixture\DummyClass::class ); + + $this->assertSame( $object_a, $object_b ); + } + + public function test_it_can_instantiate_a_class_with_named_arguments(): void { + $object = ( new SimpleInjector() ) + ->make( + Fixture\DummyClassWithNamedArguments::class, + [ 'argument_a' => 42, 'argument_b' => 'Mr Alderson' ] + ); + + $this->assertInstanceOf( Fixture\DummyClassWithNamedArguments::class, $object ); + $this->assertEquals( 42, $object->get_argument_a() ); + $this->assertEquals( 'Mr Alderson', $object->get_argument_b() ); + } + + public function test_it_allows_for_skipping_named_arguments_with_default_values(): void { + $object = ( new SimpleInjector() ) + ->make( + Fixture\DummyClassWithNamedArguments::class, + [ 'argument_a' => 42 ] + ); + + $this->assertInstanceOf( Fixture\DummyClassWithNamedArguments::class, $object ); + $this->assertEquals( 42, $object->get_argument_a() ); + $this->assertEquals( 'Mr Meeseeks', $object->get_argument_b() ); + } + + public function test_it_throws_if_a_required_named_arguments_is_missing(): void { + $this->expectException( FailedToMakeInstance::class ); + + ( new SimpleInjector() ) + ->make( Fixture\DummyClassWithNamedArguments::class ); + } + + public function test_it_throws_if_a_circular_reference_is_detected(): void { + $this->expectException( FailedToMakeInstance::class ); + $this->expectExceptionCode( FailedToMakeInstance::CIRCULAR_REFERENCE ); + + ( new SimpleInjector() ) + ->bind( + Fixture\DummyClass::class, + Fixture\DummyClassWithDependency::class + ) + ->make( Fixture\DummyClassWithDependency::class ); + } +} diff --git a/tests/php/Unit/SimpleViewFactoryTest.php b/tests/php/Unit/SimpleViewFactoryTest.php new file mode 100644 index 0000000..8dd98f7 --- /dev/null +++ b/tests/php/Unit/SimpleViewFactoryTest.php @@ -0,0 +1,21 @@ +assertInstanceOf( SimpleViewFactory::class, $factory ); + } + + public function test_it_implements_the_interface(): void { + $factory = new SimpleViewFactory(); + + $this->assertInstanceOf( ViewFactory::class, $factory ); + } +} diff --git a/tests/php/Unit/SimpleViewTest.php b/tests/php/Unit/SimpleViewTest.php new file mode 100644 index 0000000..d5327cd --- /dev/null +++ b/tests/php/Unit/SimpleViewTest.php @@ -0,0 +1,68 @@ +createMock( ViewFactory::class ); + $view = new SimpleView( + ViewHelper::VIEWS_FOLDER . 'static-view', + $view_factory_mock + ); + + $this->assertInstanceOf( SimpleView::class, $view ); + } + + public function test_it_can_be_rendered(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view = new SimpleView( + ViewHelper::VIEWS_FOLDER . 'static-view', + $view_factory_mock + ); + + $this->assertStringStartsWith( + '

Rendering works.

', + $view->render() + ); + } + + public function test_it_can_provide_rendering_context(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view = new SimpleView( + ViewHelper::VIEWS_FOLDER . 'dynamic-view', + $view_factory_mock + ); + + $this->assertStringStartsWith( + '

Rendering works with context: 42.

', + $view->render( [ 'some_value' => 42 ] ) + ); + } + + public function test_it_can_render_partials(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view_factory_mock + ->expects( $this->once() ) + ->method( 'create' ) + ->with( ViewHelper::VIEWS_FOLDER . 'partial' ) + ->willReturn( new SimpleView( + ViewHelper::VIEWS_FOLDER . 'partial', + $view_factory_mock + ) ); + + $view = new SimpleView( + ViewHelper::VIEWS_FOLDER . 'view-with-partial', + $view_factory_mock + ); + + $this->assertStringStartsWith( + '

Rendering works with partials: 42.

', + $view->render( [ 'some_value' => 42 ] ) + ); + } +} diff --git a/tests/php/Unit/TemplatedViewFactoryTest.php b/tests/php/Unit/TemplatedViewFactoryTest.php new file mode 100644 index 0000000..59f2a72 --- /dev/null +++ b/tests/php/Unit/TemplatedViewFactoryTest.php @@ -0,0 +1,21 @@ +assertInstanceOf( TemplatedViewFactory::class, $factory ); + } + + public function test_it_implements_the_interface(): void { + $factory = new TemplatedViewFactory(); + + $this->assertInstanceOf( ViewFactory::class, $factory ); + } +} diff --git a/tests/php/Unit/TemplatedViewTest.php b/tests/php/Unit/TemplatedViewTest.php new file mode 100644 index 0000000..20f01d2 --- /dev/null +++ b/tests/php/Unit/TemplatedViewTest.php @@ -0,0 +1,106 @@ +createMock( ViewFactory::class ); + $view = new TemplatedView( + 'static-view', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + + $this->assertInstanceOf( TemplatedView::class, $view ); + } + + public function test_it_can_be_rendered(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view = new TemplatedView( + 'static-view', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + + $this->assertStringStartsWith( + '

Rendering works.

', + $view->render() + ); + } + + public function test_it_can_provide_rendering_context(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view = new TemplatedView( + 'dynamic-view', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + + $this->assertStringStartsWith( + '

Rendering works with context: 42.

', + $view->render( [ 'some_value' => 42 ] ) + ); + } + + public function test_it_can_render_partials(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view_factory_mock + ->expects( $this->once() ) + ->method( 'create' ) + ->with( 'partial' ) + ->willReturn( new TemplatedView( + 'partial', + $view_factory_mock, + ViewHelper::LOCATIONS + ) ); + + $view = new TemplatedView( + 'view-with-partial', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + + $this->assertStringStartsWith( + '

Rendering works with partials: 42.

', + $view->render( [ 'some_value' => 42 ] ) + ); + } + + public function test_it_can_be_overridden_in_themes(): void { + $view_factory_mock = $this->createMock( ViewFactory::class ); + $view_a = new TemplatedView( + 'view-a', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + $view_b = new TemplatedView( + 'view-b', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + $view_c = new TemplatedView( + 'view-c', + $view_factory_mock, + ViewHelper::LOCATIONS + ); + + $this->assertStringStartsWith( + '

View A comes from plugin.

', + $view_a->render() + ); + $this->assertStringStartsWith( + '

View B comes from parent theme.

', + $view_b->render() + ); + $this->assertStringStartsWith( + '

View C comes from child theme.

', + $view_c->render() + ); + } +} diff --git a/tests/php/Unit/TestCase.php b/tests/php/Unit/TestCase.php new file mode 100644 index 0000000..a139bbf --- /dev/null +++ b/tests/php/Unit/TestCase.php @@ -0,0 +1,17 @@ +

Hello World!