diff --git a/.github/renovate.json b/.github/renovate.json
new file mode 100644
index 0000000..e69de29
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..de6682b
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,40 @@
+name: CI
+on:
+ push:
+ branches:
+ - master
+ - develop
+ - alpha
+ - beta
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.OBLAKBOT_PAT }}
+ - name: Import GPG key
+ uses: crazy-max/ghaction-import-gpg@v6
+ id: gpg
+ with:
+ gpg_private_key: ${{ secrets.OBLAKBOT_GPG_KEY }}
+ passphrase: ${{ secrets.OBLAKBOT_GPG_PASS }}
+ git_config_global: true
+ git_user_signingkey: true
+ git_commit_gpgsign: true
+ - name: Semantic Release
+ uses: cycjimmy/semantic-release-action@v4
+ with:
+ extra_plugins: |
+ @semantic-release/github
+ @semantic-release/exec
+ env:
+ GIT_AUTHOR_NAME: ${{ steps.gpg.outputs.name}}
+ GIT_AUTHOR_EMAIL: ${{ steps.gpg.outputs.email}}
+ GIT_COMMITTER_NAME: ${{ steps.gpg.outputs.name}}
+ GIT_COMMITTER_EMAIL: ${{ steps.gpg.outputs.email}}
+ GITHUB_TOKEN: ${{ secrets.OBLAKBOT_PAT }}
diff --git a/.releaserc b/.releaserc
index 4f662fb..b113a63 100644
--- a/.releaserc
+++ b/.releaserc
@@ -20,7 +20,7 @@
[
"@semantic-release/exec",
{
- "prepareCmd": "zip -r '/tmp/release.zip' ./src README.md"
+ "prepareCmd": "zip -r '/tmp/release.zip' ./src README.md ./composer.json"
}
],
[
@@ -29,8 +29,8 @@
"assets": [
{
"path": "/tmp/release.zip",
- "name": "xwp-hook-invoker-${nextRelease.version}.zip",
- "label": "xWP Hook Invoker v${nextRelease.version}"
+ "name": "xwp-di-v${nextRelease.version}.zip",
+ "label": "xWP Dependency Injection v${nextRelease.version}"
}
]
}
diff --git a/README.md b/README.md
index 40913f9..6c44ae2 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,120 @@
-
WordPress Hook Dependency Injection
+XWP-DI
+Dependency Injection Container for WordPress
-
-
-[](https://github.com/semantic-release/semantic-release)
+[](https://packagist.org/packages/x-wp/di)
+
+
+[](https://github.com/x-wp/di/actions/workflows/release.yml)
+
+This library allows you to implement [dependency injection design pattern](https://en.wikipedia.org/wiki/Dependency_injection) in your WordPress plugin or theme. It provides a simple and easy-to-use interface to manage dependencies and hook callbacks.
+
+## Key Features
+
+1. Reliable - Powered by [PHP-DI](https://php-di.org/), a mature and feature-rich dependency injection container.
+2. Interoperable - Provides PSR-11 compliant container interface.
+3. Easy to use - Reduces the boilerplate code required to manage dependencies and hook callbacks.
+4. Customizable - Allows various configuration options to customize the container behavior.
+5. Flexible - Enables advanced hook callback mechanisms.
+6. Fast - Dependencies are resolved only when needed, and the container can be compiled for better performance.
+
+## Installation
+
+You can install this package via composer:
+
+```bash
+composer require x-wp/di
+```
+
+> [!TIP]
+> We recommend using the `automattic/jetpack-autoloader` with this package to prevent autoloading issues.
+
+## Usage
+
+Below is a simple example to demonstrate how to use this library in your plugin or theme.
+
+### Creating the Application and Container
+
+You will need a class which will be used as the entry point for your plugin/theme. This class must have a `#[Module]` attribute to define the container configuration.
+
+```php
+
+ */
+ public static function configure(): array {
+ return array(
+ 'my.def' => \DI\value('my value'),
+ );
+ }
+}
+```
+
+After defining the module, you can create the application using the `xwp_create_app` function.
+
+```php
+ 'my-plugin',
+ 'module' => My_Plugin::class,
+ 'compile' => false,
+ );
+);
+
+```
+
+### Using handlers and callbacks
+
+Handler is any class which is annotated with a `#[Handler]` attribute. Class methods can be annotated with `#[Action]` or `#[Filter]` attributes to define hook callbacks.
+
+```php
+=8.0",
"automattic/jetpack-constants": "^2",
"php-di/php-di": "^7",
+ "symfony/polyfill-php81": "^1.31",
"x-wp/helper-classes": "^1.13",
"x-wp/helper-functions": "^1.13"
},
@@ -36,8 +37,15 @@
"swissspidy/phpstan-no-private": "^0.2",
"szepeviktor/phpstan-wordpress": "^1.3"
},
+ "conflict": {
+ "oblak/wp-hook-di": "*"
+ },
"provide": {
- "x-wp/di-implementation": "self.version"
+ "psr/container-implementation": "^1.0",
+ "x-wp/di-implementation": "^1.0"
+ },
+ "replace": {
+ "x-wp/hook-invoker": "*"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@@ -52,14 +60,14 @@
]
},
"config": {
- "platform": {
- "php": "8.0"
- },
"allow-plugins": {
"automattic/jetpack-autoloader": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
},
+ "platform": {
+ "php": "8.0"
+ },
"sort-packages": true
}
}
diff --git a/composer.lock b/composer.lock
index 275f525..04aecd0 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4615a7dffb243c8eb4bd73cc999cc69f",
+ "content-hash": "3879411c4b4bea5e32cc13d31fdcb283",
"packages": [
{
"name": "automattic/jetpack-constants",
@@ -299,9 +299,85 @@
},
"time": "2021-11-05T16:47:00+00:00"
},
+ {
+ "name": "symfony/polyfill-php81",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php81\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "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 backporting some PHP 8.1+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.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": "2024-09-09T11:45:10+00:00"
+ },
{
"name": "x-wp/helper-classes",
- "version": "v1.13.4",
+ "version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/x-wp/helper-classes.git",
@@ -351,22 +427,22 @@
],
"support": {
"issues": "https://github.com/x-wp/helper-classes/issues",
- "source": "https://github.com/x-wp/helper-classes/tree/v1.13.4"
+ "source": "https://github.com/x-wp/helper-classes/tree/v1.18.0"
},
"time": "2024-09-23T14:31:15+00:00"
},
{
"name": "x-wp/helper-functions",
- "version": "v1.13.4",
+ "version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/x-wp/helper-functions.git",
- "reference": "93f6c928cd08192298e572a49873e3bd6b7aad49"
+ "reference": "1e3392e49d0fe95eb13e8980081b9ceb0268e0bd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/x-wp/helper-functions/zipball/93f6c928cd08192298e572a49873e3bd6b7aad49",
- "reference": "93f6c928cd08192298e572a49873e3bd6b7aad49",
+ "url": "https://api.github.com/repos/x-wp/helper-functions/zipball/1e3392e49d0fe95eb13e8980081b9ceb0268e0bd",
+ "reference": "1e3392e49d0fe95eb13e8980081b9ceb0268e0bd",
"shasum": ""
},
"require": {
@@ -378,8 +454,10 @@
"type": "library",
"autoload": {
"files": [
- "xwp-helper-fns.php",
- "xwp-helper-fns-req.php"
+ "xwp-helper-fns-arr.php",
+ "xwp-helper-fns-num.php",
+ "xwp-helper-fns-req.php",
+ "xwp-helper-fns.php"
],
"psr-4": {
"XWP\\Helper\\Functions\\": "."
@@ -407,13 +485,13 @@
],
"support": {
"issues": "https://github.com/x-wp/helper-functions/issues",
- "source": "https://github.com/x-wp/helper-functions/tree/v1.13.4"
+ "source": "https://github.com/x-wp/helper-functions/tree/v1.18.0"
},
- "time": "2024-09-23T14:26:03+00:00"
+ "time": "2024-10-29T22:53:16+00:00"
},
{
"name": "x-wp/helper-traits",
- "version": "v1.13.4",
+ "version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/x-wp/helper-traits.git",
@@ -459,7 +537,7 @@
],
"support": {
"issues": "https://github.com/x-wp/helper-traits/issues",
- "source": "https://github.com/x-wp/helper-traits/tree/v1.13.4"
+ "source": "https://github.com/x-wp/helper-traits/tree/v1.18.0"
},
"time": "2024-09-18T12:43:44+00:00"
}
@@ -1020,16 +1098,16 @@
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.32.0",
+ "version": "1.33.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4"
+ "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4",
- "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140",
+ "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140",
"shasum": ""
},
"require": {
@@ -1061,22 +1139,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0"
},
- "time": "2024-09-26T07:23:32+00:00"
+ "time": "2024-10-13T11:25:22+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.12.5",
+ "version": "1.12.7",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17"
+ "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17",
- "reference": "7e6c6cb7cecb0a6254009a1a8a7d54ec99812b17",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0",
+ "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0",
"shasum": ""
},
"require": {
@@ -1121,7 +1199,7 @@
"type": "github"
}
],
- "time": "2024-09-26T12:45:22+00:00"
+ "time": "2024-10-18T11:12:07+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
diff --git a/src/Builder.php b/src/App_Builder.php
similarity index 65%
rename from src/Builder.php
rename to src/App_Builder.php
index fbd9d8d..fe160dc 100644
--- a/src/Builder.php
+++ b/src/App_Builder.php
@@ -17,17 +17,15 @@
*
* @extends \DI\ContainerBuilder
*/
-class Builder extends \DI\ContainerBuilder {
+class App_Builder extends \DI\ContainerBuilder {
/**
* Static method to configure the container.
*
* @param array $config Configuration options.
- * @return Builder
+ * @return App_Builder
*/
- public static function configure( array $config = array() ): Builder {
- $config = static::getDefaultConfig( $config );
-
- return ( new Builder() )
+ public static function configure( array $config = array() ): App_Builder {
+ return ( new App_Builder() )
->useAttributes( $config['attributes'] )
->useAutowiring( $config['autowiring'] )
->writeProxiesToFile( writeToFile: $config['proxies'], proxyDirectory: $config['compile_dir'] )
@@ -38,26 +36,6 @@ public static function configure( array $config = array() ): Builder {
);
}
- /**
- * Get the default configuration.
- *
- * @param array $config Configuration options.
- * @return array
- */
- protected static function getDefaultConfig( array $config ): array {
- return \wp_parse_args(
- $config,
- array(
- 'attributes' => true,
- 'autowiring' => true,
- 'compile' => 'production' === \wp_get_environment_type(),
- 'compile_class' => 'CompiledContainer' . \strtoupper( $config['id'] ),
- 'compile_dir' => __DIR__ . '/cache',
- 'proxies' => false,
- ),
- );
- }
-
//phpcs:ignore Squiz.Commenting.FunctionComment.Missing
public function enableCompilation(
string $directory,
@@ -65,10 +43,16 @@ public function enableCompilation(
string $containerParentClass = CompiledContainer::class,
bool $compile = true,
): static {
+ if ( ! $compile ) {
+ return $this;
+ }
+
+ if ( ! \is_dir( $directory ) && ! \wp_mkdir_p( $directory ) ) {
+ return $this;
+ }
+
// @phpstan-ignore return.type
- return $compile
- ? parent::enableCompilation( $directory, $containerClass, $containerParentClass )
- : $this;
+ return parent::enableCompilation( $directory, $containerClass, $containerParentClass );
}
/**
@@ -80,7 +64,7 @@ public function enableCompilation(
* @return $this
*/
public function addDefinitions( string|array|DefinitionSource ...$definitions ): static {
- return \class_exists( $definitions[0] )
+ return \is_string( $definitions[0] ) && \class_exists( $definitions[0] )
? parent::addDefinitions( \xwp_register_module( $definitions[0] )->get_definitions() )
: parent::addDefinitions( ...$definitions );
}
diff --git a/src/App_Factory.php b/src/App_Factory.php
index 8bc39a1..34e13ef 100644
--- a/src/App_Factory.php
+++ b/src/App_Factory.php
@@ -14,8 +14,10 @@
/**
* Create and manage DI containers.
*
- * @method static Container create(array $config) Create a new container.
- * @method static Container get(string $container_id) Get a container instance.
+ * @method static Container create( array $config) Create a new container.
+ * @method static Container get( string $id ) Get a container instance.
+ * @method static void extend( string $container, array $module, string $position, ?string $target ) Extend an application container definition.
+ * @method static bool decompile( string $id, bool $now ) Decompile a container.
*/
final class App_Factory {
use Singleton;
@@ -51,11 +53,41 @@ protected function call_create( array $config ): Container {
return $this->containers[ $config['id'] ];
}
- return $this->containers[ $config['id'] ] ??= Builder::configure( $config )
+ $config = $this->parse_config( $config );
+
+ return $this->containers[ $config['id'] ] ??= App_Builder::configure( $config )
->addDefinitions( $config['module'] )
+ ->addDefinitions( array( 'xwp.app.config' => $config ) )
->build();
}
+ /**
+ * Extend an application container definition.
+ *
+ * @param string $container Container ID.
+ * @param array $module Module classname or array of module classnames.
+ * @param 'before'|'after' $position Position to insert the module.
+ * @param string|null $target Target module to extend.
+ */
+ protected function call_extend( string $container, array $module, string $position = 'after', ?string $target = null ): void {
+ \add_filter(
+ "xwp_extend_import_{$container}",
+ static function ( array $imports, string $classname ) use( $module, $position, $target ): array {
+ if ( $target && $target !== $classname ) {
+ return $imports;
+ }
+
+ $params = 'after' === $position
+ ? array( $imports, $module )
+ : array( $module, $imports );
+
+ return \array_merge( ...$params );
+ },
+ 10,
+ 2,
+ );
+ }
+
/**
* Get a container instance.
*
@@ -65,4 +97,44 @@ protected function call_create( array $config ): Container {
protected function call_get( string $id ): Container {
return $this->containers[ $id ];
}
+
+ /**
+ * Decompile a container.
+ *
+ * @param string $id Container ID.
+ * @param bool $now Decompile now or on shutdown.
+ * @return bool
+ */
+ protected function call_decompile( string $id, bool $now = false ): bool {
+ $config = $this->containers[ $id ]->get( 'xwp.app.config' );
+
+ if ( ! $config['compile'] || ! \xwp_wpfs()->is_dir( $config['compile_dir'] ) ) {
+ return false;
+ }
+
+ $cb = static fn() => \xwp_wpfs()->rmdir( $config['compile_dir'], true );
+
+ // @phpstan-ignore return.void
+ return ! $now ? \add_action( 'shutdown', $cb ) : $cb();
+ }
+
+ /**
+ * Get the default configuration.
+ *
+ * @param array $config Configuration options.
+ * @return array
+ */
+ protected function parse_config( array $config ): array {
+ return \wp_parse_args(
+ $config,
+ array(
+ 'attributes' => true,
+ 'autowiring' => true,
+ 'compile' => 'production' === \wp_get_environment_type(),
+ 'compile_class' => 'CompiledContainer' . \strtoupper( $config['id'] ),
+ 'compile_dir' => \WP_CONTENT_DIR . '/cache/xwp-di/' . $config['id'],
+ 'proxies' => false,
+ ),
+ );
+ }
}
diff --git a/src/Core/REST_Controller.php b/src/Core/REST_Controller.php
new file mode 100644
index 0000000..cad1955
--- /dev/null
+++ b/src/Core/REST_Controller.php
@@ -0,0 +1,57 @@
+namespace = $namespace;
+
+ return $this;
+ }
+
+ /**
+ * Set the basename.
+ *
+ * @param string $base Basename.
+ * @return static
+ */
+ public function with_basename( string $base ): static {
+ $this->rest_base = $base;
+
+ return $this;
+ }
+
+ /**
+ * Register routes for this controller.
+ *
+ * @return void
+ */
+ public function register_routes() {
+ do_action( $this->namespace . '/' . $this->rest_base );
+ }
+}
diff --git a/src/Decorators/Action.php b/src/Decorators/Action.php
index 65a0b2a..abb325c 100644
--- a/src/Decorators/Action.php
+++ b/src/Decorators/Action.php
@@ -8,11 +8,14 @@
namespace XWP\DI\Decorators;
+use XWP\DI\Interfaces\Can_Handle;
+
/**
* Action hook decorator.
*
* @template T of object
- * @extends Filter
+ * @template H of Can_Handle
+ * @extends Filter
*
* @since 1.0.0
*/
diff --git a/src/Decorators/Ajax_Action.php b/src/Decorators/Ajax_Action.php
new file mode 100644
index 0000000..2265d08
--- /dev/null
+++ b/src/Decorators/Ajax_Action.php
@@ -0,0 +1,230 @@
+
+ * @extends Action
+ */
+#[\Attribute( \Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD )]
+class Ajax_Action extends Action {
+ public const AJAX_GET = 'GET';
+
+ public const AJAX_POST = 'POST';
+
+ public const AJAX_REQ = 'REQ';
+
+ /**
+ * Action name
+ *
+ * @var string
+ */
+ protected string $action;
+
+ /**
+ * Prefix for the action name.
+ *
+ * @var string
+ */
+ protected string $prefix;
+
+ /**
+ * Ajax method.
+ *
+ * @var 'GET'|'POST'|'REQ'
+ */
+ protected string $method;
+
+ /**
+ * Nonce query var.
+ *
+ * @var bool|string
+ */
+ protected bool|string $nonce;
+
+ /**
+ * Capability required to perform the action.
+ *
+ * @var string
+ */
+ protected ?string $cap;
+
+ /**
+ * Variables to fetch.
+ *
+ * @var array
+ */
+ protected array $vars;
+
+ /**
+ * Ajax hooks.
+ *
+ * Can contain private/public ajax hook, or both.
+ *
+ * @var array
+ */
+ protected array $hooks;
+
+ /**
+ * Variable getter function
+ *
+ * @var Closure(string, string): string
+ */
+ protected Closure $getter;
+
+ /**
+ * Constructor.
+ *
+ * @param string $action Ajax action name.
+ * @param string $prefix Prefix for the action name.
+ * @param bool $public Whether the action is public or not.
+ * @param 'GET'|'POST'|'REQ' $method Method to fetch the variable. GET, POST, or REQ.
+ * @param bool|string $nonce String defines the query var for nonce, true checks the default vars, false disables nonce check.
+ * @param string $cap Capability required to perform the action.
+ * @param array $vars Variables to fetch.
+ * @param array $params Parameters to pass to the callback. Will be resolved by the container.
+ * @param int $priority Hook priority.
+ */
+ public function __construct(
+ string $action,
+ string $prefix,
+ bool $public = true,
+ string $method = self::AJAX_REQ,
+ bool|string $nonce = false,
+ ?string $cap = null,
+ array $vars = array(),
+ array $params = array(),
+ int $priority = 10,
+ ) {
+ $this->action = $action;
+ $this->prefix = $prefix;
+ $this->method = $method;
+ $this->nonce = $nonce;
+ $this->cap = $cap;
+ $this->vars = $vars;
+ $this->hooks = $public ? array( 'wp_ajax_nopriv', 'wp_ajax' ) : array( 'wp_ajax' );
+ $this->getter = Closure::fromCallable( $this->get_fetch_cb( $method ) );
+
+ parent::__construct(
+ tag: '%s_%s_%s',
+ priority:$priority,
+ context: self::CTX_AJAX,
+ conditional: '__return_true',
+ modifiers: array( '%s', \rtrim( $prefix, '_' ), $action ),
+ invoke: self::INV_PROXIED,
+ args: 0,
+ params: $params,
+ );
+ }
+
+ /**
+ * Check if the action can be loaded.
+ *
+ * @return bool
+ */
+ public function can_load(): bool {
+ return parent::can_load() && $this->handler->loaded;
+ }
+
+ /**
+ * Get the variable fetch callback.
+ *
+ * @param 'GET'|'POST'|'REQ' $method Method to fetch the variable.
+ * @return Closure(string, mixed): mixed
+ */
+ protected function get_fetch_cb( string $method ): Closure {
+ $cb = match ( $method ) {
+ 'GET' => 'xwp_fetch_get_var',
+ 'POST' => 'xwp_fetch_post_var',
+ 'REQ' => 'xwp_fetch_req_var',
+ };
+
+ return Closure::fromCallable( $cb );
+ }
+
+ /**
+ * Loads the hook.
+ *
+ * @param ?string $tag Optional hook tag.
+ * @return bool
+ */
+ protected function load_hook( ?string $tag = null ): bool {
+ foreach ( $this->hooks as $hook ) {
+ parent::load_hook( $this->define_tag( $this->tag, array( $hook ) ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Fire the hook.
+ *
+ * @param mixed ...$args Arguments to pass to the callback.
+ * @return mixed
+ */
+ protected function fire_hook( mixed ...$args ): mixed {
+ if ( $this->nonce && ! $this->nonce_check() ) {
+ $this->fire_guard_cb( 'nonce' );
+ }
+
+ if ( $this->cap && ! $this->cap_check() ) {
+ $this->fire_guard_cb( 'cap' );
+ }
+
+ return parent::fire_hook( ...$args );
+ }
+
+ /**
+ * Get the arguments to pass to the callback.
+ *
+ * @param array $args Existing arguments.
+ * @return array
+ */
+ protected function get_cb_args( array $args ): array {
+ if ( isset( $this->vars['body'] ) ) {
+ $args[] = \json_decode( \file_get_contents( 'php://input' ), true ) ?? array();
+ }
+
+ foreach ( \xwp_array_diff_assoc( $this->vars, 'body' ) as $k => $d ) {
+ $args[] = ( $this->getter )( $k, $d );
+ }
+
+ return parent::get_cb_args( $args );
+ }
+
+ private function fire_guard_cb( string $type ): void {
+ $methods = array( "{$this->action}_guard", 'unverified_call', 'invalid_call' );
+
+ foreach ( $methods as $method ) {
+ if ( ! \method_exists( $this->handler->classname, $method ) ) {
+ continue;
+ }
+
+ $this->container->call( array( $this->handler->classname, $method ), array( $type ) );
+ return;
+ }
+
+ \wp_die( \esc_html( $type ) );
+ }
+
+ private function nonce_check(): bool {
+ $query_arg = \is_string( $this->nonce ) ? $this->nonce : false;
+
+ return \check_ajax_referer( "{$this->prefix}_{$this->action}", $query_arg, false );
+ }
+
+ private function cap_check(): bool {
+ return \current_user_can( $this->cap );
+ }
+}
diff --git a/src/Decorators/Ajax_Handler.php b/src/Decorators/Ajax_Handler.php
new file mode 100644
index 0000000..a0dc415
--- /dev/null
+++ b/src/Decorators/Ajax_Handler.php
@@ -0,0 +1,41 @@
+
+ */
+#[\Attribute( \Attribute::TARGET_CLASS )]
+class Ajax_Handler extends Handler {
+ /**
+ * Constructor
+ *
+ * @param string $container Container ID.
+ * @param int $priority Handler priority.
+ * @param null|Closure|string|array{class-string,string} $conditional Conditional callback.
+ */
+ public function __construct(
+ string $container,
+ int $priority = 10,
+ array|string|Closure|null $conditional = null,
+ ) {
+ parent::__construct(
+ tag: 'admin_init',
+ priority: $priority,
+ container: $container,
+ context: self::CTX_AJAX,
+ conditional: $conditional,
+ );
+ }
+}
diff --git a/src/Decorators/Filter.php b/src/Decorators/Filter.php
index fee8ad5..cd825db 100644
--- a/src/Decorators/Filter.php
+++ b/src/Decorators/Filter.php
@@ -19,15 +19,16 @@
* Filter hook decorator.
*
* @template T of object
+ * @template H of Can_Handle
* @extends Hook
- * @implements Can_Invoke
+ * @implements Can_Invoke
*/
#[\Attribute( \Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD )]
class Filter extends Hook implements Can_Invoke {
/**
* The handler.
*
- * @var Can_Handle
+ * @var H
*/
protected Can_Handle $handler;
@@ -99,7 +100,7 @@ public function with_reflector( Reflector $r ): static {
/**
* Set the handler.
*
- * @param Can_Handle $handler The handler.
+ * @param H $handler The handler.
* @return static
*/
public function with_handler( Can_Handle $handler ): static {
@@ -160,19 +161,30 @@ public function load(): bool {
return false;
}
- $this->loaded = ( "add_{$this->get_type()}" )(
- $this->tag,
+ $this->loaded = $this->load_hook();
+
+ return $this->loaded;
+ }
+
+ /**
+ * Loads the hook.
+ *
+ * @param ?string $tag Optional hook tag.
+ * @return bool
+ */
+ protected function load_hook( ?string $tag = null ): bool {
+ return ( "add_{$this->get_type()}" )(
+ $tag ?? $this->tag,
$this->target,
$this->priority,
$this->args,
);
-
- return $this->loaded;
}
public function invoke( mixed ...$args ): mixed {
if (
! $this->init_handler( $this->handler::INIT_JUST_IN_TIME ) ||
+ ! parent::can_load() ||
( $this->cb_valid( self::INV_ONCE ) && $this->fired ) ||
( $this->cb_valid( self::INV_LOOPED ) && $this->firing )
) {
@@ -198,15 +210,28 @@ public function invoke( mixed ...$args ): mixed {
protected function fire_hook( mixed ...$args ): mixed {
$this->firing = true;
+ return $this->container->call(
+ array( $this->handler->classname, $this->method ),
+ $this->get_cb_args( $args ),
+ );
+ }
+
+ /**
+ * Get the arguments to pass to the callback.
+ *
+ * @param array $args Existing arguments.
+ * @return array
+ */
+ protected function get_cb_args( array $args ): array {
if ( $this->params ) {
foreach ( $this->params as $param ) {
$args[] = $this->container->get( $param );
}
- }
- $args[] = $this;
+ $args[] = $this;
+ }
- return $this->container->call( array( $this->handler->classname, $this->method ), $args );
+ return $args;
}
/**
diff --git a/src/Decorators/Handler.php b/src/Decorators/Handler.php
index ea794ed..752205d 100644
--- a/src/Decorators/Handler.php
+++ b/src/Decorators/Handler.php
@@ -54,6 +54,13 @@ class Handler extends Hook implements Can_Handle {
*/
protected string $container_id;
+ /**
+ * Is the handler hookable.
+ *
+ * @var bool
+ */
+ protected bool $hookable;
+
/**
* Constructor.
*
@@ -64,6 +71,7 @@ class Handler extends Hook implements Can_Handle {
* @param null|Closure|string|array{class-string,string} $conditional Conditional callback.
* @param array|string|false $modifiers Values to replace in the tag name.
* @param string $strategy Initialization strategy.
+ * @param bool $hookable Is the handler hookable.
*/
public function __construct(
?string $tag = null,
@@ -73,10 +81,12 @@ public function __construct(
array|string|Closure|null $conditional = null,
string|array|false $modifiers = false,
string $strategy = self::INIT_DEFFERED,
+ bool $hookable = true,
) {
$this->strategy = $strategy;
$this->loaded = self::INIT_DYNAMICALY === $strategy;
$this->container_id = $container;
+ $this->hookable = $hookable;
parent::__construct( $tag, $tag ? $priority : null, $context, $conditional, $modifiers );
}
@@ -94,7 +104,9 @@ public function with_classname( string $classname ): static {
}
public function with_container( ?string $container ): static {
- $this->container_id ??= $container;
+ if ( null !== $container ) {
+ $this->container_id ??= $container;
+ }
return $this;
}
@@ -110,6 +122,10 @@ public function with_target( object $instance ): static {
$this->classname ??= $instance::class;
$this->loaded = true;
+ if ( ! $this->container->has( $this->classname ) ) {
+ $this->container->set( $this->classname, $this->instance );
+ }
+
return $this;
}
@@ -127,11 +143,20 @@ public function load(): bool {
return false;
}
- $this->instance ??= $this->container->get( $this->classname );
+ $this->instance ??= $this->initialize();
return $this->on_initialize();
}
+ /**
+ * Initialize the handler.
+ *
+ * @return T
+ */
+ protected function initialize(): object {
+ return $this->container->get( $this->classname );
+ }
+
/**
* Mark the handler as loaded, and call the on_initialize method.
*
@@ -141,7 +166,7 @@ protected function on_initialize(): bool {
$this->loaded = true;
if ( \method_exists( $this->classname, 'on_initialize' ) ) {
- $this->target->on_initialize();
+ $this->container->call( array( $this->classname, 'on_initialize' ) );
}
return $this->loaded;
@@ -157,7 +182,7 @@ public function get_target(): ?object {
}
public function can_load(): bool {
- return parent::can_load() && $this->check_method( array( $this->classname, 'can_loadialize' ) );
+ return parent::can_load() && $this->check_method( array( $this->classname, 'can_initialize' ) );
}
protected function get_id(): string {
@@ -189,4 +214,8 @@ protected function get_lazy_hook(): string {
public function is_lazy(): bool {
return self::INIT_ON_DEMAND === $this->strategy || self::INIT_JUST_IN_TIME === $this->strategy;
}
+
+ public function is_hookable(): bool {
+ return $this->hookable;
+ }
}
diff --git a/src/Decorators/Hook.php b/src/Decorators/Hook.php
index 6b32aad..51ce2d2 100644
--- a/src/Decorators/Hook.php
+++ b/src/Decorators/Hook.php
@@ -12,9 +12,8 @@
use DI\Container;
use ReflectionClass;
use ReflectionMethod;
-use XWP\DI\Hook\Context;
+use XWP\DI\Hook_Context;
use XWP\DI\Interfaces\Can_Hook;
-use XWP\DI\Traits\Hookable;
/**
* Base hook from which the action and filter decorators inherit.
@@ -112,7 +111,7 @@ public function can_load(): bool {
* @return bool
*/
public function check_context(): bool {
- return Context::is_valid_context( $this->context );
+ return Hook_Context::validate( $this->context );
}
/**
@@ -122,7 +121,7 @@ public function check_context(): bool {
* @return bool
*/
protected function check_method( array|string|\Closure|null $method ): bool {
- return ! \is_callable( $method ) || $method( $this );
+ return ! \is_callable( $method ) || $this->container->call( $method );
}
/**
diff --git a/src/Decorators/Module.php b/src/Decorators/Module.php
index 7d72f2d..d7d45b0 100644
--- a/src/Decorators/Module.php
+++ b/src/Decorators/Module.php
@@ -28,11 +28,12 @@ class Module extends Handler {
/**
* Constructor.
*
- * @param string $container Container ID.
- * @param string $hook Hook name.
- * @param int $priority Hook priority.
- * @param array $imports Array of submodules to import.
- * @param array $handlers Array of handlers to register.
+ * @param string $container Container ID.
+ * @param string $hook Hook name.
+ * @param int $priority Hook priority.
+ * @param array $imports Array of submodules to import.
+ * @param array $handlers Array of handlers to register.
+ * @param bool $extendable Is the module extendable.
*/
public function __construct(
string $container,
@@ -50,6 +51,12 @@ public function __construct(
* @var array
*/
protected array $handlers = array(),
+ /**
+ * Is the module extendable?
+ *
+ * @var bool
+ */
+ protected bool $extendable = false,
) {
parent::__construct(
tag: $hook,
@@ -82,7 +89,7 @@ protected function on_initialize(): bool {
public function get_definitions(): array {
$definitions = $this->get_definition();
- foreach ( $this->imports as $import ) {
+ foreach ( $this->get_imports() as $import ) {
$module = $this->imported ? \xwp_get_module( $import ) : \xwp_register_module( $import );
$definitions = \array_merge( $definitions, $module->get_definitions() );
@@ -93,6 +100,30 @@ public function get_definitions(): array {
return $definitions;
}
+ /**
+ * Get the module imports.
+ *
+ * @return array
+ */
+ protected function get_imports(): array {
+ if ( ! $this->extendable ) {
+ return $this->imports;
+ }
+
+ $tag = "xwp_extend_import_{$this->container_id}";
+
+ /**
+ * Filter the module imports.
+ *
+ * @param array $imports Array of submodules to import.
+ * @param class-string $classname Module classname.
+ * @return array
+ *
+ * @since 1.0@beta.8
+ */
+ return \apply_filters( $tag, $this->imports, $this->classname );
+ }
+
/**
* Get the module definition.
*
diff --git a/src/Decorators/REST_Handler.php b/src/Decorators/REST_Handler.php
new file mode 100644
index 0000000..6fe3f53
--- /dev/null
+++ b/src/Decorators/REST_Handler.php
@@ -0,0 +1,95 @@
+
+ */
+#[\Attribute( \Attribute::TARGET_CLASS )]
+class REST_Handler extends Handler {
+ /**
+ * Constructor
+ *
+ * @param string $namespace REST namespace.
+ * @param string $basename REST basename.
+ * @param string $container Container ID.
+ * @param int $priority Handler priority.
+ */
+ public function __construct(
+ protected string $namespace,
+ protected string $basename,
+ string $container,
+ int $priority = 10,
+ ) {
+ parent::__construct(
+ tag: 'rest_api_init',
+ priority: $priority,
+ container: $container,
+ context: self::CTX_REST,
+ );
+ }
+
+ /**
+ * Can the handler be loaded?
+ *
+ * Checks if the REST namespace matches the requested route.
+ *
+ * @return bool
+ */
+ public function can_load(): bool {
+ return parent::can_load() && \xwp_can_load_rest_ns( $this->namespace );
+ }
+
+ /**
+ * Initialize the handler.
+ *
+ * Sets the namespace and basename.
+ *
+ * @return object
+ */
+ protected function initialize(): object {
+ return parent::initialize()
+ ->with_namespace( $this->namespace )
+ ->with_basename( $this->basename );
+ }
+
+ /**
+ * Get the REST namespace.
+ *
+ * @return string
+ */
+ protected function get_namespace(): string {
+ return $this->namespace;
+ }
+
+ /**
+ * Get the REST basename.
+ *
+ * @return string
+ */
+ protected function get_basename(): string {
+ return $this->basename;
+ }
+
+ /**
+ * Get the REST hook.
+ *
+ * @return string
+ */
+ public function get_rest_hook(): string {
+ return $this->namespace . '/' . $this->basename;
+ }
+}
diff --git a/src/Decorators/REST_Route.php b/src/Decorators/REST_Route.php
new file mode 100644
index 0000000..599b0fd
--- /dev/null
+++ b/src/Decorators/REST_Route.php
@@ -0,0 +1,181 @@
+
+ * @extends Action
+ */
+#[\Attribute( \Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD )]
+class REST_Route extends Action {
+ /**
+ * REST Route arguments.
+ *
+ * @var string|array
+ */
+ protected array|string $route_args;
+
+ /**
+ * REST Route guard.
+ *
+ * @var string
+ */
+ protected string $route_guard;
+
+ /**
+ * REST Route endpoint.
+ *
+ * @var string
+ */
+ protected string $endpoint;
+
+ /**
+ * REST Route methods.
+ *
+ * @var string
+ */
+ protected string $methods;
+
+ /**
+ * Constructor.
+ *
+ * @param string $route REST route.
+ * @param string $methods HTTP methods.
+ * @param string|array $params Route parameters.
+ * @param string|null $guard Route guard.
+ */
+ public function __construct(
+ string $route,
+ string $methods,
+ string|array $params = array(),
+ ?string $guard = null,
+ ) {
+ parent::__construct(
+ tag: 'rest_api_init',
+ priority: 10,
+ context: self::CTX_REST,
+ invoke:self::INV_PROXIED,
+ );
+
+ $this->endpoint = $route;
+ $this->route_args = $params;
+ $this->methods = $methods;
+ $this->route_guard = $guard ?? '__return_true';
+ }
+
+ /**
+ * Set the handler instance.
+ *
+ * @param H $handler Handler instance.
+ * @return static
+ */
+ public function with_handler( Can_Handle $handler ): static {
+ return parent::with_handler( $handler )
+ ->with_tag( $handler->rest_hook )
+ ->with_priority( $handler->priority + 1 );
+ }
+
+ /**
+ * Set the route priority.
+ *
+ * @param int $priority Priority.
+ * @return static
+ */
+ protected function with_priority( int $priority ): static {
+ $this->prio = $priority;
+
+ return $this;
+ }
+
+ /**
+ * Set the route tag.
+ *
+ * @param string $tag Tag.
+ * @return static
+ */
+ protected function with_tag( string $tag ): static {
+ $this->tag = $tag;
+
+ return $this;
+ }
+
+ /**
+ * Register the REST route.
+ *
+ * @param mixed ...$args Arguments.
+ * @return mixed
+ */
+ public function invoke( mixed ...$args ): mixed {
+ return \register_rest_route(
+ $this->handler->namespace,
+ $this->route,
+ array(
+ 'args' => $this->vars,
+ 'callback' => $this->callback,
+ 'methods' => $this->methods,
+ 'permission_callback' => $this->guard,
+ ),
+ );
+ }
+
+ /**
+ * Get the route.
+ *
+ * @return string
+ */
+ protected function get_route(): string {
+ return $this->endpoint
+ ? "/{$this->handler->basename}/{$this->endpoint}"
+ : "/{$this->handler->basename}";
+ }
+
+ /**
+ * Get the route parameters.
+ *
+ * @return array
+ */
+ protected function get_vars(): array {
+ if ( \is_array( $this->route_args ) ) {
+ return $this->route_args;
+ }
+
+ return $this->handler->target->{$this->route_args}( $this->methods );
+ }
+
+ /**
+ * Get the route callback.
+ *
+ * @return array{0:T, 1: string}
+ */
+ protected function get_callback(): array {
+ return array( $this->handler->target, $this->method );
+ }
+
+ /**
+ * Get the route guard.
+ *
+ * @return string|array{T,string}
+ */
+ protected function get_guard(): string|array {
+ return \method_exists( $this->handler->classname, $this->route_guard )
+ ? array( $this->handler->target, $this->route_guard )
+ : $this->route_guard;
+ }
+}
diff --git a/src/Functions/xwp-di-container-fns.php b/src/Functions/xwp-di-container-fns.php
index d3b6862..60449b8 100644
--- a/src/Functions/xwp-di-container-fns.php
+++ b/src/Functions/xwp-di-container-fns.php
@@ -18,6 +18,33 @@ function xwp_app( string $container_id ): Container {
return \XWP\DI\App_Factory::get( $container_id );
}
+/**
+ * Create a new app container.
+ *
+ * @param array{
+ * id: string,
+ * module: class-string,
+ * attributes?: bool,
+ * autowiring?: bool,
+ * compile?: bool,
+ * compile_class?: string,
+ * compile_dir?: string,
+ * proxies?: bool,
+ * } $app Application configuration.
+ * @param string $hook Hook to create the container on.
+ * @param int $priority Hook priority.
+ * @return true
+ */
+function xwp_load_app( array $app, string $hook = 'plugins_loaded', int $priority = PHP_INT_MIN ): bool {
+ return add_action(
+ $hook,
+ static function () use( $app ): void {
+ xwp_create_app( $app );
+ },
+ $priority,
+ );
+}
+
/**
* Create a new app container.
*
@@ -36,3 +63,30 @@ function xwp_app( string $container_id ): Container {
function xwp_create_app( array $args ): Container {
return \XWP\DI\App_Factory::create( $args );
}
+
+/**
+ * Extend an application container definition.
+ *
+ * @param string $container Container ID.
+ * @param string|array $module Module classname or array of module classnames.
+ * @param 'before'|'after' $position Position to insert the module.
+ * @param string|null $target Target module to extend.
+ */
+function xwp_extend_app( string $container, string|array $module, string $position = 'after', ?string $target = null ): void {
+ if ( ! is_array( $module ) ) {
+ $module = array( $module );
+ }
+
+ \XWP\DI\App_Factory::extend( $container, $module, $position, $target );
+}
+
+/**
+ * Decompile an application container.
+ *
+ * @param string $container_id Container ID.
+ * @param bool $immediately Decompile now or on shutdown.
+ * @return bool
+ */
+function xwp_decompile_app( string $container_id, bool $immediately = false ): bool {
+ return \XWP\DI\App_Factory::decompile( $container_id, $immediately );
+}
diff --git a/src/Hook/Handler_Factory.php b/src/Handler_Factory.php
similarity index 98%
rename from src/Hook/Handler_Factory.php
rename to src/Handler_Factory.php
index 2e2d681..0632878 100644
--- a/src/Hook/Handler_Factory.php
+++ b/src/Handler_Factory.php
@@ -6,7 +6,7 @@
* @subpackage Dependency Injection
*/
-namespace XWP\DI\Hook;
+namespace XWP\DI;
use XWP\DI\Decorators\Handler;
use XWP\DI\Interfaces\Can_Handle;
diff --git a/src/Hook/Context.php b/src/Hook_Context.php
similarity index 96%
rename from src/Hook/Context.php
rename to src/Hook_Context.php
index 0134706..47fdb09 100644
--- a/src/Hook/Context.php
+++ b/src/Hook_Context.php
@@ -6,7 +6,7 @@
* @subpackage Dependency Injection
*/
-namespace XWP\DI\Hook;
+namespace XWP\DI;
use Automattic\Jetpack\Constants;
@@ -15,7 +15,7 @@
*
* @since 1.0.0
*/
-final class Context {
+final class Hook_Context {
/**
* Frontend context.
*/
@@ -81,7 +81,7 @@ public static function get(): int {
* @param int $context The context to check.
* @return bool
*/
- public static function is_valid_context( int $context ): bool {
+ public static function validate( int $context ): bool {
return 0 !== ( self::get() & $context );
}
diff --git a/src/Interfaces/Can_Handle.php b/src/Interfaces/Can_Handle.php
index acc8686..e1893d2 100644
--- a/src/Interfaces/Can_Handle.php
+++ b/src/Interfaces/Can_Handle.php
@@ -71,4 +71,11 @@ public function get_target(): ?object;
* @return bool
*/
public function is_lazy(): bool;
+
+ /**
+ * Is the handler hookable?
+ *
+ * @return bool
+ */
+ public function is_hookable(): bool;
}
diff --git a/src/Interfaces/Can_Invoke.php b/src/Interfaces/Can_Invoke.php
index 4d9c10c..26abb5d 100644
--- a/src/Interfaces/Can_Invoke.php
+++ b/src/Interfaces/Can_Invoke.php
@@ -11,13 +11,15 @@
/**
* Defines decorators that can invoke WordPress hooks.
*
- * @template THndlr of object
- * @extends Can_Hook
+ * @template TInst of object
+ * @template THndl of Can_Handle
+ * @extends Can_Hook
*
* @property-read bool $firing Is the hook firing?
- * @property-read int $fired Number of times the hook has fired.
+ * @property-read int $fired Number of times the hook has fired.
- * @property-read array{THndlr,string} $target The target method.
+ * @property-read array{TInst,string} $target The target method.
+ * @property-read Thndl $handler The handler instance.
*/
interface Can_Invoke extends Can_Hook {
/**
@@ -47,8 +49,7 @@ interface Can_Invoke extends Can_Hook {
/**
* Set the handler instance.
*
- * @template Thndlr of object
- * @param Can_Handle $handler Handler instance.
+ * @param THndl $handler Handler instance.
* @return static
*/
public function with_handler( Can_Handle $handler ): static;
diff --git a/src/Invoker.php b/src/Invoker.php
index 647c3b7..576d31b 100644
--- a/src/Invoker.php
+++ b/src/Invoker.php
@@ -9,7 +9,7 @@
namespace XWP\DI;
use XWP\DI\Decorators\Module;
-use XWP\DI\Hook\Handler_Factory;
+use XWP\DI\Handler_Factory;
use XWP\DI\Interfaces\Can_Handle;
use XWP\DI\Interfaces\Can_Invoke;
use XWP\DI\Utils\Reflection;
@@ -95,7 +95,7 @@ public function has_hooks( string $classname ): bool {
*
* @template T of object
* @param class-string $classname The handler classname.
- * @return array>>
+ * @return array>>>
*/
public function get_hooks( string $classname ): array {
return $this->has_hooks( $classname ) ? $this->hooks[ $classname ] : array();
@@ -226,7 +226,7 @@ protected function init_handler( Can_Handle $handler ): static {
* @return static
*/
protected function register_methods( Can_Handle $handler ): static {
- if ( $this->has_hooks( $handler->classname ) ) {
+ if ( $this->has_hooks( $handler->classname ) || ! $handler->is_hookable() ) {
return $this;
}
@@ -247,18 +247,20 @@ protected function register_methods( Can_Handle $handler ): static {
* Register a method.
*
* @template T of object
- * @param Can_Handle $handler The handler to register the method for.
- * @param \ReflectionMethod $m The method to register.
- * @return array>
+ * @template H of Can_Handle
+ *
+ * @param H $handler The handler to register the method for.
+ * @param \ReflectionMethod $method The method to register.
+ * @return array>
*/
- private function register_method( Can_Handle $handler, \ReflectionMethod $m ) {
+ private function register_method( Can_Handle $handler, \ReflectionMethod $method ) {
$hooks = array();
- foreach ( Reflection::get_decorators( $m, Can_Invoke::class ) as $hook ) {
+ foreach ( Reflection::get_decorators( $method, Can_Invoke::class ) as $hook ) {
$hooks[] = $hook
- ->with_handler( $handler )
- ->with_target( $m->getName() )
- ->with_reflector( $m );
+ ->with_reflector( $method )
+ ->with_target( $method->getName() )
+ ->with_handler( $handler );
}
return $hooks;
@@ -311,6 +313,8 @@ public function invoke_methods( Can_Handle $handler ): static {
}
}
+ \do_action( 'xwp_di_hooks_loaded_' . $handler->classname, $handler );
+
return $this;
}
@@ -318,8 +322,9 @@ public function invoke_methods( Can_Handle $handler ): static {
* Load hooks for a handler.
*
* @template T of object
- * @param Can_Handle $handler The handler to load hooks for.
- * @param array>> $hooks The hooks to load.
+ * @template H of Can_Handle
+ * @param H $handler The handler to load hooks for.
+ * @param array>> $hooks The hooks to load.
* @return static
*/
public function load_hooks( Can_Handle $handler, array $hooks ): static {
diff --git a/src/Traits/Accessible_Hook_Methods.php b/src/Traits/Accessible_Hook_Methods.php
index d095a3d..4811e3d 100644
--- a/src/Traits/Accessible_Hook_Methods.php
+++ b/src/Traits/Accessible_Hook_Methods.php
@@ -52,7 +52,7 @@ public function __call( string $name, array $arguments ) {
* @throws \BadMethodCallException If the method does not exist or is not hooked.
*/
public static function __callStatic( string $name, array $arguments ) {
- if ( 'can_loadialize' === $name ) {
+ if ( 'can_initialize' === $name ) {
return true;
}