From faa70cb24df673f327945ea6d89af98ec10794bb Mon Sep 17 00:00:00 2001 From: Alex Standiford Date: Sat, 12 Nov 2022 11:43:51 -0600 Subject: [PATCH] Release/3.0.0 (#51) --- README.md | 10 +- composer.json | 38 +- composer.lock | 2617 ++++++++++++++++- lib/Abstracts/Builder.php | 45 + lib/Abstracts/Event_Type.php | 243 -- lib/Abstracts/Observer.php | 57 +- lib/Abstracts/Query_Builder.php | 11 + lib/Abstracts/Registries/Object_Registry.php | 234 +- lib/Abstracts/Registries/Registry.php | 197 +- lib/Abstracts/Registry_Mutator.php | 19 + lib/Abstracts/Request_Middleware.php | 26 + lib/Abstracts/Rest_Action.php | 100 + lib/Abstracts/Sort_Method.php | 22 + lib/Abstracts/Storage.php | 25 - lib/Abstracts/Underpin.php | 790 ----- lib/Enums/Direction.php | 12 + lib/Enums/Filter.php | 25 + lib/Enums/Logger_Events.php | 13 + lib/Enums/Logger_Item_Events.php | 9 + lib/Enums/Rest.php | 12 + lib/Enums/Types.php | 15 + lib/Exceptions/Exception.php | 17 + lib/Exceptions/Instance_Not_Ready.php | 11 + lib/Exceptions/Invalid_Callback.php | 16 + lib/Exceptions/Invalid_Field.php | 7 + lib/Exceptions/Invalid_Registry_Action.php | 8 + lib/Exceptions/Invalid_Registry_Item.php | 8 + lib/Exceptions/Item_Not_Found.php | 12 + lib/Exceptions/Middleware_Exception.php | 7 + lib/Exceptions/Operation_Failed.php | 7 + lib/Exceptions/Plugin_Already_Registered.php | 12 + lib/Exceptions/Unknown_Registry_Item.php | 12 + lib/Exceptions/Unmet_Requirements.php | 17 + lib/Exceptions/Url_Exception.php | 9 + lib/Exceptions/Validation_Failed.php | 7 + lib/Factories/Accumulator.php | 61 - lib/Factories/Broadcaster.php | 109 + lib/Factories/Controller.php | 107 + lib/Factories/Data_Providers/Accumulator.php | 71 + .../Data_Providers/Bool_Provider.php | 14 + .../Data_Providers/Float_Provider.php | 14 + lib/Factories/Data_Providers/Int_Provider.php | 14 + .../Plugin_Builder_Provider.php | 17 + .../Data_Providers/Plugin_Provider.php | 23 + .../Data_Providers/String_Provider.php | 13 + lib/Factories/Dependency_Processor.php | 117 - lib/Factories/Event_Type.php | 186 ++ lib/Factories/Event_Type_Instance.php | 37 - lib/Factories/Head_Tag.php | 68 + lib/Factories/Header.php | 71 + lib/Factories/Html_Attribute.php | 38 + lib/Factories/Log_Item.php | 156 +- lib/Factories/Object_Registry.php | 28 - lib/Factories/Observer.php | 29 - lib/Factories/Observers/Loader.php | 27 +- lib/Factories/Registry.php | 29 +- lib/Factories/Registry_Items/Param.php | 84 + lib/Factories/Request.php | 278 ++ lib/Factories/Simple_Storage.php | 20 - lib/Factories/Sort_Methods/Basic.php | 55 + lib/Factories/Tag.php | 63 + lib/Factories/Underpin_Instance.php | 41 - lib/Factories/Url.php | 287 ++ lib/Helpers/Array_Helper.php | 515 ++++ lib/Helpers/Number_Helper.php | 30 + lib/Helpers/Object_Helper.php | 82 + lib/Helpers/Processors/Array_Processor.php | 390 +++ .../Processors/Dependency_Processor.php | 131 + lib/Helpers/Processors/List_Filter.php | 178 ++ lib/Helpers/Processors/List_Sorter.php | 67 + lib/Helpers/Processors/Registry_Query.php | 33 + lib/Helpers/String_Helper.php | 170 ++ lib/Interfaces/Base.php | 12 + lib/Interfaces/Can_Broadcast.php | 14 + lib/Interfaces/Can_Convert_To_Array.php | 15 + lib/Interfaces/Can_Convert_To_Instance.php | 12 + lib/Interfaces/Can_Convert_To_Model.php | 7 + lib/Interfaces/Can_Convert_To_Request.php | 11 + lib/Interfaces/Can_Convert_To_String.php | 11 + lib/Interfaces/Can_Create.php | 16 + lib/Interfaces/Can_Delete.php | 17 + lib/Interfaces/Can_Read.php | 18 + lib/Interfaces/Can_Remove.php | 9 + lib/Interfaces/Can_Update.php | 15 + lib/Interfaces/Data_Provider.php | 10 + lib/Interfaces/Data_Store.php | 8 + lib/Interfaces/Event_Type.php | 62 + lib/Interfaces/Feature_Extension.php | 18 + lib/Interfaces/Has_Response.php | 9 + lib/Interfaces/Identifiable.php | 13 + lib/Interfaces/Identifiable_Int.php | 12 + lib/Interfaces/Identifiable_String.php | 12 + lib/Interfaces/Integration_Provider.php | 58 + lib/Interfaces/Item_With_Dependencies.php | 30 +- lib/Interfaces/Loader_Item.php | 8 + lib/Interfaces/Log_Item.php | 9 + lib/Interfaces/Model.php | 19 + lib/Interfaces/Provider.php | 10 + lib/Interfaces/Query.php | 46 + lib/Interfaces/Queryable.php | 10 + lib/Interfaces/Singleton.php | 8 +- lib/Interfaces/With_Middleware.php | 13 + lib/Loaders/Logger.php | 417 --- lib/Middlewares/Rest/Has_Param_Middleware.php | 28 + .../Rest/Validate_Type_Middleware.php | 32 + lib/Registries/Head_Tag_Collection.php | 62 + lib/Registries/Immutable_Collection.php | 37 + lib/Registries/Loader.php | 40 + lib/Registries/Logger.php | 452 +++ lib/Registries/Mutable_Collection.php | 15 + .../Mutable_Collection_With_Remove.php | 10 + lib/Registries/Param_Collection.php | 79 + lib/Traits/Can_Remove_Registry_Item.php | 27 + lib/Traits/Feature_Extension.php | 41 - lib/Traits/Filter_Params.php | 177 ++ lib/Traits/Instance_Setter.php | 53 - lib/Traits/Sort_Params.php | 27 + lib/Traits/Templates.php | 486 --- lib/Traits/With_Broadcaster.php | 27 + lib/Traits/With_Closure_Converter.php | 36 + lib/Traits/With_Dependencies.php | 50 + lib/Traits/With_Instance.php | 23 + lib/Traits/With_Int_Identity.php | 19 + lib/Traits/With_Middleware.php | 56 - lib/Traits/With_Object_Cache.php | 21 + lib/Traits/With_Static_Subject.php | 92 - lib/Traits/With_Subject.php | 118 - phpunit.xml | 16 + tests/Helpers.php | 28 + tests/Test_Case.php | 9 + tests/Traits/With_Getter_Tests.php | 31 + tests/Unit/Abstracts/Observer_Test.php | 48 + .../Registries/Object_Registry_Test.php | 122 + .../Abstracts/Registries/Registry_Test.php | 153 + tests/Unit/Enums/Test_Filter.php | 54 + .../Data_Providers/Accumulator_Test.php | 62 + .../Plugin_Builder_Provider_Test.php | 23 + .../Data_Providers/Plugin_Provider_Test.php | 24 + tests/Unit/Factories/Object_Registry_Test.php | 33 + .../Unit/Factories/Observers/Loader_Test.php | 27 + tests/bootstrap.php | 2 + 141 files changed, 8755 insertions(+), 3137 deletions(-) create mode 100644 lib/Abstracts/Builder.php delete mode 100644 lib/Abstracts/Event_Type.php create mode 100644 lib/Abstracts/Query_Builder.php create mode 100644 lib/Abstracts/Registry_Mutator.php create mode 100644 lib/Abstracts/Request_Middleware.php create mode 100644 lib/Abstracts/Rest_Action.php create mode 100644 lib/Abstracts/Sort_Method.php delete mode 100644 lib/Abstracts/Storage.php delete mode 100644 lib/Abstracts/Underpin.php create mode 100644 lib/Enums/Direction.php create mode 100644 lib/Enums/Filter.php create mode 100644 lib/Enums/Logger_Events.php create mode 100644 lib/Enums/Logger_Item_Events.php create mode 100644 lib/Enums/Rest.php create mode 100644 lib/Enums/Types.php create mode 100644 lib/Exceptions/Exception.php create mode 100644 lib/Exceptions/Instance_Not_Ready.php create mode 100644 lib/Exceptions/Invalid_Callback.php create mode 100644 lib/Exceptions/Invalid_Field.php create mode 100644 lib/Exceptions/Invalid_Registry_Action.php create mode 100644 lib/Exceptions/Invalid_Registry_Item.php create mode 100644 lib/Exceptions/Item_Not_Found.php create mode 100644 lib/Exceptions/Middleware_Exception.php create mode 100644 lib/Exceptions/Operation_Failed.php create mode 100644 lib/Exceptions/Plugin_Already_Registered.php create mode 100644 lib/Exceptions/Unknown_Registry_Item.php create mode 100644 lib/Exceptions/Unmet_Requirements.php create mode 100644 lib/Exceptions/Url_Exception.php create mode 100644 lib/Exceptions/Validation_Failed.php delete mode 100644 lib/Factories/Accumulator.php create mode 100644 lib/Factories/Broadcaster.php create mode 100644 lib/Factories/Controller.php create mode 100644 lib/Factories/Data_Providers/Accumulator.php create mode 100644 lib/Factories/Data_Providers/Bool_Provider.php create mode 100644 lib/Factories/Data_Providers/Float_Provider.php create mode 100644 lib/Factories/Data_Providers/Int_Provider.php create mode 100644 lib/Factories/Data_Providers/Plugin_Builder_Provider.php create mode 100644 lib/Factories/Data_Providers/Plugin_Provider.php create mode 100644 lib/Factories/Data_Providers/String_Provider.php delete mode 100644 lib/Factories/Dependency_Processor.php create mode 100644 lib/Factories/Event_Type.php delete mode 100644 lib/Factories/Event_Type_Instance.php create mode 100644 lib/Factories/Head_Tag.php create mode 100644 lib/Factories/Header.php create mode 100644 lib/Factories/Html_Attribute.php delete mode 100644 lib/Factories/Object_Registry.php delete mode 100644 lib/Factories/Observer.php create mode 100644 lib/Factories/Registry_Items/Param.php create mode 100644 lib/Factories/Request.php delete mode 100644 lib/Factories/Simple_Storage.php create mode 100644 lib/Factories/Sort_Methods/Basic.php create mode 100644 lib/Factories/Tag.php delete mode 100644 lib/Factories/Underpin_Instance.php create mode 100644 lib/Factories/Url.php create mode 100644 lib/Helpers/Array_Helper.php create mode 100644 lib/Helpers/Number_Helper.php create mode 100644 lib/Helpers/Object_Helper.php create mode 100644 lib/Helpers/Processors/Array_Processor.php create mode 100644 lib/Helpers/Processors/Dependency_Processor.php create mode 100644 lib/Helpers/Processors/List_Filter.php create mode 100644 lib/Helpers/Processors/List_Sorter.php create mode 100644 lib/Helpers/Processors/Registry_Query.php create mode 100644 lib/Helpers/String_Helper.php create mode 100644 lib/Interfaces/Base.php create mode 100644 lib/Interfaces/Can_Broadcast.php create mode 100644 lib/Interfaces/Can_Convert_To_Array.php create mode 100644 lib/Interfaces/Can_Convert_To_Instance.php create mode 100644 lib/Interfaces/Can_Convert_To_Model.php create mode 100644 lib/Interfaces/Can_Convert_To_Request.php create mode 100644 lib/Interfaces/Can_Convert_To_String.php create mode 100644 lib/Interfaces/Can_Create.php create mode 100644 lib/Interfaces/Can_Delete.php create mode 100644 lib/Interfaces/Can_Read.php create mode 100644 lib/Interfaces/Can_Remove.php create mode 100644 lib/Interfaces/Can_Update.php create mode 100644 lib/Interfaces/Data_Provider.php create mode 100644 lib/Interfaces/Data_Store.php create mode 100644 lib/Interfaces/Event_Type.php create mode 100644 lib/Interfaces/Feature_Extension.php create mode 100644 lib/Interfaces/Has_Response.php create mode 100644 lib/Interfaces/Identifiable.php create mode 100644 lib/Interfaces/Identifiable_Int.php create mode 100644 lib/Interfaces/Identifiable_String.php create mode 100644 lib/Interfaces/Integration_Provider.php create mode 100644 lib/Interfaces/Loader_Item.php create mode 100644 lib/Interfaces/Log_Item.php create mode 100644 lib/Interfaces/Model.php create mode 100644 lib/Interfaces/Provider.php create mode 100644 lib/Interfaces/Query.php create mode 100644 lib/Interfaces/Queryable.php create mode 100644 lib/Interfaces/With_Middleware.php delete mode 100644 lib/Loaders/Logger.php create mode 100644 lib/Middlewares/Rest/Has_Param_Middleware.php create mode 100644 lib/Middlewares/Rest/Validate_Type_Middleware.php create mode 100644 lib/Registries/Head_Tag_Collection.php create mode 100644 lib/Registries/Immutable_Collection.php create mode 100644 lib/Registries/Loader.php create mode 100644 lib/Registries/Logger.php create mode 100644 lib/Registries/Mutable_Collection.php create mode 100644 lib/Registries/Mutable_Collection_With_Remove.php create mode 100644 lib/Registries/Param_Collection.php create mode 100644 lib/Traits/Can_Remove_Registry_Item.php delete mode 100644 lib/Traits/Feature_Extension.php create mode 100644 lib/Traits/Filter_Params.php delete mode 100644 lib/Traits/Instance_Setter.php create mode 100644 lib/Traits/Sort_Params.php delete mode 100644 lib/Traits/Templates.php create mode 100644 lib/Traits/With_Broadcaster.php create mode 100644 lib/Traits/With_Closure_Converter.php create mode 100644 lib/Traits/With_Dependencies.php create mode 100644 lib/Traits/With_Instance.php create mode 100644 lib/Traits/With_Int_Identity.php delete mode 100644 lib/Traits/With_Middleware.php create mode 100644 lib/Traits/With_Object_Cache.php delete mode 100644 lib/Traits/With_Static_Subject.php delete mode 100644 lib/Traits/With_Subject.php create mode 100644 phpunit.xml create mode 100644 tests/Helpers.php create mode 100644 tests/Test_Case.php create mode 100644 tests/Traits/With_Getter_Tests.php create mode 100644 tests/Unit/Abstracts/Observer_Test.php create mode 100644 tests/Unit/Abstracts/Registries/Object_Registry_Test.php create mode 100644 tests/Unit/Abstracts/Registries/Registry_Test.php create mode 100644 tests/Unit/Enums/Test_Filter.php create mode 100644 tests/Unit/Factories/Data_Providers/Accumulator_Test.php create mode 100644 tests/Unit/Factories/Data_Providers/Plugin_Builder_Provider_Test.php create mode 100644 tests/Unit/Factories/Data_Providers/Plugin_Provider_Test.php create mode 100644 tests/Unit/Factories/Object_Registry_Test.php create mode 100644 tests/Unit/Factories/Observers/Loader_Test.php create mode 100644 tests/bootstrap.php diff --git a/README.md b/README.md index 9dac6b1..10cbd07 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,8 @@ instance of `Underpin\Factories\Underpin_Instance` is created. * * @return \Underpin\Factories\Underpin_Instance The bootstrap for this plugin. */ -function plugin_name_replace_me() { - return Underpin\Abstracts\Underpin::make_class( [ +use Underpin\Helpers\Object_Helper;function plugin_name_replace_me() { + return Object_Helper::make_class( [ 'root_namespace' => 'Plugin_Name_Replace_Me', 'text_domain' => 'plugin_name_replace_me', 'version' => '1.0.0', @@ -300,7 +300,7 @@ The example above could be converted into a class that looks like this: ```php -class Plugin_Name_Replace_Me extends Underpin\Abstracts\Underpin{ +class Plugin_Name_Replace_Me extends \Underpin\Loaders\Underpin{ /** @@ -414,8 +414,8 @@ Now, simply call this class using `Underpin::make_class`. * * @return \Underpin\Factories\Underpin_Instance The bootstrap for this plugin. */ -function plugin_name_replace_me() { - return Underpin\Abstracts\Underpin::make_class( [ +use Underpin\Helpers\Object_Helper;function plugin_name_replace_me() { + return Object_Helper::make_class( [ 'class' => 'Plugin_Name_Replace_Me', 'args' => [ 'custom_param' => 'Custom paramater value', diff --git a/composer.json b/composer.json index 53ab83c..3971b4f 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,30 @@ { - "name": "underpin/underpin", - "description": "Underpin WordPress framework", - "type": "wordpress-muplugin", - "license": "MIT", - "authors": [ - { - "name": "Alex Standiford", - "email": "a@alexstandiford.com" - } - ], + "name": "underpin/underpin", + "description": "Underpin framework", + "type": "package", + "license": "MIT", + "authors": [ + { + "name": "Alex Standiford", + "email": "a@alexstandiford.com" + } + ], "autoload": { - "psr-4": {"Underpin\\": "lib/"} + "psr-4": { + "Underpin\\": "lib/" + } }, - "require": { - "composer/installers": "^2.1" + "autoload-dev": { + "psr-4": { + "Underpin\\Tests\\": "tests/" } + }, + "require": { + "composer/installers": "^2.1", + "php": "^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "mockery/mockery": "^1.5" + } } diff --git a/composer.lock b/composer.lock index 4beb17e..51c8f87 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d68ccbe153efe6178c9f4c9b0dfd8b51", + "content-hash": "791813a8af1a95e9d1e105e9b3870144", "packages": [ { "name": "composer/installers", - "version": "v2.1.0", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "75e5ef05436c90ac565a48176cc7465991908352" + "reference": "af93ba6e52236418f07a278033eba6959ee5b983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/75e5ef05436c90ac565a48176cc7465991908352", - "reference": "75e5ef05436c90ac565a48176cc7465991908352", + "url": "https://api.github.com/repos/composer/installers/zipball/af93ba6e52236418f07a278033eba6959ee5b983", + "reference": "af93ba6e52236418f07a278033eba6959ee5b983", "shasum": "" }, "require": { @@ -132,7 +132,7 @@ ], "support": { "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v2.1.0" + "source": "https://github.com/composer/installers/tree/v2.1.1" }, "funding": [ { @@ -148,10 +148,2611 @@ "type": "tidelift" } ], - "time": "2022-03-18T12:27:54+00:00" + "time": "2022-04-13T09:13:00+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "0690bde05318336c7221785f2a932467f98b64ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/0690bde05318336c7221785f2a932467f98b64ca", + "reference": "0690bde05318336c7221785f2a932467f98b64ca", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "phpoption/phpoption": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2021-11-21T21:41:47+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2021-12-04T23:24:31+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.4.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2021-12-12T23:22:04+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "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": "https://ocramius.github.io/" + } + ], + "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" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.5.0" + }, + "time": "2022-01-20T13:18:17+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.13.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + }, + "time": "2021-11-30T19:35:32+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "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": "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)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.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", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.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" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.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": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "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" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + }, + "time": "2021-12-08T12:19:24+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.13.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-07T09:28:20+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-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": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.20", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.0", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-01T12:37:26+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-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/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-15T09:54:48+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.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", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-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" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], diff --git a/lib/Abstracts/Builder.php b/lib/Abstracts/Builder.php new file mode 100644 index 0000000..a610c5f --- /dev/null +++ b/lib/Abstracts/Builder.php @@ -0,0 +1,45 @@ +args[ $key ] = $value; + + return $this; + } + + protected function set_string( string $key, string $value ): static { + $this->args[ $key ] = $value; + + return $this; + } + + protected function set_array( string $key, array $values ): static { + $this->args[ $key ] = $values; + + return $this; + } + + protected function set_int( string $key, int $value ): static { + $this->args[ $key ] = $value; + + return $this; + } + + protected function set_varidic( string $single_key, string $multi_key, array $values ): static { + if ( count( $values ) === 1 ) { + $this->args[ $single_key ] = $values[0]; + } else { + $this->args[ $multi_key ] = $values; + } + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Abstracts/Event_Type.php b/lib/Abstracts/Event_Type.php deleted file mode 100644 index 56a1952..0000000 --- a/lib/Abstracts/Event_Type.php +++ /dev/null @@ -1,243 +0,0 @@ -write_events( $this ); - reset( $this ); - } - - /** - * Write the events in this event type to each specified writer. - * - * @since 2.0.0 - */ - protected function write_events( Event_Type $event_type ) { - $this->notify( 'log:write', [ 'event_type' => $event_type ] ) ; - } - - - /** - * Enqueues an event to be logged in the system. - * - * @since 1.0.0 - * - * @param string $code The event code to use. - * @param string $message The message to log. - * @param array $data Arbitrary data associated with this event message. - * - * @return Log_Item|WP_Error The logged item, or a WP_Error if something went wrong. - */ - public function log( $code, $message, $data = array() ) { - if ( Logger::is_muted() ) { - return new WP_Error( - 'logger_muted', - 'The logger is currently muted.', - [ 'log_item_class' => $this->log_item_class ] - ); - } - return Logger::do_muted_action( function () use ( $code, $message, $data ) { - - /** - * Makes it possible to add additional data to logged events. - * - * @since 2.0.0 - * - * @param array $data list of data to add - * @param string $code event code - * @param string $message event message - * @param Event_Type $instance The current event instance - * - */ - $this->notify( 'log:init', [ 'code' => $code, 'message' => $message, 'data' => $data ] ) ; - - $item = new $this->log_item_class( $this, $code, $message, $data ); - - if ( ! $item instanceof Log_Item ) { - return new WP_Error( - 'log_item_class_invalid', - 'The log item class must be extend the Log_Item class.', - [ 'log_item_class' => $this->log_item_class ] - ); - } - - - $this[] = $item; - - $this->notify( 'log:item_logged', [ 'item' => $item ] ) ; - - return $item; - } ); - } - - /** - * Logs an error using a WP Error object. - * - * @since 1.0.0 - * - * @param WP_Error $wp_error Instance of WP_Error to use for log - * @param array $data Additional data to log - * - * @return Log_Item The logged item. - */ - public function log_wp_error( WP_Error $wp_error, $data = [] ) { - if ( ! empty( $data ) ) { - $current_data = $wp_error->get_error_data(); - $data = array_merge( (array) $current_data, $data ); - $wp_error->add_data( $data ); - } - - return $this->log( $wp_error->get_error_code(), $wp_error->get_error_message(), $wp_error->get_error_data() ); - } - - /** - * Logs an error using a WP Error object. - * - * @since 1.0.0 - * - * @param Exception $exception Exception instance to log. - * @param array $data array Data associated with this error message - * - * @return Log_Item The logged item. - */ - public function log_exception( Exception $exception, $data = array() ) { - return $this->log( $exception->getCode(), $exception->getMessage(), $data ); - } - - /** - * Getter method. - * - * @param $key - * - * @return mixed|WP_Error - */ - public function __get( $key ) { - if ( isset( $this->$key ) ) { - return $this->$key; - } else { - return new WP_Error( 'logger_param_not_set', 'The logger param ' . $key . ' could not be found.' ); - } - } - - public function __isset( $key ) { - $key = $this->$key; - if ( is_wp_error( $key ) ) { - return false; - } - - return true; - } - -} \ No newline at end of file diff --git a/lib/Abstracts/Observer.php b/lib/Abstracts/Observer.php index e1538ca..5e1396f 100644 --- a/lib/Abstracts/Observer.php +++ b/lib/Abstracts/Observer.php @@ -3,59 +3,22 @@ namespace Underpin\Abstracts; -use Underpin\Interfaces\Item_With_Dependencies; -use WP_Error; +use Underpin\Helpers\Array_Helper; +use Underpin\Helpers\Processors\Array_Processor; +use Underpin\Traits\With_Dependencies; -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +abstract class Observer implements \Underpin\Interfaces\Observer { -abstract class Observer implements Item_With_Dependencies { + use With_Dependencies; - protected $id; - - public $name = ''; - public $description = ''; - protected $priority = 10; - - protected $deps = []; - - public function __construct( $id ) { - $this->id = $id; + public function __construct( protected string $id, protected int $priority = 10 ) { } - public function get_id() { + /** + * @inheritDoc + */ + public function get_id(): string { return $this->id; } - public function get_priority() { - return $this->priority; - } - - public function get_dependencies() { - return $this->deps; - } - - public function add_dependency( string $dependency_id ) { - return $this->deps[] = $dependency_id; - } - - public function remove_dependency( string $dependency_id ) { - foreach ( $this->deps as $key => $dep ) { - if ( $dep === $dependency_id ) { - unset( $this->deps[ $key ] ); - } - } - } - - abstract public function update( $instance, Storage $args ); - - public function __get( $key ) { - if ( isset( $this->$key ) ) { - return $this->$key; - } else { - return new WP_Error( 'observer_param_not_set', 'The observer value for ' . $key . ' could not be found.' ); - } - } - } \ No newline at end of file diff --git a/lib/Abstracts/Query_Builder.php b/lib/Abstracts/Query_Builder.php new file mode 100644 index 0000000..22c29b1 --- /dev/null +++ b/lib/Abstracts/Query_Builder.php @@ -0,0 +1,11 @@ +validate_item( $key, $value ); - if ( true === $valid ) { - $this[ $key ] = Underpin::make_class( $value, $this->default_factory ); - } else { - $this[ $key ] = $valid; - } - - // If this implements middleware actions, do those things too. - if ( Underpin::has_trait( 'Underpin\Traits\With_Middleware', $this->get( $key ) ) ) { - $this->get( $key )->do_middleware_actions(); - } - - // If this implements registry actions, go ahead and start those up, too. - if ( Underpin::has_trait( 'Underpin\Traits\Feature_Extension', $this->get( $key ) ) ) { - $this->get( $key )->do_actions(); - - Logger::log( - 'debug', - 'loader_actions_ran', - 'The actions for registry called ' . $key . ' ran.', - [ 'key' => $key, 'value' => $value ] - ); - } - - return $valid; - } + protected string $default_factory = ''; /** * @inheritDoc */ - public function validate_item( $key, $value ) { - - if ( is_array( $value ) ) { - $value = $value['class'] ?? $this->default_factory; - } - - if ( $value === $this->abstraction_class || is_subclass_of( $value, $this->abstraction_class ) || $value instanceof $this->abstraction_class ) { - return true; - } - - return Logger::log_as_error( - 'error', - 'invalid_service_type', - 'The specified item could not be instantiated. Invalid instance type', - [ 'ref' => $key, 'value' => $value, 'expects_type' => $this->abstraction_class ] - ); + protected function _add( $key, $value ): void { + parent::_add( $key, $this->get_class( $value ) ); } /** - * Determines if a registry item passes the arguments. - * - * @since 1.3.0 - * - * @param string $item_key Registry item key - * @param array $args List of arguments + * @param $value * - * @return object|\WP_Error The instance, if it matches the filters. Otherwise WP_Error. + * @return object The created class */ - protected function filter_item( string $item_key, array $args ) { - $item = $this->get( $item_key ); - - if ( is_wp_error( $item ) ) { - return $item; - } - - $valid = true; - - foreach ( $args as $key => $arg ) { - // Process the argument key - $processed = explode( '__', $key ); - - // Set the field type to the first item in the array. - $field = $processed[0]; - - // If there was some specificity after a __, use it. - $type = count( $processed ) > 1 ? $processed[1] : 'in'; - - // Bail early if this field is not in this object. - if ( ! property_exists( $item, $field ) ) { - continue; - } - - $object_field = $item->$field; - - // Convert argument to an array. This allows us to always use array functions for checking. - if ( ! is_array( $arg ) ) { - $arg = array( $arg ); - } - - - // Convert field to array. This allows us to always use array functions to check. - if ( ! is_array( $object_field ) ) { - $object_field = array( $object_field ); - } - - // Run the intersection. - $fields = array_intersect( $arg, $object_field ); - - // Check based on type. - switch ( $type ) { - case 'not_in': - $valid = empty( $fields ); - break; - case 'and': - $valid = count( $fields ) === count( $arg ); - break; - default: - $valid = ! empty( $fields ); - break; - } - - if ( false === $valid ) { - break; - } - } - - if ( true === $valid ) { - return $item; - } - - return new \WP_Error( 'item_failed_filter', 'The specified item failed the filter', [ - 'item' => $item, - 'args' => $args, - ] ); + protected function get_class( $value ): object { + return Object_Helper::make_class( $value, $this->default_factory ); } /** - * Pre-filters the list of items. - * - * @since 1.3.0 - * - * @param array $args Arguments to pre-filter by. + * Queries this registry * - * @return int[]|string[] + * @return Registry_Query */ - protected function pre_filter_items( array $args = [] ) { - - // Filter out items, if loader keys are specified - if ( isset( $args['loader_key__in'] ) ) { - $items = array_intersect( array_keys( (array) $this ), $args['loader_key__in'] ); - unset( $args['loader_key__in'] ); - } else { - $items = array_keys( (array) $this ); - } - - return $items; + public function query(): Registry_Query { + return new Registry_Query( $this ); } /** - * Finds the first loader item that matches the provided arguments. - * - * @since 1.3.0 - * - * @param array $args List of filter arguments - * - * @return object|\WP_Error loader item if found, otherwise WP_Error. + * @inheritDoc */ - public function find( array $args = [] ) { - foreach ( $this->pre_filter_items( $args ) as $item_key ) { - $item = $this->filter_item( $item_key, $args ); + public function validate_item( $key, $value ): bool { - if ( ! is_wp_error( $item ) ) { - return $item; - } + if ( is_array( $value ) ) { + $value = $value['class'] ?? $this->default_factory; } - return new \WP_Error( 'item_not_found', 'No item matching the arguments could be found', [ - 'args' => $args, - ] ); - } - - /** - * Queries a loader registry. - * - * @since 1.0.0 - * @since 1.3.0 Filtered items no-longer preserve keys by default. Include "preserve_keys" argument in array if you - * want to preserve keys. - * - * @param array $args - * - * @return object[] Array of registry items. - */ - public function filter( array $args = [] ): array { - $results = []; - - // Filter out items, if loader keys are specified - foreach ( $this->pre_filter_items( $args ) as $item_key ) { - $item = $this->filter_item( $item_key, $args ); + if ( $this->is_registered( $key ) ) { + throw new Invalid_Registry_Item( "The specified key is already registered.", 403, 'error', null, null ); + } - if ( ! is_wp_error( $item ) ) { - if ( isset( $args['preserve_keys'] ) && true === $args['preserve_keys'] ) { - $results[ $item_key ] = $item; - } else { - $results[] = $item; - } - } + if ( $value === $this->abstraction_class || is_subclass_of( $value, $this->abstraction_class ) || $value instanceof $this->abstraction_class ) { + return true; } - return $results; + throw new Invalid_Registry_Item( 'The specified item could not be instantiated. Invalid instance type', 403, 'error', null, null ); } + } \ No newline at end of file diff --git a/lib/Abstracts/Registries/Registry.php b/lib/Abstracts/Registries/Registry.php index bc5869b..88ab59d 100644 --- a/lib/Abstracts/Registries/Registry.php +++ b/lib/Abstracts/Registries/Registry.php @@ -3,124 +3,189 @@ * Registry Class. * This is used any time a set of identical things are stored. * - * @since 1.0.0 * @package Underpin\Abstracts */ namespace Underpin\Abstracts\Registries; -use ArrayIterator; -use WP_Error; - -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +use Underpin\Exceptions\Invalid_Registry_Item; +use Underpin\Exceptions\Operation_Failed; +use Underpin\Exceptions\Unknown_Registry_Item; +use Underpin\Helpers\Array_Helper; +use Underpin\Helpers\String_Helper; +use Underpin\Interfaces\Can_Convert_To_Array; +use Underpin\Interfaces\Identifiable; /** * Class Registry. * - * @since 1.0.0 * @package Underpin\Abstracts */ -abstract class Registry extends ArrayIterator { +abstract class Registry implements Can_Convert_To_Array { - /** - * A human-readable description of this event type. - * This is used in debug logs to make it easier to understand why this exists. - * - * @var string - */ - public $description = ''; + protected array $storage = []; /** - * A human-readable name for this event type. - * This is used in debug logs to make it easier to understand what this is. + * Validates an item. This runs just before adding items to the registry. * - * @var string - */ - public $name = ''; - - /** - * Set to true to force this registry to skip logging. * - * @since 1.3.1 + * @param string $key The key to validate. + * @param mixed $value The value to validate. * - * @var bool + * @return boolean true if the item is valid. + * @throws Invalid_Registry_Item */ - protected $skip_logging = false; + abstract protected function validate_item( string $key, mixed $value ): bool; /** - * Registry constructor. + * Adds the item to the registry. + * + * @param string $key The key to validate. + * @param mixed $value The value to validate. + * + * @return void */ - public function __construct() { - parent::__construct(); - $this->set_default_items(); + protected function _add( string $key, mixed $value ): void { + $this->storage[ $key ] = $value; } /** - * Sets the default items for the registry. - */ - abstract protected function set_default_items(); - - /** - * Validates an item. This runs just before adding items to the registry. + * Returns true if an item is registered to this registry. * - * @since 1.0.0 + * @param string $key The key to check. * - * @param string $key The key to validate. - * @param mixed $value The value to validate. - * @return true|WP_Error true if the item is valid, WP_Error otherwise. + * @return bool True if registered, otherwise false. */ - abstract protected function validate_item( $key, $value ); - - protected function _add( $key, $value ) { - return $this[ $key ] = $value; + public function is_registered( string $key ): bool { + return isset( $this->storage[ $key ] ); } /** - * Adds an item to the registry + * Validates, and adds an item to the registry. * - * @since 1.0.0 * * @param string $key The key to validate. * @param mixed $value The value to validate. * - * @return true|WP_Error true if the item is valid, WP_Error otherwise. + * @return static The current instance + * @throws Operation_Failed */ - public function add( $key, $value ) { - $valid = $this->validate_item( $key, $value ); + public function add( string $key, mixed $value ): static { + try { + $valid = $this->validate_item( $key, $value ); + } catch ( Invalid_Registry_Item $e ) { + throw new Operation_Failed( 'Item is not valid and could not be added', previous: $e ); + } if ( true === $valid ) { $this->_add( $key, $value ); } - return $valid; + return $this; } /** * Retrieves a registered item. * * @param string $key The identifier for the item. + * * @return mixed the item value. + * @throws Unknown_Registry_Item */ - public function get( $key ) { - if ( isset( $this[ $key ] ) ) { - return $this[$key]; + public function get( string $key ): mixed { + if ( $this->is_registered( $key ) ) { + return $this->storage[ $key ]; } else { - $error = new WP_Error( - 'key_not_set', - 'Specified key is not set.', - [ - 'key' => $key, - 'name' => $this->name, - 'description' => $this->description, - 'registry_type' => get_called_class(), - ] - ); - - return $error; + throw new Unknown_Registry_Item( $key, get_called_class() ); } } + /** + * {@inheritDoc} + */ + public function to_array(): array { + return $this->storage; + } + + /** + * Maps through items in this registry. + * + * @param callable $callback + * + * @return array + */ + public function each( callable $callback ): array { + return Array_Helper::each( $this->to_array(), $callback ); + } + + /** + * Plucks a value from an array, if it is an array. Falls back to default value if not-set. + * + * @param string $key + * @param mixed|false $default + * + * @return array + */ + public function pluck( string $key, mixed $default = false ): mixed { + return Array_Helper::pluck_recursive( $this->to_array(), $key, $default ); + } + + /** + * Constructs a registry using an array of items keyed by their ID. + * + * @throws Operation_Failed + */ + public function seed( array $items ): static { + $instance = clone $this; + $instance->storage = []; + + $is_assoc = Array_Helper::is_associative( $items ); + + foreach ( $items as $key => $item ) { + if ( ! $is_assoc && $item instanceof Identifiable ) { + $instance->add( $item->get_id(), $item ); + } elseif ( ! $is_assoc ) { + $instance->add( String_Helper::create_hash( $item ), $item ); + } else { + $instance->add( $key, $item ); + } + } + + return $instance; + } + + /** + * Reduces the registry to a single value. + * + * @param callable $callback + * @param mixed $initial + * + * @return mixed + */ + public function reduce( callable $callback, mixed $initial ): mixed { + return Array_Helper::reduce( $this->to_array(), $callback, $initial ); + } + + /** + * Filters items using a callback function. + * + * @param callable $callback + * + * @return static + */ + public function filter( callable $callback ): static { + $filtered = Array_Helper::filter( $this->to_array(), $callback ); + + return $this->seed( $filtered ); + } + + /** + * Merges multiple registries into a single registry. + * + * @throws Operation_Failed + */ + public function merge( Registry ...$items ): static { + return $this->seed( Array_Helper::merge( $this->to_array(), ...Array_Helper::map( $items, fn ( Registry $item ) => $item->to_array() ) ) ); + } } \ No newline at end of file diff --git a/lib/Abstracts/Registry_Mutator.php b/lib/Abstracts/Registry_Mutator.php new file mode 100644 index 0000000..faab816 --- /dev/null +++ b/lib/Abstracts/Registry_Mutator.php @@ -0,0 +1,19 @@ +middleware->add( $middleware->get_id(), $middleware ); + + return $this; + } + + /** + * Registers a typed URL param to be included in this request. + * + * @param Param $param The param to include + * @param bool $required Set to true if this param is required in the request. + * + * @return $this + * @throws Operation_Failed + */ + protected function add_param( Param $param, bool $required = false ): static { + $this->signature->add( $param->get_id(), $param ); + + if ( $required ) { + $this->add_middleware( new Has_Param_Middleware( $param->get_id() ) ); + } + + return $this; + } + + public function set_request( Request $request ): static { + $this->request = $request; + + return $this; + } + + public function get_request(): Request { + return $this->request; + } + + /** + * Does the middleware actions for this request. + * + * @return void + */ + public function do_middleware_actions(): void { + $this->middleware->each( fn ( Request_Middleware $middleware ) => $middleware->run( $this->request ) ); + } + + /** + * Set the response. + * + * @param mixed $value + * + * @return $this + */ + protected function set_response( mixed $value ): static { + $this->response = $value; + + return $this; + } + + public function get_response(): mixed { + return $this->response; + } + + /** + * Retrieves the list of params used in this action. + * + * @return Param[] + */ + public function get_signature(): array { + return $this->signature->to_array(); + } + +} diff --git a/lib/Abstracts/Sort_Method.php b/lib/Abstracts/Sort_Method.php new file mode 100644 index 0000000..97d0bfd --- /dev/null +++ b/lib/Abstracts/Sort_Method.php @@ -0,0 +1,22 @@ +params[ $key ] ) ) { - return $this->params[ $key ]; - } - - return new WP_Error( 'param_not_set', 'The provided param ' . $key . ' is not set' ); - } - -} \ No newline at end of file diff --git a/lib/Abstracts/Underpin.php b/lib/Abstracts/Underpin.php deleted file mode 100644 index 3ca5f85..0000000 --- a/lib/Abstracts/Underpin.php +++ /dev/null @@ -1,790 +0,0 @@ -$method( ...$arguments ); - } - - // Try and get the loader. - $loader = $this->loader_registry->get( $method ); - - // If the loader was found, bail early and return it. - if ( ! is_wp_error( $loader ) ) { - return $loader; - } - - // Otherwise, return and log an error. - if ( is_wp_error( $loader ) ) { - $loader = new WP_Error( - 'method_not_found', - "The method could not be called. Either register this item as a loader, install an extension, or create a method for this call.", - [ - 'method' => $method, - 'args' => $arguments, - 'backtrace' => debug_backtrace(), - ] - ); - - return Logger::instance()->log_wp_error( 'warning', $loader ); - } - return $loader; - } - - /** - * Minimum PHP Version Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function minimum_php_version() { - return $this->minimum_php_version; - } - - /** - * Minimum WP Version Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function minimum_wp_version() { - return $this->minimum_wp_version; - } - - /** - * Plugin Version. - * - * @since 1.0.0 - * - * @return string - */ - public function version() { - return $this->version; - } - - /** - * URL Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function url() { - return trailingslashit( $this->url ); - } - - /** - * CSS URL Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function css_url() { - return trailingslashit( $this->css_url ); - } - - /** - * JS URL Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function js_url() { - return trailingslashit( $this->js_url ); - } - - /** - * Directory Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function dir() { - return trailingslashit( $this->dir ); - } - - /** - * __FILE__ Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function file() { - return $this->file; - } - - /** - * Template Directory Getter. - * - * @since 1.0.0 - * - * @return string - */ - public function template_dir() { - return trailingslashit( $this->template_dir ); - } - - /** - * Loader registry getter. - * - * @since 1.2.0 - * - * @return Object_Registry - */ - public function loaders() { - return $this->loader_registry; - } - - /** - * Determines if debug mode is enabled. - * - * @since 1.0.0 - * - * @return bool True if debug mode is enabled, otherwise false. - */ - public static function is_debug_mode_enabled() { - $is_invalid_request = defined( 'WP_TESTS_DOMAIN' ) || defined( 'WP_CLI' ) || wp_doing_ajax() || wp_doing_cron() || defined( 'REST_REQUEST' ); - - // Bail early if this is not a valid request for debug mode. - if ( $is_invalid_request ) { - return false; - } - - $debug_enabled_with_querystring = isset( $_GET['underpin_debug'] ) && '1' === $_GET['underpin_debug']; - - if ( $debug_enabled_with_querystring ) { - return true; - } - - // If WP DEBUG is enabled, turn on debug mode. - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { - return true; - } - - return false; - } - - - /** - * Fetches the specified class, and constructs the class if it hasn't been constructed yet. - * - * @since 1.0.0 - * - * @param $class - * - * @return mixed - */ - protected function _get_class( $class ) { - if ( ! isset( $this->class_registry[ $class ] ) ) { - if ( class_exists( $class ) ) { - $this->class_registry[ $class ] = new $class; - } else { - $this->class_registry[ $class ] = new WP_Error( - 'class_could_not_be_found', - 'The specified class could not be located', - [ 'class' => $class ] - ); - } - } - - return $this->class_registry[ $class ]; - } - - public static function export() { - $results = []; - - foreach ( Underpin::$instances as $key => $instance ) { - if ( $instance instanceof Underpin ) { - $results = Underpin::get_by_id( $key )->export_registered_items( $results ); - } - } - - return $results; - } - - /** - * Retrieves a list of registered loader items from the registry. - * - * @since 1.0.0 - * - * @return array - */ - public function export_registered_items( $results = [] ) { - foreach ( $this->class_registry as $key => $class ) { - if ( $class instanceof Object_Registry ) { - if ( ! empty( $class ) ) { - ob_start(); - foreach ( $class as $registered_key => $registered_class ) { - echo "******************************"; - if ( isset( $registered_class->name ) ) { - echo "\n" . $registered_class->name; - unset( $registered_class->name ); - } - if ( isset( $registered_class->description ) ) { - echo "\n" . $registered_class->description; - unset( $registered_class->description ); - } - echo "\n" . $registered_key; - - echo "\n******************************\n"; - - if ( method_exists( $registered_class, 'export' ) ) { - var_dump( $registered_class->export() ); - } else { - var_dump( $registered_class ); - } - } - - $key = explode( '\\', $key ); - $key = array_pop( $key ); - $results[ $key ] = ob_get_clean(); - } - } - } - - return $results; - } - - /** - * Sends a notice if the WordPress or PHP version are below the minimum requirement. - * - * @since 1.0.0 - */ - public function below_version_notice() { - global $wp_version; - - if ( version_compare( $wp_version, $this->minimum_wp_version, '<' ) ) { - echo '
-

' . __( sprintf( "Underpin plugin is not activated. The plugin requires at least WordPress %s to function.", $this->minimum_wp_version() ), 'underpin' ) . '

-
'; - } - - if ( version_compare( phpversion(), $this->minimum_php_version, '<' ) ) { - echo '
-

' . __( sprintf( "Underpin plugin is not activated. The plugin requires at least PHP %s to function.", $this->minimum_php_version() ), 'underpin' ) . '

-
'; - } - } - - /** - * Registers the autoloader. - * - * @sicne 1.0.0 - * - * @return bool|string - */ - protected function _setup_autoloader() { - try { - spl_autoload_register( function ( $class ) { - $class = explode( '\\', $class ); - - $root = plugin_dir_path($this->file() ) . 'lib/'; - - $root_namespace = array_shift( $class ); - - $file_name = array_pop( $class ); - $directory = str_replace( '_', '-', implode( DIRECTORY_SEPARATOR, $class ) ); - $file = $root . $directory . '/' . $file_name . '.php'; - - // If the file exists, use it. - if ( file_exists( $file ) ) { - require_once $file; - - return true; - } - - return false; - } ); - } catch ( Exception $e ) { - Logger::instance()->log_exception( 'autoload_failed', $e ); - - return $e->getMessage(); - } - - return false; - } - - /** - * Checks if the PHP version meets the minimum requirements. - * - * @since 1.0.0 - * - * @return bool True if the minimum requirements are met, false otherwise. - */ - public function supports_php_version() { - return version_compare( phpversion(), $this->minimum_php_version, '>=' ); - } - - /** - * Checks if the WP version meets the minimum requirements. - * - * @since 1.0.0 - * - * @return bool True if the minimum requirements are met, false otherwise. - */ - public function supports_wp_version() { - global $wp_version; - - return version_compare( $wp_version, $this->minimum_wp_version, '>=' ); - } - - /** - * Checks if all minimum requirements are met. - * - * @since 1.0.0 - * - * @return bool True if the minimum requirements are met, false otherwise. - */ - public function plugin_is_supported() { - return $this->supports_wp_version() && $this->supports_php_version(); - } - - /** - * A set of actions that run when this plugin does not meet the minimum requirements. - * - * @since 1.0.0 - * - * @return void - */ - protected function unsupported_actions() { - global $wp_version; - - self::$instances[ __CLASS__ ] = new WP_Error( - 'minimum_version_not_met', - __( sprintf( - "The Underpin plugin requires at least WordPress %s, and PHP %s.", - $this->minimum_wp_version, - $this->minimum_php_version - ), 'underpin' ), - array( 'current_wp_version' => $wp_version, 'php_version' => phpversion() ) - ); - - add_action( 'admin_notices', array( $this, 'below_version_notice' ) ); - } - - /** - * Checks to see if the class, or any of its parents, uses the specified trait. - * - * @since 1.3.0 - * - * @param string $trait The trait to check for - * @param object|string|false $class The class to check. - * @return bool true if the class uses the specified trait, otherwise false. - */ - public static function has_trait( $trait, $class ) { - - if ( false === $class ) { - return false; - } - - $traits = class_uses( $class ); - - if ( in_array( $trait, $traits ) ) { - return true; - } - - while ( get_parent_class( $class ) ) { - $class = get_parent_class( $class ); - - $has_trait = self::has_trait( $trait, $class ); - - if ( true === $has_trait ) { - return true; - } - } - - return false; - } - - /** - * Actions that run when this plugin meets the specified minimum requirements. - * - * @since 1.0.0 - * - * @return void - */ - protected function setup() { - - // Set up the autoloader for everything else. - $this->_setup_autoloader(); - $this->loader_registry = new Object_Registry( [ - 'abstraction_class' => '\Underpin\Abstracts\Registries\Object_Registry', - 'default_factory' => '\Underpin\Factories\Object_Registry', - 'registry_id' => $this->get_registry_key(), - ] ); - - /** - * Fires just before the bootstrap starts up. - * - * @since 1.0.0 - */ - $this->notify( 'setup', [ 'file' => $this->file(), 'class' => get_called_class() ] ); - - // Set up classes that register things. - $this->_setup(); - - /** - * Fires once the bootstrap is ready. - * - * @since 1.0.0 - */ - $this->notify( 'ready', [ 'file' => $this->file(), 'class' => get_called_class() ] ); - - } - - /** - * Setup plugin params using the provided __FILE__ - * - * @since 1.0.0 - * - * @return void - */ - protected function _setup_params( $file ) { - - // Root file for this plugin. Used in activation hooks. - $this->file = $file; - - // Root directory for this plugin. - $this->dir = plugin_dir_path( $file ); - - // The URL for this plugin. Used in asset loading. - if ( false === strpos( "/wp-content" . DIRECTORY_SEPARATOR . "themes/", $this->dir ) ) { - $this->url = plugin_dir_url( $file ); - } else { - $template = '/' . get_template() . '/'; - $template_dir_position = strpos( dirname( $file ), $template ) + strlen( $template ); - $root = trailingslashit( get_stylesheet_directory_uri() ); - $this->url = trailingslashit( $root . substr( dirname( $file ), $template_dir_position ) ); - } - - // The CSS URL for this plugin. Used in asset loading. - $this->css_url = $this->url . 'build'; - - // The JS URL for this plugin. Used in asset loading. - $this->js_url = $this->url . 'build'; - - // The template directory. Used by the template loader to determine where templates are stored. - $this->template_dir = $this->dir . 'templates/'; - } - - /** - * Retrieve the translation of $text. - * - * If there is no translation, or the text domain isn't loaded, the original text is returned. - * - * @since 1.0.0 - * - * @param string $text Text to translate. - * - * @return string Translated text. - */ - public function __( $text ) { - return __( $text, $this->text_domain ); - } - - /** - * Helper function used to construct factory classes from a standard array syntax. - * - * @since 1.2 - * - * @param mixed $value The value used to generate the class. - * Can be an array with "class" and "args", an associative array, a string, or a class - * instance. If it is an array with "class" and "args", make_class will construct the - * factory specified in - * "class" using the provided "args" - * If it is an associative array, make_class will construct the default factory, - * passing the array of arguments to the constructor. If it is a string, make_class - * will try to instantiate the class with no args. If it is already a class, - * make_class will simply return the class directly. - * @param string $default_factory The default factory to use if a class is not provided in $value. - * - * @return mixed The instantiated class. - */ - public static function make_class( $value = [], $default_factory = 'Underpin\Factories\Underpin_Instance' ) { - // If the value is a string, assume it's a class reference. - if ( is_string( $value ) ) { - $class = new $value; - - // If the value is an array, the class still needs defined. - } elseif ( is_array( $value ) ) { - - // If the class is specified, construct the class from the specified value. - if ( isset( $value['class'] ) ) { - $class = $value['class']; - $args = isset( $value['args'] ) ? $value['args'] : []; - - // Otherwise, fallback to the default, and use the value as an array of arguments for the default. - } else { - - $class = $default_factory; - $args = $value; - } - - $is_assoc = count( array_filter( array_keys( $args ), 'is_string' ) ) > 0; - // Convert single-level associative array to first argument using the array. - if ( $is_assoc ) { - $args = [ $args ]; - } - - $class = new $class( ...$args ); - - // Otherwise, assume the class is already instantiated, and return it directly. - } else { - $class = $value; - } - - return $class; - } - - /** - * Retrieves the registry key for this instance. - * - * @since 1.2.0 - * - * @return string The registry hash. - */ - public function get_registry_key( $file = '', $class = '' ) { - $file = empty( $file ) ? $this->file() : $file; - $class = empty( $class ) ? get_called_class() : $class; - return md5( $class . $file ); - } - - /** - * Fetch an Underpin Instance by the registry key. - * - * @since 1.2 - * - * @param string $key The instance key. - * - * @return Underpin|WP_Error The underpin instance if found, otherwise WP_Error. - */ - public static function get_by_id( $key ) { - if ( isset( self::$instances[ $key ] ) ) { - return self::$instances[ $key ]; - } - - return new WP_Error( - 'instance_not_found', - 'The instance key provided is not associated with an Underpin instance', - [ 'key' => $key ] - ); - } - - /** - * Fires up the plugin. - * - * @since 1.0.0 - * - * @param string $file The complete path to the root file in this plugin. Usually the __FILE__ const. - * - * @return self - */ - public function get( $file, $class = '' ) { - $key = $this->get_registry_key( $file, $class ); - if ( ! isset( self::$instances[ $key ] ) ) { - $this->_setup_params( $file ); - - // First, check to make sure the minimum requirements are met. - if ( $this->plugin_is_supported() ) { - self::$instances[ $key ] = $this; - - // Setup the plugin, if requirements were met. - self::$instances[ $key ]->setup(); - - } else { - // Run unsupported actions if requirements are not met. - $this->unsupported_actions(); - } - } - - return self::$instances[ $key ]; - } - -} diff --git a/lib/Enums/Direction.php b/lib/Enums/Direction.php new file mode 100644 index 0000000..23acbc1 --- /dev/null +++ b/lib/Enums/Direction.php @@ -0,0 +1,12 @@ +value"; + } + + public function key(): string { + return $this->field( 'filter_enum_key' ); + } + +} \ No newline at end of file diff --git a/lib/Enums/Logger_Events.php b/lib/Enums/Logger_Events.php new file mode 100644 index 0000000..af5848f --- /dev/null +++ b/lib/Enums/Logger_Events.php @@ -0,0 +1,13 @@ + $plugin_id ] ); + } + +} \ No newline at end of file diff --git a/lib/Exceptions/Unknown_Registry_Item.php b/lib/Exceptions/Unknown_Registry_Item.php new file mode 100644 index 0000000..c503f95 --- /dev/null +++ b/lib/Exceptions/Unknown_Registry_Item.php @@ -0,0 +1,12 @@ +set_values( $args ); - $this->params = $args; - $this->reset(); - } - - public function get_state() { - return $this->state; - } - - public function reset() { - $this->update( $this->default ); - } - - public function update( $state ) { - $valid = $this->is_valid( $state ); - - if ( ! is_wp_error( $valid ) ) { - $this->state = $state; - return true; - } else { - Logger::log_wp_error( 'error', $valid ); - return $valid; - } - } - - protected function is_valid( $state ) { - return $this->set_callable( $this->valid_callback, $state, $this ); - } - - public function __get( $key ) { - if ( $key === 'state' ) { - return $this->get_state(); - } - - return parent::__get($key); - } - -} \ No newline at end of file diff --git a/lib/Factories/Broadcaster.php b/lib/Factories/Broadcaster.php new file mode 100644 index 0000000..7b1682a --- /dev/null +++ b/lib/Factories/Broadcaster.php @@ -0,0 +1,109 @@ +observer_registry = Mutable_Collection::make( Mutable_Collection::class ); + } + + /** + * @param string $key + * @param callable $observer + * + * @return $this + * @throws Operation_Failed + * @throws Unknown_Registry_Item + */ + public function attach( string $key, callable $observer ): static { + try { + $this->observer_registry->get( $key ); + } catch ( Operation_Failed ) { + $this->observer_registry->add( $key, new Registry( fn ( $item ) => is_callable( $item ) ) ); + } + + $id = count( $this->observer_registry->to_array() ); + $this->observer_registry->get( $key )->add( $id, $observer ); + + Logger::log( + 'info', + new Log_Item( + code : 'event_attached', + message: 'Event attached', + context: 'registry_key', + ref : $key, + data : [ + 'subject' => get_called_class(), + 'id' => $id, + ] + ) + ); + + return $this; + } + + /** + * @throws Operation_Failed + */ + public function detach( string $key, $observer_id ): static { + try { + /* @var Mutable_Collection_With_Remove $item */ + $item = $this->observer_registry->get( $key ); + } catch ( Unknown_Registry_Item $e ) { + return $this; + } + + foreach ( $item as $iterator => $observer ) { + if ( $observer->id === $observer_id ) { + Logger::log( + 'info', + new Log_Item( + code : 'event_detached', + message: 'Event detached', + context: 'registry_key', + ref : $key, + data : [ + 'subject' => get_called_class(), + 'name' => $observer->name, + 'description' => $observer->description, + ] + ) + ); + $item->remove( $iterator ); + } + } + + return $this; + } + + public function broadcast( string $key, ?Data_Provider $args = null ): void { + try { + /* @var Immutable_Collection $item */ + $item = $this->observer_registry->get( $key ); + if ( false === $args || empty( $item->to_array() ) ) { + return; + } + /* @var callable $observer */ + foreach ( $item->to_array() as $observer ) { + $observer( $args ); + } + } catch ( Operation_Failed|Unknown_Registry_Item ) { + return; + } + + } + +} \ No newline at end of file diff --git a/lib/Factories/Controller.php b/lib/Factories/Controller.php new file mode 100644 index 0000000..66d7d85 --- /dev/null +++ b/lib/Factories/Controller.php @@ -0,0 +1,107 @@ +|null $get + * @param class-string|null $post + * @param class-string|null $put + * @param class-string|null $delete + */ + public function __construct( + public readonly string $route, + protected ?string $get = null, + protected ?string $post = null, + protected ?string $put = null, + protected ?string $delete = null + ) { + $this->middleware = Mutable_Collection::make( Request_Middleware::class, Request_Middleware::class ); + $this->signature = new Param_Collection; + } + + /** + * Adds middleware. + * + * @throws Operation_Failed + */ + public function add_middleware( Request_Middleware $middleware ): static { + $this->middleware->add( $middleware->get_id(), $middleware ); + + return $this; + } + + /** + * Registers a typed URL param to be included in this request. + * + * @param Param $param The param to include + * @param bool $required Set to true if this param is required in the request. + * + * @return $this + * @throws Operation_Failed + */ + public function add_param( Param $param, bool $required = false ): static { + $this->signature->add( $param->get_id(), $param ); + + if ( $required ) { + $this->middleware->add( 'required_param_' . $param->get_id(), new Has_Param_Middleware( $param->get_id() ) ); + } + + return $this; + } + + /** + * Gets the action in this controller from the request type. + * + * @param Rest $type The request type. + * + * @return Rest_Action + */ + public function get_action( Rest $type ): Rest_Action { + $type = strtolower( $type->value ); + + return new $this->$type( $this->middleware, $this->signature ); + } + + /** + * Gets this controller's ID + * + * @return string + */ + public function get_id(): string { + return $this->route; + } + + /** + * Converts this endpoint to an array + * + * @return array + */ + public function to_array(): array { + return Array_Helper::where_not_null( [ + Rest::Get->value => $this->get, + Rest::Post->value => $this->post, + Rest::Put->value => $this->put, + Rest::Delete->value => $this->delete, + ] ); + } + +} diff --git a/lib/Factories/Data_Providers/Accumulator.php b/lib/Factories/Data_Providers/Accumulator.php new file mode 100644 index 0000000..7942f86 --- /dev/null +++ b/lib/Factories/Data_Providers/Accumulator.php @@ -0,0 +1,71 @@ +valid_callback = $valid_callback ?? fn () => true; + $this->reset(); + } + + /** + * Retrieves the state. + * + * @return mixed + */ + public function get_state(): mixed { + return $this->state; + } + + /** + * @throws Invalid_Callback + */ + public function reset() { + $this->update( $this->default ); + } + + /** + * Updates the accumulator state. + * + * @param $state + * + * @return bool + * @throws Invalid_Callback + */ + public function update( $state ): bool { + $valid = $this->is_valid( $state ); + + if ( true === $valid ) { + $this->state = $state; + return true; + } + + return false; + } + + /** + * Checks to see if the instance is valid. + * + * @param $state + * + * @return boolean + * @throws Invalid_Callback + */ + protected function is_valid( $state ): bool { + return call_user_func( $this->valid_callback, $state ); + } + +} \ No newline at end of file diff --git a/lib/Factories/Data_Providers/Bool_Provider.php b/lib/Factories/Data_Providers/Bool_Provider.php new file mode 100644 index 0000000..68f04c9 --- /dev/null +++ b/lib/Factories/Data_Providers/Bool_Provider.php @@ -0,0 +1,14 @@ +builder; + } + +} \ No newline at end of file diff --git a/lib/Factories/Data_Providers/Plugin_Provider.php b/lib/Factories/Data_Providers/Plugin_Provider.php new file mode 100644 index 0000000..d35cf7b --- /dev/null +++ b/lib/Factories/Data_Providers/Plugin_Provider.php @@ -0,0 +1,23 @@ +plugin; + } + +} \ No newline at end of file diff --git a/lib/Factories/Data_Providers/String_Provider.php b/lib/Factories/Data_Providers/String_Provider.php new file mode 100644 index 0000000..a504a15 --- /dev/null +++ b/lib/Factories/Data_Providers/String_Provider.php @@ -0,0 +1,13 @@ +items = $items; - } - - private function get_dependencies( Item_With_Dependencies $item ) { - $deps = $item->get_dependencies(); - foreach ( $item->get_dependencies() as $dep ) { - $dep_item = $this->items->find( [ 'id' => $dep ] ); - if ( ! is_wp_error( $dep_item ) ) { - /** @var Item_With_Dependencies $dep_item */ - $deps = array_merge( $deps, $this->get_dependencies( $dep_item ) ); - } - } - - return $deps; - } - - public function filter_dependencies() { - $dependency_ids = wp_list_pluck( (array) $this->items, 'id' ); - $queue = (array) $this->items; - $items = []; - $queued_deps = []; - - while ( ! empty( $queue ) ) { - /* @var Item_With_Dependencies $item */ - $item = $queue[0]; - - // If this item depends on something that doesn't exist, skip it. - $unmet_dependencies = array_diff( $this->get_dependencies( $item ), $dependency_ids ); - - if ( ! empty( $unmet_dependencies ) ) { - Logger::log( - 'debug', - 'observer_detached', - 'An event was detached because it has unmet dependencies', - [ - 'item_id' => $item->id, - 'unmet_dependencies' => $unmet_dependencies, - ] - ); - array_shift( $queue ); - continue; - } - - - $dependencies_not_added_yet = array_diff( $item->get_dependencies(), $queued_deps ); - - // If all dependencies have not been added yet, push this to the bottom of the queue - if ( ! empty( $dependencies_not_added_yet ) ) { - $queue_item = array_shift( $queue ); - $queue[] = $queue_item; - continue; - } - - // If all dependencies have been added, add this after the last dependency - if ( empty( $dependencies_not_added_yet ) ) { - $last_dependency_key = $this->get_last_dependency( $item, $items ); - if(0 === $last_dependency_key){ - array_unshift($items, $item); - } else { - $items = array_merge( - array_slice( $items, 0, $last_dependency_key + 1 ), - [ $item ], - array_slice( $items, $last_dependency_key + 1, count( $items ) - $last_dependency_key + 1 ) - ); - } - $queued_deps[] = $item->get_id(); - array_shift( $queue ); - } - } - - return $items; - } - - protected function get_last_dependency( Item_With_Dependencies $item, $items ) { - $last_dependency = 0; - foreach ( $items as $key => $value ) { - /* @var Item_With_Dependencies $value */ - $found_dependencies = in_array( $value->get_id(), $item->get_dependencies() ); - if ( ! empty( $found_dependencies ) ) { - $last_dependency = $key; - continue; - } - - // If both items have the same dependencies, use priority. - if ( ! empty( array_intersect( $value->get_dependencies(), $item->get_dependencies() ) ) ) { - if ( $item->get_priority() > $value->get_priority() ) { - $last_dependency = $key; - } - } - } - - return $last_dependency; - } - - public static function prepare( $items ) { - $instance = new self( $items ); - return $instance->filter_dependencies(); - } - -} \ No newline at end of file diff --git a/lib/Factories/Event_Type.php b/lib/Factories/Event_Type.php new file mode 100644 index 0000000..014c283 --- /dev/null +++ b/lib/Factories/Event_Type.php @@ -0,0 +1,186 @@ +broadcast( Logger_Item_Events::write_events->name, $this ); + } + + /** + * Enqueues an event to be logged in the system. + * + * + * @param Interfaces\Log_Item $item The item to log + * + * @return Log_Item|null The logged item. + */ + public function log( Interfaces\Log_Item $item ): ?Interfaces\Log_Item { + Logger::mute(); + try { + $this->events[] = $item->set_type( $this ); + + $this->broadcast( Logger_Item_Events::event_logged, $item ); + } catch ( Invalid_Registry_Item|Operation_Failed $e ) { + } + Logger::unmute(); + + return $item; + } + + /** + * Logs an error from an exception object. + * + * + * @param Exception $exception Exception instance to log. + * @param string|int|null $ref + * @param array $data array Data associated with this error message + * + * @return Log_Item|null The logged item. + */ + public function log_exception( Exception $exception, string|int|null $ref = null, array $data = array() ): ?Log_Item { + return $this->log( new Log_Item( code: $exception->getCode(), message: $exception->getMessage(), ref: $ref, data: $data ) ); + } + + /** + * @return string + */ + function get_type(): string { + return $this->type; + } + + /** + * @return int + */ + function get_volume(): int { + return $this->volume; + } + + /** + * @return string + */ + function get_group(): string { + return $this->group; + } + + /** + * @return string + */ + function get_psr_level(): string { + return $this->psr_level; + } + + public function to_array(): array { + return $this->events; + } + + protected function broadcast( Logger_Item_Events $id, ?Data_Provider $provider = null ): static { + $this->get_broadcaster()->broadcast( $id->name, $provider ); + + return $this; + } + + /** + * @param Logger_Item_Events $key + * @param Observer $observer + * + * @return $this + */ + public function attach( Logger_Item_Events $key, Observer $observer ): static { + $this->get_broadcaster()->attach( $key->name, $observer ); + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Factories/Event_Type_Instance.php b/lib/Factories/Event_Type_Instance.php deleted file mode 100644 index b54dcec..0000000 --- a/lib/Factories/Event_Type_Instance.php +++ /dev/null @@ -1,37 +0,0 @@ -set_values($args); - - parent::__construct(); - } - -} \ No newline at end of file diff --git a/lib/Factories/Head_Tag.php b/lib/Factories/Head_Tag.php new file mode 100644 index 0000000..f34fd80 --- /dev/null +++ b/lib/Factories/Head_Tag.php @@ -0,0 +1,68 @@ +middleware = Mutable_Collection::make( Request_Middleware::class ); + } + + public function add_middleware( Request_Middleware $middleware ): static { + $this->middleware->add( $middleware->get_id(), $middleware ); + + return $this; + } + + public function set_request( Request $request ): static { + $this->request = $request; + + return $this; + } + + public function get_request(): Request { + return $this->request; + } + + public function get_id(): string|int { + return $this->id; + } + + public function get_tag(): Tag { + return $this->tag; + } + + /** + * @return void + * @throws Middleware_Exception + */ + public function do_middleware_actions(): void { + //TODO: RENAME REST MIDDLEWARE TO SOMETHING MORE-GENERIC + $this->middleware->each( function ( Request_Middleware $middleware ) { + $middleware->run( $this->request ); + } ); + } + + public function to_string(): string { + return $this->get_tag()->to_string(); + } + + public function __toString(): string { + return $this->to_string(); + } + +} \ No newline at end of file diff --git a/lib/Factories/Header.php b/lib/Factories/Header.php new file mode 100644 index 0000000..31c0360 --- /dev/null +++ b/lib/Factories/Header.php @@ -0,0 +1,71 @@ +id ) { + $this->id = String_Helper::create_hash( $this->to_string() ); + } + + return $this->id; + } + + /** + * Creates a new instance from a header string. + * + * @param string $header + * + * @return Header + */ + public static function from_string( string $header ): Header { + $key = String_Helper::before( $header, ':' ); + $value = trim( String_Helper::after( $header, ':' ) ); + + return new static( $key, $value ); + } + + /** + * Gets the header value. + * + * @return mixed + */ + public function get_value(): mixed { + return $this->value; + } + + /** + * Gets the header key. + * + * @return string + */ + public function get_key(): string { + return $this->key; + } + + public function to_string(): string { + return $this->get_key() . ': ' . $this->get_value(); + } + + public function __toString(): string { + return $this->to_string(); + } + +} diff --git a/lib/Factories/Html_Attribute.php b/lib/Factories/Html_Attribute.php new file mode 100644 index 0000000..794bfbc --- /dev/null +++ b/lib/Factories/Html_Attribute.php @@ -0,0 +1,38 @@ +value ?? ''; + } + + public function set_value( string $value ): static { + $this->value = $value; + + return $this; + } + + public function get_id(): string|int { + return $this->id; + } + + public function to_string(): string { + return $this->get_value() ? $this->get_id() . '="' . $this->get_value() . '"' : $this->get_id(); + } + + public function __toString(): string { + return $this->to_string(); + } + +} \ No newline at end of file diff --git a/lib/Factories/Log_Item.php b/lib/Factories/Log_Item.php index c5debe4..8654415 100644 --- a/lib/Factories/Log_Item.php +++ b/lib/Factories/Log_Item.php @@ -3,7 +3,6 @@ * Single Log item instance. * Handles output and formatting for log item. * - * @since 1.0.0 * @package Underpin\Factories */ @@ -11,126 +10,96 @@ namespace Underpin\Factories; -use Underpin\Abstracts\Event_Type; -use WP_Error; - -if ( ! defined( 'ABSPATH' ) ) { - exit; -} - /** * Class Log_Item * - * @since 1.0.0 * @package Underpin\Factories */ -class Log_Item { - - /** - * Event code. - * - * @since 1.0.0 - * - * @var string Event code - */ - public $code; - - /** - * Message - * - * @since 1.0.0 - * - * @var string Message. - */ - public $message; - - /** - * Ref. - * - * @since 1.0.0 - * - * @var mixed Reference. Usually an id or something related to this item. - */ - public $ref; - - /** - * Context. - * - * @since 1.0.0 - * - * @var mixed Reference Context. Usually a slug that offers context to what the ID is. - */ - public $context; - - /** - * Event data. - * - * @since 1.0.0 - * - * @var array Data. - */ - public $data; +class Log_Item implements \Underpin\Interfaces\Log_Item { /** * Event type. * - * @since 1.0.0 * * @var string Event code */ - public $type; + public readonly string $type; /** * Event type object. * - * @since 1.0.0 * - * @var Event_Type Event + * @var Event_Type|null Event */ - protected $event_type; + protected ?Event_Type $event_type = null; /** * Log Item Constructor. * - * @since 1.0.0 * - * @param string $type Event log type - * @param string $code The event code to use. - * @param string $message The message to log. - * @param array $data Arbitrary data associated with this event message. + * @param string $code The event code to use. + * @param string $message The message to log. + * @param string $context + * @param int|string|null $ref + * @param array $data Arbitrary data associated with this event message. */ - public function __construct( $type, $code, $message, $data = array() ) { - if ( $type instanceof Event_Type ) { - $this->type = $type->type; - $this->event_type = $type; - } else { - $this->type = $type; - } - $this->code = $code; - $this->message = $message; - $this->data = $data; + public function __construct( + /** + * Event code. + * + * + * @var string Event code + */ + public readonly string $code, + /** + * Message + * + * + * @var string Message. + */ + public readonly string $message, + + /** + * Context. + * + * + * @var mixed Reference Context. Usually a slug that offers context to what the ID is. + */ + public readonly string $context = '', + + /** + * Ref. + * + * + * @var mixed Reference. Usually an id or something related to this item. + */ + public readonly int|string|null $ref = null, + public readonly array $data = array() + ) { + } - if ( isset( $this->data['context'] ) ) { - $this->context = $this->data['context']; - unset( $this->data['context'] ); - } + public function set_type( ?Event_Type $type ): static { + $this->event_type = $type; + + return $this; + } - if ( isset( $this->data['ref'] ) ) { - $this->ref = $this->data['ref']; - unset( $this->data['ref'] ); + public function get_event_type(): ?Event_Type { + if ( ! isset( $this->event_type ) ) { + return null; } + return $this->event_type; } /** * Formats the event log. * - * @since 1.0.0 * * @return string */ - public function format() { + public function __toString(): string { $additional_data = []; if ( ! empty( $this->ref ) ) { @@ -139,12 +108,12 @@ public function format() { } if ( $this->event_type instanceof Event_Type ) { - $additional_data['group'] = $this->event_type->group; - $additional_data['volume'] = $this->event_type->volume; - $additional_data['psr_level'] = $this->event_type->psr_level; + $additional_data['group'] = $this->event_type->get_group(); + $additional_data['volume'] = $this->event_type->get_volume(); + $additional_data['psr_level'] = $this->event_type->get_psr_level(); } - $log_message = 'Underpin ' . $this->type . ' event' . ': ' . $this->code . ' - ' . $this->message; + $log_message = 'Underpin ' . $this->event_type->name . ' event' . ': ' . $this->code . ' - ' . $this->message; $data = array_merge( @@ -159,15 +128,4 @@ public function format() { return date( 'm/d/Y H:i:s' ) . ': ' . $log_message; } - /** - * Converts this log item to a WP Error object. - * - * @since 1.0.0 - * - * @return WP_Error - */ - public function error() { - return new WP_Error( $this->code, $this->message, $this->data ); - } - } \ No newline at end of file diff --git a/lib/Factories/Object_Registry.php b/lib/Factories/Object_Registry.php deleted file mode 100644 index 08eee52..0000000 --- a/lib/Factories/Object_Registry.php +++ /dev/null @@ -1,28 +0,0 @@ -set_values( $args ); - parent::__construct(); - } - - protected function set_default_items() { - foreach ( $this->default_items as $key => $default_item ) { - $this->add( $key, $default_item ); - } - } -} \ No newline at end of file diff --git a/lib/Factories/Observer.php b/lib/Factories/Observer.php deleted file mode 100644 index 50c222d..0000000 --- a/lib/Factories/Observer.php +++ /dev/null @@ -1,29 +0,0 @@ -set_values( $args ); - parent::__construct( $id ); - } - - public function update( $instance, Storage $args ) { - $this->set_callable( $this->update, $instance, $args ); - } - -} \ No newline at end of file diff --git a/lib/Factories/Observers/Loader.php b/lib/Factories/Observers/Loader.php index 3f37e4f..4b21184 100644 --- a/lib/Factories/Observers/Loader.php +++ b/lib/Factories/Observers/Loader.php @@ -3,20 +3,27 @@ namespace Underpin\Factories\Observers; -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +use Underpin\Exceptions\Invalid_Registry_Item; +use Underpin\Exceptions\Operation_Failed; +use Underpin\Factories\Data_Providers\Plugin_Builder_Provider; +use Underpin\Interfaces\Data_Provider; +use Underpin\Abstracts\Observer; -class Loader extends \Underpin\Abstracts\Observer { +class Loader extends Observer { - public function __construct( $key, $args ) { - $this->key = $key; - $this->args = $args; - parent::__construct( $key . '_loader' ); + public function __construct( protected string $key, protected string|array|object $instance, protected int $priority = 10 ) { + parent::__construct( $key, $this->priority ); + $this->id = $key . '_loader'; } - public function update( $instance, \Underpin\Abstracts\Storage $args ) { - $instance->loaders()->add( $this->key, $this->args ); + /** + * @throws Operation_Failed + * @throws Invalid_Registry_Item + */ + public function update( $instance, ?Data_Provider $provider ): void { + if ( $provider instanceof Plugin_Builder_Provider ) { + $provider->get_builder()->loaders()->add( $this->key, $this->instance ); + } } } \ No newline at end of file diff --git a/lib/Factories/Registry.php b/lib/Factories/Registry.php index 95fa82f..0b775ac 100644 --- a/lib/Factories/Registry.php +++ b/lib/Factories/Registry.php @@ -3,36 +3,15 @@ namespace Underpin\Factories; -use Underpin\Traits\Instance_Setter; -use WP_Error; - -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +use Closure; class Registry extends \Underpin\Abstracts\Registries\Registry { - use Instance_Setter; - - protected $default_items = []; - - protected $validate_callback; - - public function __construct( $args = [] ) { - $this->set_values( $args ); - parent::__construct(); - } - - protected function set_default_items() { - foreach ( $this->default_items as $key => $value ) { - $this->add( $key, $value ); - } - - unset( $this->default_items ); + public function __construct( protected Closure $validate_callback ) { } - protected function validate_item( $key, $value ) { - return $this->set_callable( $this->validate_callback, $key, $value ); + protected function validate_item( $key, $value ): bool { + return call_user_func( $this->validate_callback, $key, $value ) === true; } } \ No newline at end of file diff --git a/lib/Factories/Registry_Items/Param.php b/lib/Factories/Registry_Items/Param.php new file mode 100644 index 0000000..e975f48 --- /dev/null +++ b/lib/Factories/Registry_Items/Param.php @@ -0,0 +1,84 @@ +type; + } + + /** + * @param mixed $value + * + * @return $this + * @throws Validation_Failed + */ + public function set_value( mixed $value ): static { + if ( $this->validate( $value ) ) { + $this->value = $value; + } + + return $this; + } + + /** + * @param $value + * + * @throws Validation_Failed + * @return true + */ + protected function validate( $value ): bool { + if ( gettype( $value ) !== $this->type->value ) { + throw new Validation_Failed( 'Param ' . $this->get_id() . ' expects ' . $this->type->value . ', ' . gettype( $value ) . ' given.' ); + } + + return true; + } + + /** + * @return mixed + */ + public function get_value(): mixed { + return $this->value; + } + + /** + * @return string + */ + public function get_id(): string { + return $this->id; + } + + /** + * @return string + */ + public function __toString(): string { + return $this->to_string(); + } + + /** + * @return string + */ + public function to_string(): string { + return (string) $this->value; + } + +} \ No newline at end of file diff --git a/lib/Factories/Request.php b/lib/Factories/Request.php new file mode 100644 index 0000000..7d226d7 --- /dev/null +++ b/lib/Factories/Request.php @@ -0,0 +1,278 @@ +headers = Mutable_Collection_With_Remove::make( Header::class ); + } + + /** + * Gets the current request. + * + * @return Request + */ + public static function current(): Request { + if ( ! isset( static::$current ) ) { + static::$current = new static; + } + + return static::$current; + } + + /** + * Sets a flag on this request. + * + * @param string $flag + * + * @return $this + */ + public function set_flag( string $flag ): static { + if ( ! $this->has_flag( $flag ) ) { + $this->flags[] = $flag; + } + + return $this; + } + + /** + * Unsets a flag on this request. + * + * @param string $flag + * + * @return $this + */ + public function unset_flag( string $flag ): static { + if ( $this->has_flag( $flag ) ) { + unset( $this->flags[ $flag ] ); + } + + return $this; + } + + /** + * Sets or unsets a flag based on a boolean value + * + * @param string $flag + * @param bool $binding + * + * @return $this + */ + public function bind_flag( string $flag, bool $binding ): static { + return $binding ? $this->set_flag( $flag ) : $this->unset_flag( $flag ); + } + + /** + * Returns true if this flag is set. + * + * @param string $flag + * + * @return bool + */ + public function has_flag( string $flag ): bool { + return in_array( $flag, $this->flags ); + } + + /** + * Updates this request's identity + * + * @return ?Identifiable + */ + public function get_identity(): ?Identifiable { + return $this->identity ?? null; + } + + /** + * Sets this request's identity + * + * @param ?Identifiable $id + * + * @return $this + */ + public function set_identity( ?Identifiable $id ): static { + $this->identity = $id; + + return $this; + } + + /** + * Sets the URL + * + * @param Url $url The URL object to set. + * + * @return $this + */ + public function set_url( Url $url ): static { + $this->url = $url; + + return $this; + } + + /** + * Gets the request URL + * + * @return Url + */ + public function get_url(): Url { + return $this->url; + } + + /** + * Retrieves the list of headers in this request. + * + * @return Mutable_Collection_With_Remove + */ + public function get_headers(): Mutable_Collection_With_Remove { + return $this->headers; + } + + /** + * Gets all the headers from the specified key. + * + * It is technically possible for a request to have multiple headers with identical keys + * Because of this, we can't simply get a header by the key directly. + * + * @param string $key + * + * @return array + * @throws Operation_Failed + */ + public function get_headers_by_key( string $key ): array { + return $this->headers->query()->in( 'key', $key )->get_results()->to_array(); + } + + /** + * @param Header $header + * + * @return $this + * @throws Operation_Failed + */ + public function set_header( Header $header ): static { + $this->get_headers()->add( $header->get_id(), $header ); + + return $this; + } + + /** + * Gets the specified URL param from the URL. + * + * @param string $key + * + * @return Param + * @throws Unknown_Registry_Item + */ + public function get_param( string $key ): Param { + return $this->get_url()->get_params()->get( $key ); + } + + /** + * Sets, or updates the URL param to the specified value. + * + * @param Param $param + * + * @return $this + * @throws Operation_Failed + */ + public function set_param( Param $param ): static { + if ( $this->get_url()->get_params()->is_registered( $param->get_id() ) ) { + try { + $original = $this->get_url()->get_params()->get( $param->get_id() ); + } catch ( Unknown_Registry_Item $e ) { + throw new Operation_Failed( 'An item was registered, but the registry could not obtain the registered item.', 500, 'error', $e ); + } + + if ( ! $param instanceof $original ) { + throw new Operation_Failed( 'The param ' . $param->get_id() . ' Is already set, and the new param is not an instance of the newly specified param.', 500, 'error' ); + } + + $original->set_value( $param->get_value() ); + } + + try { + $this->get_url()->get_params()->add( $param->get_id(), $param ); + } catch ( Operation_Failed $e ) { + throw new Operation_Failed( 'Could not set the param.', 500, 'error', $e ); + } + + return $this; + } + + /** + * Removes a header + * + * @param string $id + * + * @return $this + * @throws Operation_Failed + */ + public function remove_header( string $id ): static { + $this->get_headers()->remove( $id ); + + return $this; + } + + /** + * Sets the request method. + * + * @param Rest $method The method to set. + * + * @return $this + */ + public function set_method( Rest $method ): static { + $this->method = $method; + + return $this; + } + + /** + * Gets the rest method. + * + * @return Rest + */ + public function get_method(): Rest { + return $this->method; + } + + /** + * Sets the request body. + * + * @param string $body + * + * @return $this + */ + public function set_body( string $body ): static { + $this->body = $body; + + return $this; + } + + /** + * Gets the request body. + * + * @return string + */ + public function get_body(): string { + return $this->body; + } + +} diff --git a/lib/Factories/Simple_Storage.php b/lib/Factories/Simple_Storage.php deleted file mode 100644 index c988617..0000000 --- a/lib/Factories/Simple_Storage.php +++ /dev/null @@ -1,20 +0,0 @@ -params = $args; - } - -} \ No newline at end of file diff --git a/lib/Factories/Sort_Methods/Basic.php b/lib/Factories/Sort_Methods/Basic.php new file mode 100644 index 0000000..0f16ecb --- /dev/null +++ b/lib/Factories/Sort_Methods/Basic.php @@ -0,0 +1,55 @@ + $item_b; + break; + case "array": + $result = count( $item_a ) <=> count( $item_b ); + break; + case "boolean": + case "NULL": + $result = (int) $item_a <=> (int) $item_b; + break; + default: + if ( $item_a instanceof Mutable_Collection ) { + $result = count( $item_a->to_array() ) <=> count( $item_b->to_array() ); + } elseif ( $item_a instanceof \DateTime ) { + $result = (int) $item_a->format( 'U' ) <=> (int) $item_b->format( 'U' ); + } else { + try { + settype( $item_a, 'string' ); + settype( $item_b, 'string' ); + $result = strcmp( (string) $item_a, (string) $item_b ); + } catch ( Exception $e ) { + throw new Operation_Failed( 'could not sort items using basic sort method.', previous: $e ); + } + } + } + + if ( $direction === Direction::Descending ) { + $result = $result * -1; + } + + return $result; + } + +} \ No newline at end of file diff --git a/lib/Factories/Tag.php b/lib/Factories/Tag.php new file mode 100644 index 0000000..467f881 --- /dev/null +++ b/lib/Factories/Tag.php @@ -0,0 +1,63 @@ +attributes = Mutable_Collection_With_Remove::make( Html_Attribute::class ); + } + + public function add_attribute( Html_Attribute $attribute ): static { + $this->attributes->add( $attribute->get_id(), $attribute ); + + return $this; + } + + public function get_content(): ?string { + return $this->content ?? null; + } + + public function set_content( string $content ): static { + $this->content = $content; + + return $this; + } + + public function set_tag( string $tag ): static { + $this->tag = $tag; + + return $this; + } + + public function get_tag(): string { + return $this->tag; + } + + public function __toString(): string { + return $this->to_string(); + } + + public function to_string(): string { + $attributes = implode( ' ', $this->attributes->each( fn ( Html_Attribute $attribute ) => (string) $attribute ) ); + $output = implode( ' ', [ '<' . $this->get_tag(), $attributes . '>' ] ); + + if ( $this->get_content() ) { + $output .= $this->get_content(); + } + + if ( $this->get_content() || $this->force_close ) { + $output .= 'get_tag() . '>'; + } + + return $output; + } + +} \ No newline at end of file diff --git a/lib/Factories/Underpin_Instance.php b/lib/Factories/Underpin_Instance.php deleted file mode 100644 index 82a0b9a..0000000 --- a/lib/Factories/Underpin_Instance.php +++ /dev/null @@ -1,41 +0,0 @@ -set_values( $args ); - } - - protected function _setup() { - $this->set_callable( $this->setup_callback, $this ); - } - -} \ No newline at end of file diff --git a/lib/Factories/Url.php b/lib/Factories/Url.php new file mode 100644 index 0000000..3701c37 --- /dev/null +++ b/lib/Factories/Url.php @@ -0,0 +1,287 @@ +clear_params(); + } + + /** + * @param string $url + * @param Types[] $param_signature + * + * @return static + * @throws Url_Exception + */ + public static function from( string $url, array $param_signature = [] ): static { + $pieces = parse_url( $url ); + + if ( ! is_array( $pieces ) ) { + throw new Url_Exception( message: 'Failed to create URL', data: [ 'url' => $pieces ] ); + } + + $instance = new static; + + if ( isset( $pieces['scheme'] ) ) { + $instance->set_protocol( $pieces['scheme'] ); + } + + if ( isset( $pieces['host'] ) ) { + $instance->set_host( $pieces['host'] ); + } + + if ( isset( $pieces['port'] ) ) { + $instance->set_port( $pieces['port'] ); + } + + if ( isset( $pieces['path'] ) ) { + $instance->set_path( $pieces['path'] ); + } + + if ( isset( $pieces['query'] ) ) { + $instance->get_params()->from_string( $pieces['query'], $param_signature ); + } + + if ( isset( $pieces['fragment'] ) ) { + $instance->set_fragment( $pieces['fragment'] ); + } + + return $instance; + } + + /** + * Magic Method. Allows casting of object using (string) + * + * @return string + */ + public function __toString(): string { + return $this->to_string(); + } + + /** + * Gets the URL path. Example: /path/to/location + * + * @return string + */ + public function get_path(): ?string { + return $this->path ?? null; + } + + /** + * Sets the URL path. + * + * @param string $path The path to set. + * + * @return $this + */ + public function set_path( string $path ): static { + $this->path = String_Helper::prepend( $path, '/' ); + + return $this; + } + + /** + * Appends an item to the path + * + * @param string $path + * + * @return $this + */ + public function append_path( string $path ): static { + return $this->set_path( String_Helper::trim_trailing( $this->get_path(), '/' ) . String_Helper::prepend( $path, '/' ) ); + } + + /** + * Gets the URL protocol, without the trailing ://. Examples: http, https + * + * @return string + */ + public function get_protocol(): string { + return $this->protocol; + } + + /** + * Sets the protocol. Only allows lowercase characters per standard. + * + * @param string $protocol + * + * @return $this + */ + public function set_protocol( string $protocol ): static { + $this->protocol = preg_replace( '/[^a-z]/', '', $protocol ); + + return $this; + } + + /** + * Gets the host. Example: www.domain.com + * + * @return string + */ + public function get_host(): string { + return $this->host; + } + + /** + * Sets the host. + * + * @param string $host + * + * @return $this + */ + public function set_host( string $host ): static { + $this->host = str_replace( '/', '', $host ); + + return $this; + } + + /** + * Sets the port. + * + * @param int $port + * + * @return $this + */ + public function set_port( int $port ): static { + $this->port = $port; + + return $this; + } + + /** + * Gets the port. + * + * @return int|null + */ + public function get_port(): ?int { + return $this->port ?? null; + } + + /** + * Gets the URL fragment, without the leading '#' + * + * @return string|null + */ + public function get_fragment(): ?string { + return $this->fragment ?? null; + } + + /** + * Sets the fragment. + * + * @param string $fragment + * + * @return $this + */ + public function set_fragment( string $fragment ): static { + $this->fragment = str_replace( '#', '', $fragment ); + + return $this; + } + + /** + * Gets the URL param registry object. + * + * @return Param_Collection + */ + public function get_params(): Param_Collection { + return $this->params; + } + + /** + * Adds a param to the URL + * + * @param Param $param + * + * @return $this + * @throws Operation_Failed + */ + public function add_param( Param $param ): static { + try { + $this->params->add( $param->get_id(), $param ); + } catch ( Operation_Failed|Invalid_Registry_Item $e ) { + throw new Operation_Failed( 'Could not add URL param', previous: $e ); + } + + return $this; + } + + /** + * @param string $key + * + * @return $this + * @throws Operation_Failed + */ + public function remove_param( string $key ): static { + $this->params->remove( $key ); + + return $this; + } + + /** + * Resets the param collection. + * + * @return $this + */ + public function clear_params(): static { + $this->params = new Param_Collection(); + + return $this; + } + + /** + * Converts this URL object to a string. + * + * @return string + */ + public function to_string(): string { + return (string) ( new Array_Processor( $this->to_array() ) )->where_not_null()->set_separator( '' ); + } + + /** + * Accessor for URL. Makes it possible to easily pluck this value. + * + * @return string + */ + public function get_url(): string { + return $this->to_string(); + } + + /** + * Converts URL parts to an implode-ready array + * + * @return array + */ + public function to_array(): array { + return [ + 'protocol' => $this->get_protocol() . '://', + 'host' => $this->get_host(), + 'port' => $this->get_port() ? ':' . $this->get_port() : null, + 'path' => $this->get_path(), + 'params' => ! empty( $this->get_params()->to_array() ) ? '?' . $this->get_params() : null, + 'fragment' => $this->get_fragment() ? '#' . $this->get_fragment() : null, + ]; + } + +} diff --git a/lib/Helpers/Array_Helper.php b/lib/Helpers/Array_Helper.php new file mode 100644 index 0000000..e81014b --- /dev/null +++ b/lib/Helpers/Array_Helper.php @@ -0,0 +1,515 @@ + $item !== null ); + } + + /** + * Maps values, retaining keys if the array is associative. + * + * @param array $subject + * @param callable $callback + * + * @return array + */ + public static function each( array $subject, callable $callback ): array { + if ( Array_Helper::is_associative( $subject ) ) { + $result = []; + foreach ( $subject as $key => $value ) { + $result[ $key ] = $callback( $value, $key ); + } + } else { + $result = Array_Helper::map( $subject, $callback ); + } + + return $result; + } + + /** + * Retrieve items after the specified array position. + * + * @param array $subject the array + * @param int $position The position to retrieve after + * + * @return array + */ + public static function after( array $subject, int $position ): array { + return array_slice( $subject, $position ); + } + + /** + * Retrieve items before the specified array position. + * + * @param array $subject the array + * @param int $position The position to retrieve before + * + * @return array + */ + public static function before( array $subject, int $position ): array { + return Array_Helper::diff( Array_Helper::after( $subject, $position ) ); + } + + /** + * Iterates over each value in the array + * passing them to the callback function. + * If the callback function returns true, the + * current value from array is returned into + * the result array. + * + * @param array $subject The items to filter + * @param callable $callback + * + * @return array + */ + public static function filter( array $subject, callable $callback ): array { + return array_filter( $subject, $callback ); + } + + /** + * Returns the values of the array. + * + * @param array $subject + * + * @return array + */ + public static function values( array $subject ): array { + return array_values( $subject ); + } + + /** + * Returns the keys of the array. + * + * @param array $subject + * + * @return array + */ + public static function keys( array $subject ): array { + return array_keys( $subject ); + } + + /** + * Retrieves an item from a dot-based syntax + * + * @param array $subject + * @param string $dot + * + * @return mixed + * @throws Item_Not_Found + */ + public static function dot( array $subject, string $dot ): mixed { + foreach ( explode( '.', $dot ) as $item ) { + if ( !isset( $subject[ $item ] ) ) { + throw new Item_Not_Found( $item ); + } else { + $subject = $subject[ $item ]; + } + } + + return $subject; + } + + public static function remove( array $subject, string|int $key ): array { + if ( isset( $subject[ $key ] ) ) { + unset( $subject[ $key ] ); + } + + return $subject; + } + + /** + * Force an item to be an array, even if it is not an array. + * + * @param $item mixed The item to force into an array + * + * @return array + */ + public static function wrap( $item ) { + if ( ! is_array( $item ) ) { + $item = [ $item ]; + } + + return $item; + } + + /** + * Create an array of new instances given arguments to pass + * + * @param $array array The list of items to instantiate + * @param $instance string The instance to create + * + * @return array + */ + public static function hydrate( array $array, string $instance ): array { + $result = []; + foreach ( $array as $item ) { + $result[] = new $instance( ...$item ); + } + + return $result; + } + + /** + * Flattens arrays of arrays into a single array where the parent array is embedded as an item keyed by the $key + * param Example: + * [ + * 'group-1' => [['key' => 'value', 'another' => 'value'], ['key' => 'another-value', 'another' => 'value']], + * 'group-2' => [['key' => 'value', 'another' => 'value'], ['key' => 'another-value', 'another' => 'value']], + * ] + * + * Becomes: + * + * [ + * ['group' => 'group-1', 'key' => 'value', 'another' => 'value'], + * ['group' => 'group-1', 'key' => 'another-value', 'another' => 'value'], + * ['group' => 'group-2', 'key' => 'value', 'another' => 'value'], + * ['group' => 'group-2', 'key' => 'another-value', 'another' => 'value'] + * ] + * + * @param array $subject The array to flatten + * @param string $group_key The key to use for the group identifier. + * + */ + public static function flatten( array $subject, string $group_key = 'group' ): array { + $result = []; + foreach ( $subject as $group_id => $items ) { + foreach ( $items as $item ) { + $new_item = Array_Helper::wrap( $item ); + $new_item[ $group_key ] = $group_id; + $result[] = $new_item; + } + } + + return $result; + } + + /** + * Updates the array to contain a key equal to the array's key value. + * + * @param array $subject + * @param string $key + * @param string $value_key + * + * @return array + */ + public static function to_indexed( array $subject, string $key = 'key', string $value_key = 'value' ): array { + $result = []; + + foreach ( $subject as $subject_key => $value ) { + if ( Array_Helper::is_associative( Array_Helper::wrap( $value ) ) ) { + $result[] = Array_Helper::merge( [ $key => $subject_key ], $value ); + } else { + $result[] = Array_Helper::merge( [ $key => $subject_key ], [ $value_key => $value ] ); + } + } + + return $result; + } + + /** + * Strips out duplicate items in the provided array. + * + * @param array $subject + * + * @return array + */ + public static function unique( array $subject ): array { + return array_unique( $subject ); + } + + /** + * Sorts an array by the keys. + * + * @param array $subject + * + * @return void + */ + public static function key_sort( array &$subject ): void { + ksort( $subject ); + } + + /** + * Sorts an array. + * + * @param array $subject The item to sort + * @param callable|int $method The method. Can be any supported flag documented in PHP's asort, or a sorting + * callback. + * @param Direction $direction + * + * @return void + */ + public static function sort( array &$subject, callable|int $method = SORT_REGULAR, Direction $direction = Direction::Ascending ): void { + if($direction === Direction::Ascending) { + is_callable( $method ) ? uasort( $subject, $method ) : asort( $subject, $method ); + }else{ + is_callable( $method ) ? uasort( $subject, $method ) : arsort( $subject, $method ); + } + } + + /** + * Merges arrays together. + * + * @param array ...$args + * + * @return array + */ + public static function merge( array ...$args ): array { + return array_merge( ...$args ); + } + + /** + * Reverses the order of the items in the array. + * + * @param array $subject The input array. + * @param bool $preserve_keys If set to true keys are preserved. + * + * @return array + */ + public static function reverse( array $subject, bool $preserve_keys = true ): array { + return array_reverse( $subject, $preserve_keys ); + } + + /** + * Plucks a value from an array, if it is an array. Falls back to default value if not-set. + * + * @param mixed $item The array from which to pluck. + * @param string $key The key to pluck + * @param mixed $default The fallback value + * + * @return mixed The value + */ + public static function pluck( array $item, string $key, mixed $default = null ): mixed { + $array = self::wrap( $item ); + + if ( isset( $array[ $key ] ) ) { + return $array[ $key ]; + } + + return $default; + } + + /** + * Recursively plucks values from a set of items. + * + * @param object[]|array[] $items The list of items. + * @param string $key The key that the value is set against. + * @param mixed $default The default value to use when the value is not set. + * + * @return array Array of values plucked from the list. + */ + public static function pluck_recursive( array $items, string $key, mixed $default = false ): array { + $result = []; + foreach ( $items as $id => $item ) { + if ( is_object( $item ) ) { + try { + $result[ $id ] = Object_Helper::pluck( $item, $key ); + } catch ( Invalid_Field $e ) { + $result[ $id ] = $default; + continue; + } + } elseif ( Array_Helper::is_associative( $item ) ) { + $result[ $id ] = self::pluck( $item, $key, $default ); + } elseif ( is_array( $item ) ) { + $result[ $id ] = array_merge( $result, self::pluck_recursive( $item, $key, $default ) ); + } else { + $result[ $id ] = $default; + } + } + + return $result; + } + + /** + * Cast all items in the array to the specified type. + * + * @param array $items + * @param string $type + * + * @return array + */ + public static function cast( array $items, string $type ): array { + $result = []; + foreach ( $items as $item ) { + settype( $item, $type ); + $result[] = $item; + } + + return $result; + } + + /** + * Returns true if this array is an associative array. + * + * @param array $items + * + * @return bool + */ + public static function is_associative( array $items ): bool { + foreach ( array_keys( $items ) as $item ) { + if ( is_string( $item ) ) { + return true; + } + } + + return false; + } + + /** + * Adds items to the beginning of the array. + * + * @param array $array array that the item should be prepended to. + * @param mixed ...$items Items to add + * + * @return void + */ + public static function prepend( array &$array, mixed ...$items ): void { + $array = self::wrap( $array ); + array_unshift( $array, ...$items ); + } + + /** + * Adds items to the end of the array. + * + * @param array $array array that the item should be prepended to. + * @param mixed ...$items Items to add + * + * @return void + */ + public static function append( array &$array, mixed ...$items ): void { + $array = self::wrap( $array ); + foreach ( $items as $item ) { + $array[] = $item; + } + } + + public static function flip( $array ): array { + return array_flip( $array ); + } + + /** + * Recursively sorts, and optionally mutates an array of arrays. + * + * @param array $array The array to sort. + * + * @type bool $convert_closures If true, closures will be converted to an identifiable string. Default true. + * @type bool $recursive if true, this function will normalize recursively, manipulating sub-arrays. + * + * @return array The normalized array + * @throws \ReflectionException + */ + public static function normalize( array $array, $convert_colsures = true, $recursive = true ): array { + + foreach ( $array as $key => $value ) { + // Normalize recursively. + if ( is_array( $value ) && true === $recursive ) { + $args = func_get_args(); + $args[0] = $value; + $array[ $key ] = self::normalize( ...$args ); + } + + // If closures need converted, and this is a closure, transform this into an identifiable string. + if ( true === $convert_colsures && $value instanceof Closure ) { + $array[ $key ] = self::convert_closure( $value ); + } + } + + // Sorting behavior depends on if the array is associative, or not. + if ( self::is_associative( $array ) ) { + ksort( $array ); + } else { + sort( $array ); + } + + return $array; + } + + /** + * Returns an array that contains the values contained in all arrays. + * + * @param array ...$items + * + * @return array + */ + public static function intersect( array ...$items ): array { + return array_intersect( ...self::map( func_get_args(), [ Array_Helper::class, 'wrap' ] ) ); + } + + /** + * Returns an array that contains the values contained in all arrays. + * + * @param array ...$items + * + * @return array + */ + public static function intersect_keys( array ...$items ): array { + return array_intersect_key( ...self::map( func_get_args(), [ Array_Helper::class, 'wrap' ] ) ); + } + + /** + * Returns an array that contains values only contained in a single array. + * + * @param array ...$items + * + * @return array + */ + public static function diff( array ...$items ): array { + return array_diff( ...self::map( func_get_args(), [ Array_Helper::class, 'wrap' ] ) ); + } + + /** + * Combines arrays into a single array, with each item overriding items from the previous array. + * + * @param array ...$items + * + * @return array + */ + public static function replace_recursive( array ...$items ): array { + return array_replace_recursive( $items ); + } + + /** + * Combines arrays into a single array, with each item overriding items from the previous array. + * + * @param array ...$items + * + * @return array + */ + public static function replace( array ...$items ): array { + return array_replace( $items ); + } + +} \ No newline at end of file diff --git a/lib/Helpers/Number_Helper.php b/lib/Helpers/Number_Helper.php new file mode 100644 index 0000000..35755e7 --- /dev/null +++ b/lib/Helpers/Number_Helper.php @@ -0,0 +1,30 @@ + '0', + 1 => '1st', + 2 => '2nd', + 3 => '3rd', + default => $number . 'th' + }; + } + +} \ No newline at end of file diff --git a/lib/Helpers/Object_Helper.php b/lib/Helpers/Object_Helper.php new file mode 100644 index 0000000..e37484a --- /dev/null +++ b/lib/Helpers/Object_Helper.php @@ -0,0 +1,82 @@ +$field; + } catch ( Exception $e ) { + throw new Invalid_Field( message: 'The provided field cannot be retrieved.', code: 0, type: 'error', previous: $e ); + } + } + } + + return $value; + } + +} \ No newline at end of file diff --git a/lib/Helpers/Processors/Array_Processor.php b/lib/Helpers/Processors/Array_Processor.php new file mode 100644 index 0000000..0bd69e6 --- /dev/null +++ b/lib/Helpers/Processors/Array_Processor.php @@ -0,0 +1,390 @@ +subject; + } + + /** + * Flips the array, setting the values as the keys and vice-versa. + * Note if the subject is an array of anything but integers or strings this will fail. + * If you aren't sure about the values, you may want to use cast(), first. + * + * @return $this + */ + public function flip(): static { + $this->subject = Array_Helper::flip( $this->subject ); + + return $this; + } + + public function keys(): static { + $this->subject = array_keys( $this->subject ); + + return $this; + } + + public function each( callable $callback ) { + $this->subject = Array_Helper::each( $this->subject, $callback ); + + return $this; + } + + public function after( int $position ): static { + $this->subject = Array_Helper::after( $this->subject, $position ); + + return $this; + } + + public function before( int $position ): static { + $this->subject = Array_Helper::before( $this->subject, $position ); + + return $this; + } + + /** + * Removes the provided key. + * + * @param string|int $key + * + * @return $this + */ + public function remove( string|int $key ): static { + $this->subject = Array_Helper::remove( $this->subject, $key ); + + return $this; + } + + /** + * Create an array of new instances given arguments to pass + * + * @param $instance string The instance to create + * + * @return $this + */ + public function hydrate( string $instance ): static { + $this->subject = Array_Helper::hydrate( $this->subject, $instance ); + + return $this; + } + + /** + * Flattens arrays of arrays into a single array where the parent array is embedded as an item keyed by the $key param + * Example: + * [ + * 'group-1' => [['key' => 'value', 'another' => 'value'], ['key' => 'another-value', 'another' => 'value']], + * 'group-2' => [['key' => 'value', 'another' => 'value'], ['key' => 'another-value', 'another' => 'value']], + * ] + * + * Becomes: + * + * [ + * ['group' => 'group-1', 'key' => 'value', 'another' => 'value'], + * ['group' => 'group-1', 'key' => 'another-value', 'another' => 'value'], + * ['group' => 'group-2', 'key' => 'value', 'another' => 'value'], + * ['group' => 'group-2', 'key' => 'another-value', 'another' => 'value'] + * ] + * + * @param string $group_key The key to use for the group identifier. + * + */ + public function flatten( string $group_key = 'group' ): static { + $this->subject = Array_Helper::flatten( $this->subject, $group_key ); + + return $this; + } + + public function to_indexed( string $key = 'key' ): static { + $this->subject = Array_Helper::to_indexed( $this->subject, $key ); + + return $this; + } + + /** + * Strips out duplicate items in the provided array. + * + * @return $this + */ + public function unique(): static { + $this->subject = Array_Helper::unique( $this->subject ); + + return $this; + } + + /** + * Merges the provided arrays with the array that is being processed. + * + * @param array ...$defaults + * + * @return $this + */ + public function merge( array ...$defaults ): static { + $this->subject = Array_Helper::merge( $this->subject, ...$defaults ); + + return $this; + } + + /** + * Combines arrays into a single array, with each item overriding items from the previous array. + * + * @param array ...$items + * + * @return $this + */ + public function replace_recursive( array ...$items ): static { + $this->subject = Array_Helper::merge( $this->subject, ...$items ); + + return $this; + } + + /** + * Combines arrays into a single array, with each item overriding items from the previous array. + * + * @param array ...$items + * + * @return $this + */ + public function replace( array ...$items ): static { + $this->subject = Array_Helper::merge( $this->subject, ...$items ); + + return $this; + } + + + /** + * Adds an item to the beginning of the array. + * + * @param mixed ...$items items to add. + * + * @return $this + */ + public function prepend( ...$items ): static { + Array_Helper::prepend( $this->subject, ...$items ); + + return $this; + } + + /** + * Adds items to the end of the array. + * + * @param mixed ...$items Items to add + * + * @return void + */ + public function append( ...$items ): static { + Array_Helper::append( $this->subject, ...$items ); + + } + + /** + * Recursively sorts, and optionally mutates an array of arrays. + * + * @type bool $convert_closures If true, closures will be converted to an identifiable string. Default true. + * @type bool $recursive if true, this function will normalize recursively, manipulating sub-arrays. + * + * @throws ReflectionException + */ + public function normalize( $convert_colsures = true, $recursive = true ): static { + $this->subject = Array_Helper::normalize( $this->subject, $convert_colsures, $recursive ); + + return $this; + } + + /** + * Sorts an array by the keys. + * + * @return static + */ + public function key_sort(): static { + Array_Helper::key_sort( $this->subject ); + + return $this; + } + + /** + * Sorts the array. + * + * @param callable|int $method The method. Can be any supported flag documented in PHP's asort, or a sorting + * callback. + * + * @return static + */ + public function sort( callable|int $method = SORT_REGULAR, Direction $direction = Direction::Ascending ): static { + Array_Helper::sort( $this->subject, $method, $direction ); + + return $this; + } + + /** + * Applies the callback to the elements of the array being processed. + * + * @param callable $callback The callback. + * + * @return static + */ + public function map( callable $callback ): static { + $this->subject = Array_Helper::map( $this->subject, $callback ); + + return $this; + } + + /** + * @param callable $callback + * @param mixed $initial + * + * @return $this + */ + public function reduce( callable $callback, mixed $initial ): static { + $this->subject = Array_Helper::reduce($this->subject, $callback, $initial); + + return $this; + } + + /** + * Iterates over each value in the array + * passing them to the callback function. + * If the callback function returns true, the + * current value from array is returned into + * the result array. + * + * @param callable $callback + * + * @return static + */ + public function filter( callable $callback ): static { + $this->subject = Array_Helper::filter( $this->subject, $callback ); + + return $this; + } + + public function where_not_null(): static { + $this->subject = Array_Helper::where_not_null( $this->subject ); + + return $this; + } + + /** + * Strips the keys from the array. + * + * @return static + */ + public function values(): static { + $this->subject = Array_Helper::values( $this->subject ); + + return $this; + } + + /** + * Cast all items in the array to the specified type. + * + * @param string $type + * + * @return static + */ + public function cast( string $type ): static { + $this->subject = Array_Helper::cast( $this->subject, $type ); + + return $this; + } + + /** + * Plucks a value from an array, if it is an array. Falls back to default value if not-set. + * + * @param string $key The key to pluck + * @param mixed $default The fallback value + * + * @return static + */ + public function pluck( string $key, mixed $default = false ): static { + $this->subject = Array_Helper::pluck_recursive( $this->subject, $key, $default ); + + return $this; + } + + /** + * Filters the array to only contain values contained in all provided arrays. + * + * @param array ...$items + * + * @return static + */ + public function intersect( array ...$items ): static { + $this->subject = Array_Helper::intersect( $this->subject, ...$items ); + + return $this; + } + + /** + * Filters the array to only contain values contained in all provided arrays. + * + * @param array ...$items + * + * @return static + */ + public function intersect_keys( array ...$items ): static { + $this->subject = Array_Helper::intersect_keys( $this->subject, ...$items ); + + return $this; + } + + /** + * Filters the array to only contain values only contained in a single array. + * + * @param array ...$items + * + * @return static + */ + public function diff( ...$items ): static { + $this->subject = Array_Helper::diff( $this->subject, ...$items ); + + return $this; + } + + /** + * Reverses the order of the items in the array. + * + * @param bool $preserve_keys If set to true keys are preserved. + * + * @return static + */ + public function reverse( bool $preserve_keys = true ): static { + $this->subject = Array_Helper::reverse( $this->subject, $preserve_keys ); + + return $this; + } + + public function set_separator( string $separator ): static { + $this->separator = $separator; + + return $this; + } + + public function __toString(): string { + return $this->to_string(); + } + + public function to_string(): string { + return implode( $this->separator, $this->subject ); + } + +} \ No newline at end of file diff --git a/lib/Helpers/Processors/Dependency_Processor.php b/lib/Helpers/Processors/Dependency_Processor.php new file mode 100644 index 0000000..73053a0 --- /dev/null +++ b/lib/Helpers/Processors/Dependency_Processor.php @@ -0,0 +1,131 @@ +get_dependencies(); + foreach ( $item->get_dependencies() as $dep ) { + $dep_item = $this->items->query()->equals( 'id', $dep )->find(); + if ( $dep_item ) { + /** @var Item_With_Dependencies $dep_item */ + $deps = array_merge( $deps, $this->get_dependencies( $dep_item ) ); + } + } + + return $deps; + } + + public function filter_dependencies(): array { + $queue = $this->items->to_array(); + $items = []; + $queued_deps = []; + + // If there's zero, or 1 item in the array, there's nothing to sort. Just return it. + if ( count( $queue ) < 2 ) { + return $queue; + } + + while ( ! empty( $queue ) ) { + $key = array_key_first( $queue ); + /* @var Item_With_Dependencies $item */ + $item = $queue[ $key ]; + + // If this item depends on something that doesn't exist, skip it. + $unmet_dependencies = array_diff( $this->get_dependencies( $item ), $this->items->pluck( 'id' ) ); + + if ( ! empty( $unmet_dependencies ) ) { + Logger::log( + 'debug', + new Log_Item( + code : 'observer_detached', + message: 'An event was detached because it has unmet dependencies', + context: 'id', + ref : $item->get_id(), + data : [ + 'unmet_dependencies' => $unmet_dependencies, + ] + ) + ); + array_shift( $queue ); + continue; + } + + + $dependencies_not_added_yet = array_diff( $item->get_dependencies(), $queued_deps ); + + // If all dependencies have not been added yet, push this to the bottom of the queue + if ( ! empty( $dependencies_not_added_yet ) ) { + $queue_item = array_shift( $queue ); + $queue[] = $queue_item; + continue; + } + + // If all dependencies have been added, add this after the last dependency + $last_dependency_key = $this->get_last_dependency( $item, $items ); + if ( null === $last_dependency_key ) { + $items = array_merge( [ $key => $item ], $items ); + } else { + $items = array_merge( + array_slice( $items, 0, $last_dependency_key + 1, true ), + [ $key => $item ], + array_slice( $items, $last_dependency_key + 1, count( $items ) - $last_dependency_key + 1 ) + ); + } + $queued_deps[] = $item->get_id(); + array_shift( $queue ); + } + + return $items; + } + + protected function get_last_dependency( Item_With_Dependencies $item, $items ): ?int { + foreach ( $items as $key => $value ) { + /* @var Item_With_Dependencies $value */ + + // If neither have dependencies, use priority. + if ( empty( $item->get_dependencies() ) && empty( $value->get_dependencies() ) ) { + if ( $item->get_priority() > $value->get_priority() ) { + $last_dependency = $key; + } + continue; + } + + // If the value is a dependency, use it. + $found_dependencies = in_array( $value->get_id(), $item->get_dependencies() ); + if ( ! empty( $found_dependencies ) ) { + $last_dependency = $key; + continue; + } + + // If both items have the same dependencies, use priority. + if ( ! empty( Array_Helper::intersect( $value->get_dependencies(), $item->get_dependencies() ) ) ) { + if ( $item->get_priority() > $value->get_priority() ) { + $last_dependency = $key; + } + } + } + + if ( ! isset( $last_dependency ) ) { + return null; + } + + $keys = array_keys( $items ); + + return array_search( $last_dependency, $keys ); + } + + public function mutate(): array { + return $this->filter_dependencies(); + } + +} \ No newline at end of file diff --git a/lib/Helpers/Processors/List_Filter.php b/lib/Helpers/Processors/List_Filter.php new file mode 100644 index 0000000..8ffabf0 --- /dev/null +++ b/lib/Helpers/Processors/List_Filter.php @@ -0,0 +1,178 @@ + 1 ? $processed[1] : 'in'; + + return [ 'field' => $field, 'type' => $type ]; + } + + + /** + * Determines if a registry item passes the arguments. + * + * @param object $item Item to filter + * + * @return ?object The instance, if it matches the filters. + */ + protected function filter_item( object $item, ): ?object { + $valid = true; + + foreach ( $this->filter_args as $key => $arg ) { + /* @var string $field */ + /* @var string $type */ + extract( $this->prepare_field( $key ) ); + + + try { + if ( 'instanceof' === $field ) { + $value = array_keys(array_merge( class_uses( $item ), class_implements( $item ), class_parents( $item ) )); + } else { + $value = Object_Helper::pluck( $item, $field ); + } + } catch ( Invalid_Field $e ) { + continue; + } + + if ( $type === Filter::callback->value ) { + $valid = $arg( $value ); + } else { + + $fields = Array_Helper::intersect( Array_Helper::wrap( $arg ), Array_Helper::wrap( $value ) ); + + // Check based on type. + $valid = match ( $type ) { + Filter::not_in->value => empty( $fields ), + Filter::in->value => ! empty( $fields ), + Filter::and->value => count( $fields ) === count( $arg ), + Filter::equals->value => isset( $fields[0] ) && $fields[0] === $arg, + Filter::less_than->value => array_sum( Array_Helper::wrap( $value ) ) < $arg, + Filter::greater_than->value => array_sum( Array_Helper::wrap( $value ) ) > $arg, + Filter::greater_than_or_equal_to->value => array_sum( Array_Helper::wrap( $value ) ) >= $arg, + Filter::less_than_or_equal_to->value => array_sum( Array_Helper::wrap( $value ) ) <= $arg, + }; + } + + if ( false === $valid ) { + break; + } + } + + if ( true === $valid ) { + return $item; + } + + return null; + } + + + /** + * Pre-filters the list of items. + * + * @return array + */ + protected function filter_item_keys(): array { + $items = $this->items; + + // Filter out keys, if keys are specified + if ( isset( $this->filter_args[ Filter::in->key() ] ) ) { + $items = Array_Helper::intersect( array_keys( $this->items ), $this->filter_args[ Filter::in->key() ] ); + unset( $this->filter_args[ Filter::in->key() ] ); + } + + if ( isset( $this->filter_args[ Filter::not_in->key() ] ) ) { + $items = Array_Helper::diff( array_keys( $this->items ), $this->filter_args[ Filter::not_in->key() ] ); + } + + return $items; + } + + + /** + * Finds the first loader item that matches the provided arguments. + * + * @return ?object loader item if found. + */ + public function find(): ?object { + foreach ( $this->filter_item_keys() as $item_key => $item ) { + if ( ! isset( $this->items[ $item_key ] ) ) { + continue; + } + + $item = $this->filter_item( $this->items[ $item_key ] ); + + if ( $item ) { + return $item; + } + } + + return null; + } + + + /** + * Queries a loader registry. + * + * @return object[] Array of registry items. + */ + public function filter(): array { + $results = []; + foreach ( $this->filter_item_keys() as $item_key => $item ) { + if ( ! isset( $this->items[ $item_key ] ) ) { + continue; + } + + $item = $this->filter_item( $this->items[ $item_key ] ); + + if ( $item ) { + $results[$item_key] = $item; + } + } + + return $results; + } + + /** + * Seeds a new instance of the list filter, using pre-generated arguments and items. + * + * @param array $items + * @param array $args + * + * @return static + */ + public static function seed( array $items, array $args ): static { + $self = new static( $items ); + $self->filter_args = $args; + + return $self; + } + +} \ No newline at end of file diff --git a/lib/Helpers/Processors/List_Sorter.php b/lib/Helpers/Processors/List_Sorter.php new file mode 100644 index 0000000..acd4cc0 --- /dev/null +++ b/lib/Helpers/Processors/List_Sorter.php @@ -0,0 +1,67 @@ +sort_args = $args; + + return $self; + } + + /** + * @param $key + * + * @return array + */ + protected function prepare_field( $key ): array { + // Process the argument key + $processed = explode( '__', $key ); + + return [ 'field' => $processed[0], 'direction' => Direction::from( $processed[1] ) ]; + } + + /** + * @return array + * @throws Operation_Failed + */ + public function sort(): array { + foreach ( $this->sort_args as $field__direction => $method ) { + /* @var string $field */ + /* @var Direction $direction */ + extract( $this->prepare_field( $field__direction ) ); + + if ( ! $method ) { + $method = Basic::class; + } + + usort( $this->items, function ( $a, $b ) use ( $field, $direction, $method ) { + /* @var Sort_Method $instance */ + $instance = Object_Helper::make_class( $method, Sort_Method::class ); + + return $instance->sort( $a, $b, $field, $direction ); + } ); + } + + return $this->items; + } + +} \ No newline at end of file diff --git a/lib/Helpers/Processors/Registry_Query.php b/lib/Helpers/Processors/Registry_Query.php new file mode 100644 index 0000000..bf31410 --- /dev/null +++ b/lib/Helpers/Processors/Registry_Query.php @@ -0,0 +1,33 @@ +registry->to_array(), $this->filter_args )->filter(); + $sorted = List_Sorter::seed($filtered_items, $this->sort_args)->sort(); + + return $this->registry->seed( $sorted ); + } + + public function find(): ?object { + return List_Filter::seed($this->registry->to_array(), $this->filter_args)->find(); + } + +} \ No newline at end of file diff --git a/lib/Helpers/String_Helper.php b/lib/Helpers/String_Helper.php new file mode 100644 index 0000000..2a22c89 --- /dev/null +++ b/lib/Helpers/String_Helper.php @@ -0,0 +1,170 @@ +get( $key )->do_actions(); - } - - return $valid; - } - - public function __construct() { - self::mute(); - parent::__construct(); - $this->notify( 'init' ); - $this->set_default_items(); - self::unmute(); - } - - /** - * @return self - */ - public static function instance(): Singleton { - if ( ! self::$instance instanceof self ) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Retrieves all events that have happened for this request. - * - * @since 1.0.0 - * - * @param bool $type The event type to retrieve. If false, this will get all events. - * - * @return array|WP_Error list of all events, or a WP_Error if something went wrong. - */ - public static function get_request_events( $type = false ) { - if ( false !== $type ) { - $events = self::instance()->get( $type ); - - // Return the error. - if ( is_wp_error( $events ) ) { - return $events; - } else { - return (array) $events; - } - - } else { - $result = []; - foreach ( self::instance() as $type => $events ) { - $result[ $type ] = (array) self::instance()->get( $type ); - } - - return $result; - } - } - - /** - * Enqueues an event to be logged in the system - * - * @since 1.0.0 - * - * @param string $type Event log type - * @param string $code The event code to use. - * @param string $message The message to log. - * @param array $data Arbitrary data associated with this event message. - * - * @return Log_Item|WP_Error Log item, with error message. WP_Error if something went wrong. - */ - public static function log( $type, $code, $message, $data = array() ) { - if ( self::is_muted() ) { - return new WP_Error( - 'logger_is_muted', - 'Could not log event because this was ran in a muted action', - [ - 'type' => $type, - 'code' => $code, - 'message' => $message, - 'data' => $data, - ] - ); - } - - $event_type = self::instance()->get( $type ); - - if ( is_wp_error( $event_type ) ) { - return $event_type; - } - - $item = $event_type->log( $code, $message, $data ); - self::instance()->notify( 'event:logged', ['event' => $item, 'event_type' => $event_type]); - - return $item; - } - - /** - * Mutes the logger. - * - * @since 1.0.0 - */ - private static function mute() { - self::$is_muted = true; - } - - /** - * Un-Mutes the logger. - * - * @since 1.0.0 - */ - private static function unmute() { - self::$is_muted = false; - } - - /** - * Does an action, muting all events that would otherwise happen. - * - * @since 1.2.4 - * - * @param callable $action The muted action to call. - * - * @return mixed|WP_Error The action returned result, or WP_Error if something went wrong. - */ - public static function do_muted_action( $action ) { - if ( is_callable( $action ) ) { - self::mute(); - $result = $action(); - self::unmute(); - } else { - $result = new WP_Error( - 'muted_action_not_callable', - 'The provided muted action is not callable', - [ 'ref' => $action, 'type' => 'function' ] - ); - } - - return $result; - } - - /** - * Fetches the mute status of the logger. - * - * @since 1.0.0 - */ - public static function is_muted() { - return self::$is_muted; - } - - /** - * Enqueues an event to be logged in the system, and then returns a WP_Error object. - * - * @since 1.0.0 - * - * @param string $type Event log type - * @param string $code The event code to use. - * @param string $message The message to log. - * @param array $data Arbitrary data associated with this event message. - * - * @return WP_Error Log item, with error message. WP_Error if something went wrong. - */ - public static function log_as_error( $type, $code, $message, $data = array() ) { - $item = self::instance()->log( $type, $code, $message, $data ); - - if ( ! is_wp_error( $item ) ) { - $item = $item->error(); - } - - return $item; - } - - /** - * Logs an error using a WP Error object. - * - * @since 1.0.0 - * - * @param string $type Error log type - * @param WP_Error $wp_error Instance of WP_Error to use for log - * @param array $data Additional data to log - * - * @return Log_Item|WP_Error The logged item, if successful, otherwise WP_Error. - */ - public static function log_wp_error( $type, WP_Error $wp_error, $data = [] ) { - $item = self::instance()->get( $type ); - - if ( is_wp_error( $item ) ) { - return $item; - } - - $error = $item->log_wp_error( $wp_error, $data ); - - return $error; - } - - /** - * Logs an error from within an exception. - * - * @since 1.0.0 - * - * @param string $type Error log type - * @param Exception $exception Exception instance to log. - * @param array $data array Data associated with this error message - * - * @return Log_Item|WP_Error Log Item, with error message if successful, otherwise WP_Error. - */ - public static function log_exception( $type, Exception $exception, $data = array() ) { - $item = self::instance()->get( $type ); - - if ( is_wp_error( $type ) ) { - return $item; - } - - $error = $item->log_exception( $exception, $data ); - - return $error; - } - - /** - * @param string $key - * - * @return Event_Type|WP_Error Event type, if it exists. WP_Error, otherwise. - */ - public function get( $key ) { - $result = parent::get( $key ); - - // If the logged event could not be found, search event type keys. - if ( is_wp_error( $result ) ) { - $event_type = $this->find( [ - 'type' => $key, - ] ); - - // If that also comes up empty, return the error. - if ( is_wp_error( $event_type ) ) { - return $result; - } - - // Return the discovered event. - $result = $event_type; - } - - return $result; - } - - /** - * @inheritDoc - */ - protected function set_default_items() { - $defaults = [ - 'group' => 'core', - ]; - - $this->add( 'emergency', array_merge( $defaults, [ - 'type' => 'emergency', - 'description' => 'Intended to be used only for the most-severe events.', - 'name' => "Emergency", - 'psr_level' => 'emergency', - ] ) ); - - $this->add( 'alert', array_merge( $defaults, [ - 'type' => 'alert', - 'description' => 'Intended to be used when someone should be notified about this problem.', - 'name' => "Alert", - 'psr_level' => 'alert', - ] ) ); - - $this->add( 'critical', array_merge( $defaults, [ - 'type' => 'critical', - 'description' => 'Intended to log events when an error occurs that is potentially damaging.', - 'name' => "Critical Error", - 'psr_level' => 'critical', - ] ) ); - - $this->add( 'error', array_merge( $defaults, [ - 'type' => 'error', - 'description' => 'Intended to log events when something goes wrong.', - 'name' => "Error", - 'psr_level' => 'error', - ] ) ); - - if ( Underpin::is_debug_mode_enabled() ) { - - $this->add( 'warning', array_merge( [ - 'type' => 'warning', - 'description' => 'Intended to log events when something seems wrong.', - 'name' => 'Warning', - 'psr_level' => 'warning', - ] ) ); - - $this->add( 'notice', array_merge( [ - 'type' => 'notice', - 'description' => 'Posts informative notices when something is neither good nor bad.', - 'name' => 'Notice', - 'psr_level' => 'notice', - ] ) ); - - $this->add( 'info', array_merge( [ - 'type' => 'info', - 'description' => 'Posts informative messages that something is most-likely going as-expected.', - 'name' => 'Info', - 'psr_level' => 'info', - ] ) ); - - $this->add( 'debug', array_merge( [ - 'type' => 'debug', - 'description' => 'A place to put information that is only useful in debugging context.', - 'name' => 'Debug', - 'psr_level' => 'debug', - ] ) ); - } - } - - /** - * Gathers errors from a set of variables. - * - * @since 1.0.0 - * - * @param mixed ...$items - * - * @return WP_Error - */ - public static function gather_errors( ...$items ) { - $errors = new WP_Error(); - $items = func_get_args(); - foreach ( $items as $item ) { - self::extract( $errors, $item ); - } - - return $errors; - } - - /** - * Appends errors to a WP_Error object. - * - * @since 1.0.0 - * - * @param WP_Error $error The error to append to. Passed by reference. - * @param Log_Item|WP_Error $log_item The log item to append. If this has multiple errors, it will append all of them. - * - * @return void - */ - public static function extract( WP_Error &$error, $log_item ) { - - // Transform the log item into a WP_Error, if it is a Log_item - if ( $log_item instanceof Log_Item ) { - $log_item = $log_item->error(); - } - - // Append the error, if it is an error. - if ( $log_item instanceof WP_Error ) { - foreach ( $log_item->get_error_codes() as $code ) { - $error->add( $code, $log_item->get_error_message( $code ), $log_item->get_error_data( $code ) ); - } - } - } - -} \ No newline at end of file diff --git a/lib/Middlewares/Rest/Has_Param_Middleware.php b/lib/Middlewares/Rest/Has_Param_Middleware.php new file mode 100644 index 0000000..462f682 --- /dev/null +++ b/lib/Middlewares/Rest/Has_Param_Middleware.php @@ -0,0 +1,28 @@ +get_param( $this->param ); + } catch ( Operation_Failed $exception ) { + throw new Middleware_Exception( message: 'Missing ' . $this->param . '.', code: 400, previous: $exception ); + } + } + + public function get_id(): string|int { + return "has_param_$this->param"; + } + +} \ No newline at end of file diff --git a/lib/Middlewares/Rest/Validate_Type_Middleware.php b/lib/Middlewares/Rest/Validate_Type_Middleware.php new file mode 100644 index 0000000..e1ff64e --- /dev/null +++ b/lib/Middlewares/Rest/Validate_Type_Middleware.php @@ -0,0 +1,32 @@ +get_param( $this->param ) ); + if ( $type !== $this->type->name ) { + throw new Middleware_Exception( message: 'Param ' . $this->param . ' must be a ' . $this->type->name . ', ' . $type . ' given.' ); + } + } + + public function get_id(): string { + return "validate_{$this->param}_type_{$this->type->name}"; + } + +} diff --git a/lib/Registries/Head_Tag_Collection.php b/lib/Registries/Head_Tag_Collection.php new file mode 100644 index 0000000..2ed65cf --- /dev/null +++ b/lib/Registries/Head_Tag_Collection.php @@ -0,0 +1,62 @@ +set_request( $request ); + try { + $tag->do_middleware_actions(); + + return true; + } catch ( Middleware_Exception $e ) { + return false; + } + } + + /** + * Filters out tags that should not be included in this request. + * + * @param Request $request + * + * @return $this + */ + protected function filter_tags( Request $request ): static { + return $this->filter( fn ( Head_Tag $tag ) => $this->include_in_request( $tag, $request ) ); + } + + /** + * Processes the tags, sorting them by dependency. + * + * @throws Operation_Failed + */ + protected function process_tags( Request $request ): array { + return ( new Dependency_Processor( $this->filter_tags( $request ) ) )->mutate(); + } + + /** + * Gets the tags for the current request, sorted by dependency. + * + * @param Request $request + * + * @return Head_Tag_Collection + * @throws Operation_Failed + */ + public function get_request_tags( Request $request ): static { + return $this->seed( $this->process_tags( $request ) ); + } + +} \ No newline at end of file diff --git a/lib/Registries/Immutable_Collection.php b/lib/Registries/Immutable_Collection.php new file mode 100644 index 0000000..d5feb6f --- /dev/null +++ b/lib/Registries/Immutable_Collection.php @@ -0,0 +1,37 @@ +mutable = true; + $result = parent::seed( $items ); + $result->mutable = false; + $this->mutable = false; + + return $result; + } + + public function lock(): static { + $this->mutable = false; + + return $this; + } + + /** + * @inheritDoc + */ + public function add( string $key, mixed $value ): static { + if ( ! $this->mutable ) { + throw new Operation_Failed( 'This collection is immutable. More items cannot be added to it.', 403, 'error', ref: 'class', data: [ static::class ] ); + } + + return parent::add( $key, $value ); + } + +} \ No newline at end of file diff --git a/lib/Registries/Loader.php b/lib/Registries/Loader.php new file mode 100644 index 0000000..1af0a15 --- /dev/null +++ b/lib/Registries/Loader.php @@ -0,0 +1,40 @@ +query()->instance_of( Feature_Extension::class )->get_results()->each( function ( Feature_Extension $extension, string $key ) { + $extension->do_actions(); + Logger::log( + 'debug', + new Log_Item( + code : 'middleware_actions_ran', + message: 'The middleware actions for a registry item ran.', + ref : $key + ) + ); + } ); + } + + function do_middleware_actions(): void { + $this->query()->instance_of( With_Middleware::class )->get_results()->each( function ( With_Middleware $middleware, string $key ) { + $middleware->do_middleware_actions(); + Logger::log( + 'debug', + new Log_Item( + code : 'loader_actions_ran', + message: 'The actions for a registry item ran.', + ref : $key + ) + ); + } ); + } + +} \ No newline at end of file diff --git a/lib/Registries/Logger.php b/lib/Registries/Logger.php new file mode 100644 index 0000000..f50adae --- /dev/null +++ b/lib/Registries/Logger.php @@ -0,0 +1,452 @@ +is_muted = true; + $this->set_default_items(); + $this->is_muted = false; + self::$setting_up = false; + } + + + /** + * @return self + * @throws Instance_Not_Ready + */ + public static function instance(): static { + // This instance is in the midst of being set up, so return null. + if ( self::$setting_up ) { + throw new Instance_Not_Ready( 'The logger is still being set up, and cannot be accessed.', null ); + } + + if ( ! isset( self::$instance ) || ! self::$instance instanceof self ) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Sets the volume for logged events. + * If an event requires the volume is higher than this set value, it will not be logged. + * + * @param int $volume + * + * @throws Instance_Not_Ready + * @return Logger + */ + public static function set_volume( int $volume ): Logger { + $instance = self::instance(); + $instance->volume = $volume; + $instance->broadcast( Logger_Events::volume_changed, new Int_Provider( $volume ) ); + + return $instance; + } + + /** + * Returns true if the provided event type can be logged. + * + * @param Event_Type $event_type + * + * @return bool + */ + private function can_log( Event_Type $event_type ): bool { + return ! $this->is_muted() && $this->volume >= $event_type->get_volume(); + } + + /** + * Enqueues an event to be logged in the system + * + * + * @param string $type Event log type + * @param Log_Item $log_item Item to log + * + * @return ?Log_Item Log item, with error message. Null if muted, or type is invalid. + */ + public static function log( string $type, Log_Item $log_item ): ?Log_Item { + try { + $event_type = self::instance()->get( $type ); + + if ( ! self::instance()->can_log( $event_type ) ) { + return null; + } + } catch ( Unknown_Registry_Item|Instance_Not_Ready ) { + return null; + } + + return $event_type->log( $log_item ); + } + + protected static function auto_log( string $type, Log_Item|Exception $item ): ?Log_Item { + if ( $item instanceof Log_Item ) { + return self::log( $type, $item ); + } else { + return self::log_exception( $type, $item ); + } + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function debug( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'debug', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function info( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'info', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function notice( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'notice', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function warning( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'warning', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function error( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'error', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function critical( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'critical', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function alert( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'alert', $item ); + } + + /** + * Logs an item of the specified type. + * + * @param Log_Item|Exception $item A log item to log, or an exception. + * + * @return Log_Item|null + */ + public static function emergency( Log_Item|Exception $item ): ?Log_Item { + return self::auto_log( 'emergency', $item ); + } + + /** + * Mutes the logger. + * + * @throws Instance_Not_Ready + * + */ + public static function mute(): Logger { + $instance = self::instance(); + $instance->is_muted = true; + $instance->broadcast( Logger_Events::muted ); + + return $instance; + } + + /** + * Un-Mutes the logger. + * + * @throws Instance_Not_Ready + * + */ + public static function unmute(): Logger { + $instance = self::instance(); + $instance->broadcast( Logger_Events::unmuted ); + $instance->is_muted = false; + + return $instance; + } + + /** + * Fetches the mute status of the logger. + * + */ + public static function is_muted(): bool { + try { + return self::instance()->is_muted; + } catch ( Instance_Not_Ready ) { + return true; + } + } + + /** + * Logs an error from within an exception. + * + * + * @param string $type Error log type + * @param Exception $exception Exception instance to log. + * @param string|int|null $ref + * @param array $data array Data associated with this error message + * + * @return ?Log_Item Log Item, with error message if successful, otherwise null. + */ + public static function log_exception( string $type, Exception $exception, string|int|null $ref = null, array $data = array() ): ?Log_Item { + try { + $event_type = self::instance()->get( $type ); + + if ( ! self::instance()->can_log( $event_type ) ) { + return null; + } + + $error = $event_type->log_exception( $exception, $ref, $data ); + } catch ( Unknown_Registry_Item $e ) { + // Fail silently if the instance is invalid. + return null; + } + + return $error; + } + + /** + * Gets all events in the logger + * + * @return Mutable_Collection + * @throws Operation_Failed + * @throws ReflectionException + */ + public function get_events(): Mutable_Collection { + return Immutable_Collection::make( Log_Item::class )->seed( $this->reduce( fn ( array $acc, Event_Type $event_type ) => array_merge( $acc, $event_type->to_array() ), [] ) ); + } + + /** + * @param string $key + * + * @return Interfaces\Event_Type + * @throws Unknown_Registry_Item + */ + public function get( string $key ): Event_Type { + try { + return parent::get( $key ); + // If the logged event could not be found, search event type keys. + } catch ( Unknown_Registry_Item $exception ) { + /* @var Event_Type $result */ + $result = $this->query()->equals( 'type', $key )->find(); + + if ( ! $result ) { + throw $exception; + } + + return $result; + } + } + + /** + * @throws Invalid_Registry_Item + * @throws Operation_Failed + */ + protected function set_default_items(): void { + $defaults = [ + 'group' => 'core', + ]; + + $this + ->add( 'emergency', array_merge( $defaults, [ + 'type' => 'emergency', + 'description' => 'Intended to be used only for the most-severe events.', + 'name' => "Emergency", + 'psr_level' => 'emergency', + 'volume' => 10, + ] ) ) + ->add( 'alert', array_merge( $defaults, [ + 'type' => 'alert', + 'description' => 'Intended to be used when someone should be notified about this problem.', + 'name' => "Alert", + 'psr_level' => 'alert', + 'volume' => 20, + ] ) ) + ->add( 'critical', array_merge( $defaults, [ + 'type' => 'critical', + 'description' => 'Intended to log events when an error occurs that is potentially damaging.', + 'name' => "Critical Error", + 'psr_level' => 'critical', + 'volume' => 30, + ] ) ) + ->add( 'error', array_merge( $defaults, [ + 'type' => 'error', + 'description' => 'Intended to log events when something goes wrong.', + 'name' => "Error", + 'psr_level' => 'error', + 'volume' => 40, + ] ) ) + ->add( 'warning', array_merge( [ + 'type' => 'warning', + 'description' => 'Intended to log events when something seems wrong.', + 'name' => 'Warning', + 'psr_level' => 'warning', + 'volume' => 50, + ] ) ) + ->add( 'notice', array_merge( [ + 'type' => 'notice', + 'description' => 'Posts informative notices when something is neither good nor bad.', + 'name' => 'Notice', + 'psr_level' => 'notice', + 'volume' => 60, + ] ) ) + ->add( 'info', array_merge( [ + 'type' => 'info', + 'description' => 'Posts informative messages that something is most-likely going as-expected.', + 'name' => 'Info', + 'psr_level' => 'info', + 'volume' => 70, + ] ) ) + ->add( 'debug', array_merge( [ + 'type' => 'debug', + 'description' => 'A place to put information that is only useful in debugging context.', + 'name' => 'Debug', + 'psr_level' => 'debug', + 'volume' => 80, + ] ) ); + + $this->broadcast( Logger_Events::ready ); + } + + /** + * @param string $key The enum case to use as the key. + * @param Observer $observer + * + * @return $this + * @throws Operation_Failed + * @throws Unknown_Registry_Item + * @see Logger_Events + * + */ + public function attach( string $key, Interfaces\Observer $observer ): static { + $this->get_broadcaster()->attach( $key, $observer ); + + return $this; + } + + /** + * @param string $key The enum case to use as the key. + * @param string $observer_id The instance to detach. + * + * @see Logger_Events + * + * @return $this + * @throws Operation_Failed + */ + function detach( string $key, string $observer_id ): static { + $this->get_broadcaster()->detach( $key, $observer_id ); + + return $this; + } + + protected function broadcast( Logger_Events $key, ?Data_Provider $args = null ): static { + $this->get_broadcaster()->broadcast( $key->name, $args ); + + Logger::debug( new Log_Item( + code : 'item_broadcasted', + message: "An item was broadcasted", + context: 'instance', + ref : get_called_class() + ) ); + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Registries/Mutable_Collection.php b/lib/Registries/Mutable_Collection.php new file mode 100644 index 0000000..6a182f0 --- /dev/null +++ b/lib/Registries/Mutable_Collection.php @@ -0,0 +1,15 @@ +abstraction_class = $abstraction_class; + $self->default_factory = $default ?? $self->abstraction_class; + + return $self; + } + +} \ No newline at end of file diff --git a/lib/Registries/Mutable_Collection_With_Remove.php b/lib/Registries/Mutable_Collection_With_Remove.php new file mode 100644 index 0000000..fa7da7a --- /dev/null +++ b/lib/Registries/Mutable_Collection_With_Remove.php @@ -0,0 +1,10 @@ + $value ) { + if ( isset( $param_signature[ $key ] ) ) { + $type = $param_signature[ $key ]; + $this->add( $key, ( new Param( $key, $type ) )->set_value( $value ) ); + } else { + $this->add( $key, ( new Param( $key, Types::String ) )->set_value( $value ) ); + } + } + } catch ( Operation_Failed $exception ) { + throw new Url_Exception( 'Could not create URL from string', 500, $exception ); + } + + return $this; + } + + /** + * Magic method. Makes it possible to cast this registry with (string) + * + * @return string + */ + public function __toString(): string { + return $this->to_string(); + } + + /** + * Converts the set of URL params into a string. + * + * @return string + */ + public function to_string(): string { + return (string) ( new Array_Processor( $this->to_array() ) ) + ->values() + ->each( fn ( Param $value ) => $value->to_string() ) + ->set_separator( '&' ); + } + +} \ No newline at end of file diff --git a/lib/Traits/Can_Remove_Registry_Item.php b/lib/Traits/Can_Remove_Registry_Item.php new file mode 100644 index 0000000..593a9c3 --- /dev/null +++ b/lib/Traits/Can_Remove_Registry_Item.php @@ -0,0 +1,27 @@ +storage[ $key ] ) ) { + throw new Operation_Failed( 'Could not remove, item does not exist.' ); + } + + unset( $this->storage[ $key ] ); + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Traits/Feature_Extension.php b/lib/Traits/Feature_Extension.php deleted file mode 100644 index a9d398e..0000000 --- a/lib/Traits/Feature_Extension.php +++ /dev/null @@ -1,41 +0,0 @@ -filter_args[ Filter::less_than->field( $field ) ] = $number; + + return $this; + } + + public function less_than_or_equal( string $field, int|float $number ): static { + $this->filter_args[ Filter::less_than_or_equal_to->field( $field ) ] = $number; + + return $this; + } + + public function greater_than( string $field, int|float $number ): static { + $this->filter_args[ Filter::greater_than->field( $field ) ] = $number; + + return $this; + } + + public function greater_than_or_equal( string $field, int|float $number ): static { + $this->filter_args[ Filter::greater_than_or_equal_to->field( $field ) ] = $number; + + return $this; + } + + public function filter_from_callback( string $field, callable $callback ): static { + $this->filter_args[ Filter::callback->field( $field ) ] = $callback; + + return $this; + } + + /** + * Sets the query to only include items that are not an of the provided instances. + * + * @param array $values The values to filter. + * + * @return $this + */ + public function not_instance_of( ...$values ): static { + $this->filter_args[ Filter::not_in->field( 'instanceof' ) ] = $values; + + return $this; + } + + /** + * Sets the query to only include items that are an instance of all the provided instances. + * + * @param array $values The values to filter. + * + * @return $this + */ + public function has_all_instances( ...$values ): static { + $this->filter_args[ Filter::and->field( 'instanceof' ) ] = $values; + + return $this; + } + + /** + * Sets the query to only include items that are instance of any the provided instances. + * + * @param array $values The values to filter. + * + * @return $this + */ + public function has_any_instances( ...$values ): static { + $this->filter_args[ Filter::in->field( 'instanceof' ) ] = $values; + + return $this; + } + + /** + * Sets the query to only include items that are instance provided instances. + * + * @param string $value the instance + * + * @return $this + */ + public function instance_of( string $value ): static { + $this->filter_args[ Filter::equals->field( 'instanceof' ) ] = $value; + + return $this; + } + + /** + * Sets the query to filter out items whose field has any of the provided values. + * + * @param string $field The field to check against. + * @param array $values The values to filter. + * + * @return $this + */ + public function not_in( string $field, ...$values ): static { + $this->filter_args[ Filter::not_in->field( $field ) ] = $values; + + return $this; + } + + /** + * Sets the query to filter out items whose field does not have all the provided values. + * + * @param string $field The field to check against. + * @param array $values The values to filter. + * + * @return $this + */ + public function and( string $field, ...$values ): static { + $this->filter_args[ Filter::and->field( $field ) ] = $values; + + return $this; + } + + /** + * Sets the query to filter out items whose field does not have all the provided values. + * + * @param string $field The field to check against. + * @param array $values The values to filter. + * + * @return $this + */ + public function in( string $field, ...$values ): static { + $this->filter_args[ Filter::in->field( $field ) ] = $values; + + return $this; + } + + + /** + * Sets the query to filter out items whose value is not identical to the provided value. + * + * @param string $field The field to check against. + * @param mixed $value The value to check. + * + * @return $this + */ + public function equals( string $field, mixed $value ): static { + $this->filter_args[ Filter::equals->field( $field ) ] = $value; + + return $this; + } + + /** + * Sets the query to filter out items whose key has any of the provided values. + * + * @param array $values The values to filter. + * + * @return $this + */ + public function key_not_in( ...$values ): static { + $this->filter_args[ Filter::not_in->key() ] = $values; + + return $this; + } + + /** + * Sets the query to filter out items whose key does not have all the provided values. + * + * @param array $values The values to filter. + * + * @return $this + */ + public function key_in( ...$values ): static { + $this->filter_args[ Filter::in->key() ] = $values; + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Traits/Instance_Setter.php b/lib/Traits/Instance_Setter.php deleted file mode 100644 index c32154d..0000000 --- a/lib/Traits/Instance_Setter.php +++ /dev/null @@ -1,53 +0,0 @@ - $value ) { - if ( property_exists( $this, $arg ) ) { - $this->$arg = $value; - unset( $args[ $arg ] ); - } - } - } - - /** - * Set a custom callback from the provided argument, and set or arguments. - * - * @since 1.2.0 - * - * @param callable $callable The callback - * @param mixed ...$args The arguments to pass to the callback - * - * @return false|mixed|\WP_Error - */ - protected function set_callable( $callable, ...$args ) { - if ( is_callable( $callable ) ) { - return call_user_func( $callable, ...$args ); - } - - return new \WP_Error( - 'invalid_callback', - 'The provided callback is invalid', - [ - 'callback' => $callable, - 'stack' => debug_backtrace(), - ] - ); - } - -} \ No newline at end of file diff --git a/lib/Traits/Sort_Params.php b/lib/Traits/Sort_Params.php new file mode 100644 index 0000000..2671fe2 --- /dev/null +++ b/lib/Traits/Sort_Params.php @@ -0,0 +1,27 @@ + $method + * + * @return $this + */ + public function sort_by( string $field, Direction $direction = Direction::Ascending, Sort_Method|string $method = Basic::class ): static { + $this->sort_args[ $field . '__' . $direction->value ] = $method; + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Traits/Templates.php b/lib/Traits/Templates.php deleted file mode 100644 index 9fc31df..0000000 --- a/lib/Traits/Templates.php +++ /dev/null @@ -1,486 +0,0 @@ - 'private', - ]; - } - - /** - * Gets the specified template, if it is valid. - * - * @since 1.0.0 - * - * @param $template_name string The template name to get. - * @param $params array of param values that can be used in the template via get_param(). - * - * @return string The template contents. - */ - public function get_template( $template_name, array $params = [] ) { - - if ( $this->is_valid_template( $template_name ) ) { - - if ( $this->template_file_exists( $template_name ) ) { - - $template = $this->include_template( $template_name, $params ); - } else { - $template_path = $this->get_template_path( $template_name ); - $template = Logger::log_as_error( - 'error', - 'template_file_does_not_exist', - __( "Template $template_name was not loaded because the file located at $template_path does not exist.", 'underpin' ) - ); - - /** - * Fires just after the template loader determines that the template file does not exist. - */ - $this->notify( 'template:file_not_found', [ 'template_name' => $template_name, 'params' => $params ] ); - } - } else { - $template = Logger::log_as_error( - 'error', - 'underpin_invalid_template', - "Template was not loaded because it is not in the list of use-able templates.", - [ - 'class' => get_called_class(), - 'name' => $this->name, - 'description' => $this->description, - 'template' => $template_name, - ] - ); - - /** - * Fires just after the template loader determines that the template is not in the current class template schema. - */ - $this->notify( 'template:not_in_schema', [ 'template_name' => $template_name, 'params' => $params ] ); - } - - return is_wp_error( $template ) ? '' : $template; - } - - /** - * Gets the current template depth. - * - * The template depth goes up each time a template is loaded within the base template. This is used internally to - * determine which params should be loaded-in, but it can also be useful when recursively loading in a template. - * - * @since 1.0.0 - * - * @return int The current template depth. - */ - public function get_depth() { - return $this->depth; - } - - /** - * Get the value of the specified param, if it exists. - * - * Params are passed into a template via the params argument of get_template. - * - * @since 1.0.0 - * - * @param mixed $param The param to load. - * @param mixed $default (optional) The default value of the param, if it does not exist. - * - * @return mixed The parameter value, if it exists. Otherwise, this will use the default value. - */ - public function get_param( $param, $default = false ) { - if ( isset( $this->params[ $this->depth ] ) && isset( $this->params[ $this->depth ][ $param ] ) ) { - $param = $this->params[ $this->depth ][ $param ]; - } else { - $param = $default; - } - - return $param; - } - - /** - * Retrieves all of the params for the current template. - * - * @since 1.0.0 - * - * @return array List of params for the current template - */ - public function get_params() { - if ( isset( $this->params[ $this->depth ] ) ) { - return $this->params[ $this->depth ]; - } - - return []; - } - - /** - * Gets the template directory based on the template group. - * - * @since 1.0.0 - * - * @return string - */ - protected function get_template_directory() { - $template_group = $this->get_template_group(); - $template_directory = trailingslashit( $this->get_template_root_path() ) . $template_group; - - return $template_directory; - } - - /** - * Gets the visibility of the current template - * - * @since 1.0.0 - * - * @param string $template_name The template to use. - * - * @return string The template visibility. - */ - private function get_template_visibility( $template_name ) { - $visibility = $this->get_template_arg( $template_name, 'override_visibility' ); - - // If the override visibility is invalid, grab the default. - if ( ! in_array( $visibility, $this->visibility_types ) ) { - $defaults = $this->get_template_arg_defaults(); - $visibility = $defaults['override_visibility']; - } - - return $visibility; - } - - /** - * Gets the template path, given the file name. - * - * @since 1.0.0 - * - * @param $template_name string the template name to include. - * - * @return string The complete template path. - */ - protected function get_template_path( $template_name ) { - return trailingslashit( $this->get_template_directory() ) . $template_name . '.php'; - } - - /** - * Locates the template that should be loaded. - * - * If a template is defined as public, the template can be overridden inside a theme, or child theme. - * This function checks the child theme, then the parent theme, and finally the plugin fallback. - * - * @since 1.0.0 - * - * @param $template_name string The template name to locate. - * - * @return string The path to the located template. - */ - protected function locate_template( $template_name ) { - $template_visibility = $this->get_template_visibility( $template_name ); - // Bail early if the template is private. - if ( 'private' === $template_visibility ) { - return $this->get_template_path( $template_name ); - } - - $template_group = trailingslashit( $this->get_template_group() ); - $override_path = trailingslashit( $this->get_override_dir() ) . $template_group; - $override_file_path = trailingslashit( $override_path ) . $template_name . '.php'; - - // Check to see if we have a template override from another plugin - if ( 'plugins' === $template_visibility || 'public' === $template_visibility ) { - - - // If another plugin has an override, use it. - if ( file_exists( $override_file_path ) ) { - $template = $override_file_path; - } - - // Make sure someone isn't trying to force-load from the theme. - if ( false !== strpos( $override_path, get_stylesheet_directory() ) || false !== strpos( $override_path, get_template_directory() ) ) { - unset( $template ); - } - } - - // Check to see if we have a theme override. - if ( 'themes' === $template_visibility || 'public' === $template_visibility ) { - - // If the active child theme has an override, use it. - if ( file_exists( trailingslashit( get_stylesheet_directory() ) . $override_file_path ) ) { - $template = trailingslashit( get_stylesheet_directory() ) . $override_file_path; - - // If the active parent theme has an override, use it. - } elseif ( file_exists( trailingslashit( get_template_directory() ) . $override_file_path ) ) { - $template = trailingslashit( get_template_directory() ) . $override_file_path; - } - } - - // If we didn't get an override, load the default. - if ( ! isset( $template ) ) { - $template = $this->get_template_path( $template_name ); - } - - return $template; - } - - /** - * Specify the override directory for other themes and plugins. - * - * @since 1.2.0 - * - * @return string - */ - protected function get_override_dir() { - return 'underpin-templates/'; - } - - /** - * Gets a single argument from a template configuration. Falls back to the default value - * - * @since 1.0.0 - * - * @param string $template_name The template name to get the argument from. - * @param string $arg The argument to fetch. - * - * @return WP_Error|mixed A WP Error object if something went wrong, the argument for the current value otherwise. - */ - private function get_template_arg( $template_name, $arg ) { - if ( isset( $this->get_template_arg_defaults()[ $arg ] ) ) { - if ( $this->is_valid_template( $template_name ) ) { - $templates = $this->get_templates(); - - $result = isset( $templates[ $template_name ][ $arg ] ) ? $templates[ $template_name ][ $arg ] : $this->get_template_arg_defaults()[ $arg ]; - } else { - $result = Logger::log_as_error( - 'error', - 'underpin_get_arg_invalid_template', - "Template $template_name argument $arg was not fetched because $template_name is not a valid template." - ); - } - } else { - $result = Logger::log_as_error( - 'error', - 'underpin_get_arg_invalid_argument', - "Template $template_name argument $arg was not fetched because $arg is not a valid template argument." - ); - } - - return $result; - } - - /** - * Checks to see if the current template is defined. - * - * @since 1.0.0 - * - * @param $template_name string The template name to check. - * - * @return bool True if the template is valid, false otherwise. - */ - public function is_valid_template( $template_name ) { - $valid_templates = array_keys( $this->get_templates() ); - - return in_array( $template_name, $valid_templates ); - } - - /** - * Checks to see if the template file exists. - * - * @since 1.0.0 - * - * @param $template_name string The template name to check. - * - * @return bool True if the template file exists, false otherwise. - */ - public function template_file_exists( $template_name ) { - return file_exists( $this->get_template_path( $template_name ) ); - } - - /** - * Updates current depth and params, gets the template contents. - * - * @since 1.0.0 - * - * @param string $template_name The template name. - * @param array $params The params to use in the template. - * - * @return false|string The template contents if the file exists, false otherwise. - */ - private function include_template( $template_name, $params ) { - - $this->depth++; - if ( 'private' !== $this->get_template_arg( $template_name, 'override_visibility' ) ) { - $this->params[ $this->depth ] = $this->apply_filters( "template:params", new Accumulator( [ - 'default' => $params, - 'template_name' => $template_name, - 'path' => $this->get_template_path( $template_name ), - 'valid_callback' => function ( $state ) { - return is_array( $state ); - }, - ] ) ); - } else { - $this->params[ $this->depth ] = $params; - } - - $template_filter_args = [ 'template_name' => $template_name, 'path' => $this->get_template_path( $template_name ) ]; - - if ( 'private' !== $this->get_template_visibility( $template_name ) ) { - /** - * Fires just before the template output buffer begins. - */ - $this->notify( 'template:before_buffer', $template_filter_args ); - } - - ob_start(); - - if ( 'private' !== $this->get_template_visibility( $template_name ) ) { - /** - * Fires inside of the output buffer, just before the template is rendered. - * - * Note that this only fires when the provided template is not private. - */ - $this->notify( 'template:before_template', $template_filter_args ); - } - - underpin_include_file_with_scope( $this->locate_template( $template_name ), [ - 'template' => $this, - ] ); - - if ( 'private' !== $this->get_template_visibility( $template_name ) ) { - - /** - * Fires inside of the output buffer, just after the template is rendered. - * - * Note that this only fires when the provided template is not private. - */ - $this->notify( 'template:after_template', $template_filter_args ); - } - - $result = ob_get_clean(); - - if ( 'private' !== $this->get_template_visibility( $template_name ) ) { - - /** - * Fires outside of the output buffer, just after the template is rendered. - * - * Note that this only fires when the provided template is not private. - */ - $this->notify( 'template:after_buffer', $template_filter_args ); - } - - unset( $this->params[ $this->depth ] ); - $this->depth--; - - return $result; - } - -} - -/** - * Includes a file and passes the specified scope items as local scope. - * - * @since 1.0.0 - * - * @param $file string The file to include - * @param $scope array The scope items keyed by their variable name. - * - * @return bool True if include was successful, false otherwise. - */ -function underpin_include_file_with_scope( $file, $scope ) { - if ( file_exists( $file ) ) { - extract( $scope ); - include $file; - - return true; - } - - return false; -} \ No newline at end of file diff --git a/lib/Traits/With_Broadcaster.php b/lib/Traits/With_Broadcaster.php new file mode 100644 index 0000000..965c6ed --- /dev/null +++ b/lib/Traits/With_Broadcaster.php @@ -0,0 +1,27 @@ +broadcaster ) ) { + $this->broadcaster = new Broadcaster; + } + + return $this->broadcaster; + } + +} \ No newline at end of file diff --git a/lib/Traits/With_Closure_Converter.php b/lib/Traits/With_Closure_Converter.php new file mode 100644 index 0000000..afe802c --- /dev/null +++ b/lib/Traits/With_Closure_Converter.php @@ -0,0 +1,36 @@ +getFileName() ); + $file->seek( $ref->getStartLine() - 1 ); + $content = ''; + while ( $file->key() < $ref->getEndLine() ) { + $content .= $file->current(); + $file->next(); + } + + return array( + $content, + $ref->getStaticVariables(), + ); + } +} \ No newline at end of file diff --git a/lib/Traits/With_Dependencies.php b/lib/Traits/With_Dependencies.php new file mode 100644 index 0000000..49dba84 --- /dev/null +++ b/lib/Traits/With_Dependencies.php @@ -0,0 +1,50 @@ +priority; + } + + /** + * @inheritDoc + */ + public function get_dependencies(): array { + return $this->dependencies; + } + + /** + * @inheritDoc + */ + public function add_dependency( string $dependency_id ): static { + Array_Helper::append( $this->dependencies, $dependency_id ); + + return $this; + } + + /** + * @inheritDoc + */ + public function remove_dependency( string $dependency_id ): static { + $this->dependencies = ( new Array_Processor( $this->dependencies ) ) + ->flip() + ->remove( $dependency_id ) + ->flip() + ->to_array(); + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Traits/With_Instance.php b/lib/Traits/With_Instance.php new file mode 100644 index 0000000..51a5404 --- /dev/null +++ b/lib/Traits/With_Instance.php @@ -0,0 +1,23 @@ +id; + } + + public function set_id( int|null $id ): static { + $this->id =$id; + + return $this; + } + +} \ No newline at end of file diff --git a/lib/Traits/With_Middleware.php b/lib/Traits/With_Middleware.php deleted file mode 100644 index 8d5f5c7..0000000 --- a/lib/Traits/With_Middleware.php +++ /dev/null @@ -1,56 +0,0 @@ -middlewares as $value ) { - $this->attach( 'middleware', $value ); - } - } - - /** - * Fires the middleware actions if it has not already been ran. - * - * @since 1.3.0 - */ - public function do_middleware_actions() { - $this->prepare_middlewares(); - if ( false === $this->middleware_ran ) { - $this->notify( 'middleware' ); - } - - $this->middleware_ran = true; - } - -} \ No newline at end of file diff --git a/lib/Traits/With_Object_Cache.php b/lib/Traits/With_Object_Cache.php new file mode 100644 index 0000000..0697a46 --- /dev/null +++ b/lib/Traits/With_Object_Cache.php @@ -0,0 +1,21 @@ +object_cache[ $key ] ) ) { + $this->object_cache[ $key ] = $setter(); + } + + return $this->object_cache[$key]; + } + +} \ No newline at end of file diff --git a/lib/Traits/With_Static_Subject.php b/lib/Traits/With_Static_Subject.php deleted file mode 100644 index 6f2346b..0000000 --- a/lib/Traits/With_Static_Subject.php +++ /dev/null @@ -1,92 +0,0 @@ - Object_Registry::class, - ] ); - } - - if ( is_wp_error( self::$observer_registry->get( $key ) ) ) { - self::$observer_registry->add( $key, new Object_Registry( [ - 'abstraction_class' => '\Underpin\Abstracts\Observer', - 'default_factory' => '\Underpin\Factories\Observer', - ] ) ); - } - } - - public static function attach( $key, \Underpin\Abstracts\Observer $observer ) { - self::init_observer_registry( $key ); - - self::$observer_registry[ $key ][] = $observer; - } - - public static function detach( $key, $observer_id ) { - self::init_observer_registry( $key ); - - foreach ( self::$observer_registry->get( $key ) as $iterator => $observer ) { - if ( $observer->id === $observer_id ) { - unset( self::$observer_registry[ $key ][ $iterator ] ); - } - } - - } - - private function setup_args( $key, $args = null ) { - self::init_observer_registry( $key ); - - // Bail if there are no observers set. - if ( empty( (array) self::$observer_registry->get( $key ) ) ) { - return false; - } - - if ( ! $args instanceof Storage ) { - $args = new Simple_Storage( $args ); - } - - return $args; - } - - protected function notify( $key, $args = null ) { - $args = $this->setup_args( $key, $args ); - - if ( false === $args ) { - return; - } - - /* @var Observer $observer */ - foreach ( Dependency_Processor::prepare( self::$observer_registry->get( $key ) ) as $observer ) { - $observer->update( $this, $args ); - } - } - - protected function apply_filters( $key, Accumulator $accumulator ) { - $args = $this->setup_args( $key, $accumulator ); - - if ( ! $args instanceof Accumulator ) { - return $accumulator->get_state(); - } - - /* @var Observer $observer */ - foreach ( Dependency_Processor::prepare( self::$observer_registry->get( $key ) ) as $observer ) { - $observer->update( $this, $args ); - } - - return $args->get_state(); - } - -} \ No newline at end of file diff --git a/lib/Traits/With_Subject.php b/lib/Traits/With_Subject.php deleted file mode 100644 index f22fbd4..0000000 --- a/lib/Traits/With_Subject.php +++ /dev/null @@ -1,118 +0,0 @@ -observer_registry ) ) { - $this->observer_registry = new Object_Registry( [ - 'abstraction_class' => Object_Registry::class, - ] ); - } - - if ( is_wp_error( $this->observer_registry->get( $key ) ) ) { - $this->observer_registry->add( $key, new Object_Registry( [ - 'abstraction_class' => '\Underpin\Abstracts\Observer', - 'default_factory' => '\Underpin\Factories\Observer', - ] ) ); - } - } - - public function attach( $key, Observer $observer ) { - $this->init_observer_registry( $key ); - - $this->observer_registry[ $key ][] = $observer; - - Logger::log( - 'info', - 'event_attached', - 'Event attached', - [ - 'subject' => get_called_class(), - 'key' => $key, - 'name' => $observer->name, - 'description' => $observer->description, - ] - ); - } - - public function detach( $key, $observer_id ) { - $this->init_observer_registry( $key ); - - foreach ( $this->observer_registry->get( $key ) as $iterator => $observer ) { - if ( $observer->id === $observer_id ) { - Logger::log( - 'info', - 'event_detached', - 'Event detached', - [ - 'subject' => get_called_class(), - 'key' => $key, - 'name' => $observer->name, - 'description' => $observer->description, - ] - ); - unset( $this->observer_registry[ $key ][ $iterator ] ); - } - } - - } - - private function setup_args( $key, $args = null ) { - self::init_observer_registry( $key ); - - // Bail if there are no observers set. - if ( empty( (array) $this->observer_registry->get( $key ) ) ) { - return false; - } - - - if ( ! $args instanceof Storage ) { - $args = new Simple_Storage( $args ); - } - - return $args; - } - - protected function notify( $key, $args = [] ) { - $args = $this->setup_args( $key, $args ); - - if ( false === $args ) { - return; - } - - /* @var Observer $observer */ - foreach ( Dependency_Processor::prepare( $this->observer_registry->get( $key ) ) as $observer ) { - $observer->update( $this, $args ); - } - } - - protected function apply_filters( $key, Accumulator $accumulator ) { - $args = $this->setup_args( $key, $accumulator ); - - if ( ! $args instanceof Accumulator ) { - return $accumulator->get_state(); - } - - /* @var Observer $observer */ - foreach ( Dependency_Processor::prepare( $this->observer_registry->get( $key ) ) as $observer ) { - $observer->update( $this, $args ); - } - - return $args->get_state(); - } - -} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d5bc659 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + ./tests/Unit + + + + + + + + ./lib + + + \ No newline at end of file diff --git a/tests/Helpers.php b/tests/Helpers.php new file mode 100644 index 0000000..2551cf2 --- /dev/null +++ b/tests/Helpers.php @@ -0,0 +1,28 @@ +getMethod( $method_name ); + $method->setAccessible( true ); + + return $method->invokeArgs( $object, $args ); + } + + public static function get_protected_property($object, $property) { + $reflection = new \ReflectionClass($object); + $reflection_property = $reflection->getProperty($property); + + return $reflection_property; + } + + public static function set_protected_property($object, $property, $value) { + $reflection_property = self::get_protected_property($object,$property); + $reflection_property->setAccessible(true); + + $reflection_property->setValue($object, $value); + } +} \ No newline at end of file diff --git a/tests/Test_Case.php b/tests/Test_Case.php new file mode 100644 index 0000000..658ebb4 --- /dev/null +++ b/tests/Test_Case.php @@ -0,0 +1,9 @@ +get_instance(); + + Helpers::set_protected_property( $instance, $key, $value ); + $this->assert_field( $value, call_user_func( [ $instance, "get_$key" ] ) ); + } + + abstract public function provider_can_get_fields(): Generator; + + abstract protected function get_instance(): object; + + protected function assert_field( mixed $expected, mixed $value ): void { + if ( method_exists( $this, 'assertSame' ) ) { + $this->assertSame( $expected, $value ); + } + } + +} \ No newline at end of file diff --git a/tests/Unit/Abstracts/Observer_Test.php b/tests/Unit/Abstracts/Observer_Test.php new file mode 100644 index 0000000..c586747 --- /dev/null +++ b/tests/Unit/Abstracts/Observer_Test.php @@ -0,0 +1,48 @@ +getMockForAbstractClass( originalClassName: Observer::class, callOriginalConstructor: false ); + } + + public function provider_can_get_fields(): Generator { + yield 'id' => [ 'id', 'foo' ]; + yield 'priority' => [ 'priority', 20 ]; + yield 'dependencies' => [ 'dependencies', [ 'this', 'that' ] ]; + } + + /** + * @covers \Underpin\Abstracts\Observer::add_dependency + */ + public function test_can_add_dependency(): void { + $instance = $this->get_instance()->add_dependency( "bar" ); + $dependencies = Helpers::get_protected_property( $instance, 'dependencies' ); + + $this->assertSame( [ 'bar' ], $dependencies->getValue( $instance ) ); + } + + /** + * @covers \Underpin\Abstracts\Observer::remove_dependency + */ + public function test_can_remove_dependency(): void { + $instance = $this->get_instance(); + Helpers::set_protected_property( $instance, 'dependencies', [ 'bar', 'foo' ] ); + + $instance->remove_dependency( 'foo' ); + + $this->assertSame( [ 'bar' ], Helpers::get_protected_property( $instance, 'dependencies' )->getValue( $instance ) ); + } + +} \ No newline at end of file diff --git a/tests/Unit/Abstracts/Registries/Object_Registry_Test.php b/tests/Unit/Abstracts/Registries/Object_Registry_Test.php new file mode 100644 index 0000000..b0974d8 --- /dev/null +++ b/tests/Unit/Abstracts/Registries/Object_Registry_Test.php @@ -0,0 +1,122 @@ +createPartialMock( Object_Registry::class, [ 'get_class' ] ); + $expected = (object) [ 'bar' => 'baz' ]; + Helpers::set_protected_property( $class, 'default_factory', 'foo' ); + + $class->expects( $this->once() )->method( 'get_class' )->willReturn( $expected ); + + Helpers::call_inaccessible_method( $class, '_add', 'foo', 'bar' ); + + $this->assertSame( [ 'foo' => $expected ], $class->to_array() ); + } + + /** + * @return void + * @covers \Underpin\Abstracts\Registries\Object_Registry::add + * @throws \Underpin\Exceptions\Invalid_Registry_Item + * @throws \Underpin\Exceptions\Operation_Failed + */ + public function test_can_add(): void { + $class = $this->createPartialMock( Object_Registry::class, [ '_add', 'validate_item', 'init_object' ] ); + $key = 'key'; + $value = 'value'; + $class->expects( $this->once() )->method( '_add' )->with( $key, $value ); + $class->expects( $this->once() )->method( 'validate_item' )->willReturn( true ); + $class->expects( $this->once() )->method( 'init_object' ); + + $this->assertSame( $class, $class->add( $key, $value ) ); + } + + /** + * @covers \Underpin\Abstracts\Registries\Object_Registry::init_object + * + * @dataProvider provider_can_init_object + */ + public function test_can_init_object( bool $with_middleware, bool $feature_extension ) { + $class = Mockery::mock( Object_Registry::class ); + + $mocked_interfaces = []; + if ( $with_middleware ) $mocked_interfaces[] = With_Middleware::class; + if ( $feature_extension ) $mocked_interfaces[] = Feature_Extension::class; + + $instance = Mockery::mock( ...$mocked_interfaces )->makePartial(); + + $class->expects( 'get' )->with( 'foo' )->andReturn( $instance ); + + $instance->shouldReceive( 'do_middleware_actions' )->times( (int) $with_middleware ); + $instance->shouldReceive( 'do_actions' )->times( (int) $feature_extension ); + + Helpers::call_inaccessible_method( $class, 'init_object', 'foo' ); + } + + /* @see test_can_init_object */ + public function provider_can_init_object(): Generator { + yield 'with middleware trait' => [ true, false ]; + yield 'basic class' => [ false, false ]; + yield 'with feature extension' => [ false, true ]; + yield 'with trait and extension' => [ true, true ]; + } + + /** + * @covers \Underpin\Abstracts\Registries\Object_Registry::get_class + */ + public function test_get_class() { + $this->assertEquals( + Object_Helper::make_class( Underpin::class ), + Helpers::call_inaccessible_method( $this->createMock( Object_Registry::class ), 'get_class', Underpin::class ) + ); + } + + + /** + * @dataProvider provider_can_validate_item + */ + public function test_can_validate_item( bool $valid, string $key, mixed $value ) { + $instance = $this->getMockForAbstractClass( Object_Registry::class ); + Helpers::set_protected_property( $instance, 'storage', [ 'foo' => 'bar' ] ); + Helpers::set_protected_property( $instance, 'abstraction_class', Registry::class ); + + if ( ! $valid ) { + $this->expectException( Invalid_Registry_Item::class ); + } + + $result = Helpers::call_inaccessible_method( $instance, 'validate_item', $key, $value ); + + if ( $valid ) { + $this->assertSame( true, $result ); + } + } + + /* @see test_can_validate_item */ + public function provider_can_validate_item(): Generator { + yield 'validation fails when item key is already set' => [ false, 'foo', Registry::class ]; + yield 'validation passes when item is instance of abstraction class' => [ true, 'bar', new \Underpin\Factories\Registry( fn () => false ) ]; + yield 'validation passes when item is subclass of abstraction class' => [ true, 'bar', Object_Registry::class ]; + yield 'validation passes when item is the same as the abstraction class' => [ true, 'bar', Registry::class ]; + yield 'item fails when item is not an instance of abstraction class' => [ false, 'bar', Log_Item::class ]; + yield 'item fails when item is null' => [ false, 'bar', null ]; + } + +} \ No newline at end of file diff --git a/tests/Unit/Abstracts/Registries/Registry_Test.php b/tests/Unit/Abstracts/Registries/Registry_Test.php new file mode 100644 index 0000000..06530fa --- /dev/null +++ b/tests/Unit/Abstracts/Registries/Registry_Test.php @@ -0,0 +1,153 @@ +getMockForAbstractClass( Registry::class ); + + Helpers::call_inaccessible_method( $class, '_add', 'foo', 'bar' ); + + $this->assertSame( [ 'foo' => 'bar' ], $class->to_array() ); + } + + /** + * Covers \Underpin\Factories\Registry::add + * + * @dataProvider provider_can_add + * + * @param $valid + * + * @return void + * @throws Invalid_Registry_Item + */ + public function test_can_add( $valid ): void { + $mock = $this->getMockForAbstractClass( + originalClassName : Registry::class, + callOriginalConstructor: false, + mockedMethods : [ 'validate_item', '_add' ] + ); + + $assertion = $mock->expects( $this->once() )->method( 'validate_item' ); + + if ( $valid instanceof \Throwable ) { + $assertion->willThrowException( $valid ); + $this->expectException( $valid::class ); + } else { + $assertion->willReturn( $valid ); + } + + $mock->expects( $this->exactly( true === $valid ? 1 : 0 ) )->method( '_add' )->with( 'key', 'value' ); + + $result = $mock->add( 'key', 'value' ); + $this->assertSame( $mock, $result ); + } + + /* @see test_can_add */ + public function provider_can_add(): Generator { + yield 'Stores data in the array when valid' => [ true ]; + yield 'Does not store data when invalid' => [ false ]; + yield 'Does not store data when exception is thrown' => [ new Invalid_Registry_Item( 'foo', 9001, 'error', null, null ) ]; + } + + /** + * Covers \Underpin\Factories\Registry::get + * + * @param bool $exists + * + * @return void + * @dataProvider provider_can_get + */ + public function test_get_throws_exceptions_when_item_cannot_be_found( bool $exists ): void { + $mock = $this->getMockForAbstractClass( originalClassName: Registry::class, mockedMethods: [ 'is_registered' ] ); + + $mock->expects( $this->once() )->method( 'is_registered' )->willReturn( $exists ); + + Helpers::set_protected_property( $mock, 'storage', [ 'foo' => 'bar' ] ); + + if ( ! $exists ) { + $this->expectException( Operation_Failed::class ); + } + + $mock->get( 'foo' ); + } + + public function provider_can_get(): Generator { + yield 'item is set' => [ true ]; + yield 'item is not set' => [ false ]; + } + + /** + * @throws Operation_Failed + */ + public function test_can_get() { + $mock = $this->getMockForAbstractClass( originalClassName: Registry::class, mockedMethods: [ 'is_registered' ] ); + $mock->expects( $this->once() )->method( 'is_registered' )->willReturn( true ); + Helpers::set_protected_property( $mock, 'storage', [ 'foo' => 'bar', 'bar' => 'baz' ] ); + + $this->assertSame( $mock->get( 'foo' ), 'bar' ); + } + + /** + * Covers \Underpin\Factories\Registry::get + * + * @dataProvider provider_can_add + * + * @return void + */ + public function test_get_throws_exceptions_when_key_is_not_set(): void { + $mock = $this->getMockForAbstractClass( originalClassName: Registry::class ); + + Helpers::set_protected_property( $mock, 'storage', [ 'foo' => 'bar' ] ); + + $this->expectException( Operation_Failed::class ); + + $mock->get( 'baz' ); + } + + /** + * @param array $registry_items + * @param bool $expected + * + * @return void + * @dataProvider provider_is_registered + */ + public function test_is_registered( array $registry_items, bool $expected ): void { + $mock = $this->getMockForAbstractClass( Registry::class ); + + Helpers::set_protected_property( $mock, 'storage', $registry_items ); + + $this->assertSame( $expected, Helpers::call_inaccessible_method( $mock, 'is_registered', 'foo' ) ); + } + + /* @see test_is_registered */ + public function provider_is_registered(): Generator { + yield 'registered' => [ [ 'foo' => 'bar', 'bar' => 'baz' ], true ]; + yield 'not registered' => [ [ 'boo' => 'bar', 'bar' => 'foo' ], false ]; + } + + /** + * @covers \Underpin\WordPress\Abstracts\Registry::query + */ + public function test_can_query() { + $mock = $this->getMockForAbstractClass( Registry::class ); + Helpers::set_protected_property( $mock, 'storage', [ 'foo' => 'bar' ] ); + + $this->assertEquals( new List_Filter( [ 'foo' => 'bar' ] ), $mock->query() ); + } + +} \ No newline at end of file diff --git a/tests/Unit/Enums/Test_Filter.php b/tests/Unit/Enums/Test_Filter.php new file mode 100644 index 0000000..ee8a3e7 --- /dev/null +++ b/tests/Unit/Enums/Test_Filter.php @@ -0,0 +1,54 @@ +assertSame( $enum->field( 'foo' ), $expected ); + } + + public function provider_can_get_field(): Generator { + yield 'not_in' => [ Filter::not_in, 'foo__not_in' ]; + yield 'in' => [ Filter::in, 'foo__in' ]; + yield 'and' => [ Filter::and, 'foo__and' ]; + yield 'equals' => [ Filter::equals, 'foo__equals' ]; + } + + /** + * @dataProvider provider_can_get_key + * @covers Filter::key + * + * @param Filter $enum + * @param $expected + * + * @return void + */ + public function test_can_get_key( Filter $enum, $expected ): void { + $this->assertSame( $enum->key(), $expected ); + } + + public function provider_can_get_key(): Generator { + yield 'not_in' => [ Filter::not_in, 'filter_enum_key__not_in' ]; + yield 'in' => [ Filter::in, 'filter_enum_key__in' ]; + yield 'and' => [ Filter::and, 'filter_enum_key__and' ]; + yield 'equals' => [ Filter::equals, 'filter_enum_key__equals' ]; + } + +} \ No newline at end of file diff --git a/tests/Unit/Factories/Data_Providers/Accumulator_Test.php b/tests/Unit/Factories/Data_Providers/Accumulator_Test.php new file mode 100644 index 0000000..e8c0d40 --- /dev/null +++ b/tests/Unit/Factories/Data_Providers/Accumulator_Test.php @@ -0,0 +1,62 @@ + [ 'state', [ 'foo' ] ]; + } + + protected function get_instance(): object { + return new Accumulator(); + } + + /** + * @throws Invalid_Callback + */ + public function test_can_reset() { + $instance = new Accumulator( default: [ 'foo' ] ); + + $instance->reset(); + + $this->assertSame( [ 'foo' ], Helpers::get_protected_property( $instance, 'state' )->getValue( $instance ) ); + } + + /** + * @throws Invalid_Callback + */ + public function test_can_construct() { + $instance = new Accumulator( default: [ 'test-default' ] ); + + $default = Helpers::get_protected_property( $instance, 'default' )->getValue( $instance ); + + $this->assertSame( $default, [ 'test-default' ] ); + } + + /** + * @dataProvider provider_can_set_valid_callback + * @throws Invalid_Callback + */ + public function test_can_set_valid_callback( $callback, $expected ) { + $instance = new Accumulator( valid_callback: $callback ); + $this->assertEquals( $expected, Helpers::get_protected_property( $instance, 'valid_callback' )->getValue( $instance ) ); + } + + /* @see test_can_set_valid_callback */ + public function provider_can_set_valid_callback(): Generator { + yield 'unset callback' => [ null, fn () => true ]; + yield 'closure' => [ fn () => 'neat', fn () => 'neat' ]; + } + +} \ No newline at end of file diff --git a/tests/Unit/Factories/Data_Providers/Plugin_Builder_Provider_Test.php b/tests/Unit/Factories/Data_Providers/Plugin_Builder_Provider_Test.php new file mode 100644 index 0000000..a24520b --- /dev/null +++ b/tests/Unit/Factories/Data_Providers/Plugin_Builder_Provider_Test.php @@ -0,0 +1,23 @@ + [ 'builder', $this->createMock( Plugin_Builder::class ) ]; + } + + protected function get_instance(): object { + return new Plugin_Builder_Provider( $this->createMock( Plugin_Builder::class ) ); + } + +} \ No newline at end of file diff --git a/tests/Unit/Factories/Data_Providers/Plugin_Provider_Test.php b/tests/Unit/Factories/Data_Providers/Plugin_Provider_Test.php new file mode 100644 index 0000000..bb7f3b5 --- /dev/null +++ b/tests/Unit/Factories/Data_Providers/Plugin_Provider_Test.php @@ -0,0 +1,24 @@ + [ 'plugin', $this->createMock( Base::class ) ]; + } + + protected function get_instance(): object { + return new Plugin_Provider( $this->createMock( Base::class ) ); + } + +} \ No newline at end of file diff --git a/tests/Unit/Factories/Object_Registry_Test.php b/tests/Unit/Factories/Object_Registry_Test.php new file mode 100644 index 0000000..1a432bc --- /dev/null +++ b/tests/Unit/Factories/Object_Registry_Test.php @@ -0,0 +1,33 @@ +assertSame( $expected, $result ); + } + + /* @see test_can_validate_item */ + public function provider_can_validate_item(): Generator { + yield 'non_boolean_callback' => [ fn () => 'invalid', false ]; + yield 'callback_true' => [ fn () => true, true ]; + yield 'callback_false' => [ fn () => false, false ]; + yield 'uses_args' => [ fn ( $key, $value ) => $key === 'foo' && $value === 'bar', true ]; + } + +} \ No newline at end of file diff --git a/tests/Unit/Factories/Observers/Loader_Test.php b/tests/Unit/Factories/Observers/Loader_Test.php new file mode 100644 index 0000000..8cf0772 --- /dev/null +++ b/tests/Unit/Factories/Observers/Loader_Test.php @@ -0,0 +1,27 @@ + 'bar'] ); + $provider = $this->createPartialMock( Plugin_Builder_Provider::class, [ 'get_builder' ] ); + $loaders = $this->createPartialMock( Object_Registry::class, [ 'add' ] ); + + $loaders->expects($this->once())->method('add')->with('test', ['foo' => 'bar']); + + $provider->expects( $this->once() )->method( 'get_builder' )->willReturn( $builder ); + + $loader->update( new class { + + }, $provider ); + } + +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..eeb199c --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,2 @@ +