diff --git a/includes/abilities-api.php b/includes/abilities-api.php index 3839670..c37cd5a 100644 --- a/includes/abilities-api.php +++ b/includes/abilities-api.php @@ -20,12 +20,12 @@ * * @since 0.1.0 * - * @param string|\WP_Ability $name The name of the ability, or WP_Ability instance. - * The name must be a string containing a namespace prefix, i.e. `my-plugin/my-ability`. It can only - * contain lowercase alphanumeric characters, dashes and the forward slash. - * @param array $properties Optional. An associative array of properties for the ability. This should - * include `label`, `description`, `input_schema`, `output_schema`, - * `execute_callback`, `permission_callback`, and `meta`. + * @param string $name The name of the ability. The name must be a string containing a namespace + * prefix, i.e. `my-plugin/my-ability`. It can only contain lowercase + * alphanumeric characters, dashes and the forward slash. + * @param array $properties An associative array of properties for the ability. This should include + * `label`, `description`, `input_schema`, `output_schema`, `execute_callback`, + * `permission_callback`, `meta`, and `ability_class`. * @return ?\WP_Ability An instance of registered ability on success, null on failure. * * @phpstan-param array{ @@ -36,10 +36,11 @@ * execute_callback?: callable( array $input): (mixed|\WP_Error), * permission_callback?: callable( ?array $input ): bool, * meta?: array, + * ability_class?: class-string<\WP_Ability>, * ... * } $properties */ -function wp_register_ability( $name, array $properties = array() ): ?WP_Ability { +function wp_register_ability( string $name, array $properties = array() ): ?WP_Ability { if ( ! did_action( 'abilities_api_init' ) ) { _doing_it_wrong( __FUNCTION__, @@ -47,7 +48,7 @@ function wp_register_ability( $name, array $properties = array() ): ?WP_Ability /* translators: 1: abilities_api_init, 2: string value of the ability name. */ esc_html__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ), 'abilities_api_init', - '' . esc_html( $name instanceof WP_Ability ? $name->get_name() : $name ) . '' + '' . esc_html( $name ) . '' ), '0.1.0' ); diff --git a/includes/abilities-api/class-wp-abilities-registry.php b/includes/abilities-api/class-wp-abilities-registry.php index 8ec5867..39f1b93 100644 --- a/includes/abilities-api/class-wp-abilities-registry.php +++ b/includes/abilities-api/class-wp-abilities-registry.php @@ -43,12 +43,12 @@ final class WP_Abilities_Registry { * * @since 0.1.0 * - * @param string|\WP_Ability $name The name of the ability, or WP_Ability instance. The name must be a string - * containing a namespace prefix, i.e. `my-plugin/my-ability`. It can only - * contain lowercase alphanumeric characters, dashes and the forward slash. - * @param array $properties Optional. An associative array of properties for the ability. This should - * include `label`, `description`, `input_schema`, `output_schema`, - * `execute_callback`, `permission_callback`, and `meta`. + * @param string $name The name of the ability. The name must be a string containing a namespace + * prefix, i.e. `my-plugin/my-ability`. It can only contain lowercase + * alphanumeric characters, dashes and the forward slash. + * @param array $properties An associative array of properties for the ability. This should include + * `label`, `description`, `input_schema`, `output_schema`, + * `execute_callback`, `permission_callback`, `meta`, and ability_class. * @return ?\WP_Ability The registered ability instance on success, null on failure. * * @phpstan-param array{ @@ -59,16 +59,11 @@ final class WP_Abilities_Registry { * execute_callback?: callable( array $input): (mixed|\WP_Error), * permission_callback?: ?callable( ?array $input ): bool, * meta?: array, + * ability_class?: class-string<\WP_Ability>, * ... * } $properties */ - public function register( $name, array $properties = array() ): ?WP_Ability { - $ability = null; - if ( $name instanceof WP_Ability ) { - $ability = $name; - $name = $ability->get_name(); - } - + public function register( string $name, array $properties = array() ): ?WP_Ability { if ( ! preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $name ) ) { _doing_it_wrong( __METHOD__, @@ -90,12 +85,6 @@ public function register( $name, array $properties = array() ): ?WP_Ability { return null; } - // If the ability is already an instance, we can skip the rest of the validation. - if ( null !== $ability ) { - $this->registered_abilities[ $name ] = $ability; - return $ability; - } - if ( empty( $properties['label'] ) || ! is_string( $properties['label'] ) ) { _doing_it_wrong( __METHOD__, @@ -159,17 +148,22 @@ public function register( $name, array $properties = array() ): ?WP_Ability { return null; } - $ability = new WP_Ability( + if ( isset( $properties['ability_class'] ) && ! is_a( $properties['ability_class'], WP_Ability::class, true ) ) { + _doing_it_wrong( + __METHOD__, + esc_html__( 'The ability properties should provide a valid `ability_class` that extends WP_Ability.' ), + '0.1.0' + ); + return null; + } + + // The class is only used to instantiate the ability, and is not a property of the ability itself. + $ability_class = $properties['ability_class'] ?? WP_Ability::class; + unset( $properties['ability_class'] ); + + $ability = new $ability_class( $name, - array( - 'label' => $properties['label'], - 'description' => $properties['description'], - 'input_schema' => $properties['input_schema'] ?? array(), - 'output_schema' => $properties['output_schema'] ?? array(), - 'execute_callback' => $properties['execute_callback'], - 'permission_callback' => $properties['permission_callback'] ?? null, - 'meta' => $properties['meta'] ?? array(), - ) + $properties ); $this->registered_abilities[ $name ] = $ability; diff --git a/includes/abilities-api/class-wp-ability.php b/includes/abilities-api/class-wp-ability.php index eeae20c..ad3e9d0 100644 --- a/includes/abilities-api/class-wp-ability.php +++ b/includes/abilities-api/class-wp-ability.php @@ -15,7 +15,6 @@ * Encapsulates the properties and methods related to a specific ability in the registry. * * @since 0.1.0 - * @access private * * @see WP_Abilities_Registry */ @@ -91,6 +90,8 @@ class WP_Ability { * * Do not use this constructor directly. Instead, use the `wp_register_ability()` function. * + * @access private + * * @see wp_register_ability() * * @since 0.1.0 @@ -108,11 +109,28 @@ class WP_Ability { * execute_callback: callable( array $input): (mixed|\WP_Error), * permission_callback?: ?callable( ?array $input ): bool, * meta?: array, + * ..., * } $properties */ public function __construct( string $name, array $properties ) { $this->name = $name; + foreach ( $properties as $property_name => $property_value ) { + if ( ! property_exists( $this, $property_name ) ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: %s: Property name. */ + esc_html__( 'Property "%1$s" is not a valid property for ability "%2$s". Please check the %3$s class for allowed properties.' ), + '' . esc_html( $property_name ) . '', + '' . esc_html( $this->name ) . '', + '' . esc_html( self::class ) . '' + ), + '0.1.0' + ); + continue; + } + $this->$property_name = $property_value; } } diff --git a/tests/unit/abilities-api/wpAbilitiesRegistry.php b/tests/unit/abilities-api/wpAbilitiesRegistry.php index c341d23..61f8110 100644 --- a/tests/unit/abilities-api/wpAbilitiesRegistry.php +++ b/tests/unit/abilities-api/wpAbilitiesRegistry.php @@ -106,19 +106,6 @@ public function test_register_invalid_uppercase_characters_in_name() { $this->assertNull( $result ); } - /** - * Should reject ability instance with invalid name. - * - * @covers WP_Abilities_Registry::register - * - * @expectedIncorrectUsage WP_Abilities_Registry::register - */ - public function test_register_invalid_name_using_instance() { - $ability = new WP_Ability( 'invalid_name', array() ); - $result = $this->registry->register( $ability ); - $this->assertNull( $result ); - } - /** * Should reject ability registration without a label. * @@ -277,21 +264,6 @@ public function test_register_incorrect_already_registered_ability() { $this->assertNull( $result ); } - /** - * Should reject registration for already registered ability when passing an ability instance. - * - * @covers WP_Abilities_Registry::register - * - * @expectedIncorrectUsage WP_Abilities_Registry::register - */ - public function test_register_incorrect_already_registered_ability_using_instance() { - $ability = $this->registry->register( self::$test_ability_name, self::$test_ability_properties ); - - $result = $this->registry->register( $ability ); - - $this->assertNull( $result ); - } - /** * Should successfully register a new ability. * @@ -306,19 +278,6 @@ public function test_register_new_ability() { ); } - /** - * Should successfully register a new ability using an instance. - * - * @covers WP_Abilities_Registry::register - * @covers WP_Ability::construct - */ - public function test_register_new_ability_using_instance() { - $ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties ); - $result = $this->registry->register( $ability ); - - $this->assertSame( $ability, $result ); - } - /** * Should return false for ability that's not registered. * diff --git a/tests/unit/abilities-api/wpRegisterAbility.php b/tests/unit/abilities-api/wpRegisterAbility.php index 1668d55..2a5d03a 100644 --- a/tests/unit/abilities-api/wpRegisterAbility.php +++ b/tests/unit/abilities-api/wpRegisterAbility.php @@ -1,5 +1,14 @@ assertEquals( 'ability_invalid_permissions', $actual->get_error_code() ); } + /** + * Tests registering an ability with a custom ability class. + */ + public function test_register_ability_custom_ability_class(): void { + do_action( 'abilities_api_init' ); + + $result = wp_register_ability( + self::$test_ability_name, + array_merge( + self::$test_ability_properties, + array( + 'ability_class' => Mock_Custom_Ability::class, + ) + ) + ); + + $this->assertInstanceOf( Mock_Custom_Ability::class, $result ); + $this->assertSame( + 9999, + $result->execute( + array( + 'a' => 2, + 'b' => 3, + ) + ) + ); + + // Try again with an invalid class throws a doing it wrong. + $this->setExpectedIncorrectUsage( WP_Abilities_Registry::class . '::register' ); + wp_register_ability( + self::$test_ability_name, + array_merge( + self::$test_ability_properties, + array( + 'ability_class' => 'Non_Existent_Class', + ) + ) + ); + } + + /** * Tests executing an ability with input not matching schema. */