diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php index ce4b89af4..59138e1bd 100644 --- a/includes/Checker/Abstract_Check_Runner.php +++ b/includes/Checker/Abstract_Check_Runner.php @@ -368,7 +368,10 @@ final public function get_checks_to_run() { $check_flags = $check_flags | Check_Repository::INCLUDE_EXPERIMENTAL; } - $checks = $this->check_repository->get_checks( $check_flags, $check_slugs ); + $checks = $this->check_repository->get_checks( $check_flags ) + ->require( $check_slugs ) // Ensures all of the given slugs are valid. + ->include( $check_slugs ) // Ensures only the checks with the given slugs are included. + ->to_map(); // Filters the checks by specific categories. $categories = $this->get_categories(); diff --git a/includes/Checker/Check_Collection.php b/includes/Checker/Check_Collection.php new file mode 100644 index 000000000..c5698cbe3 --- /dev/null +++ b/includes/Checker/Check_Collection.php @@ -0,0 +1,74 @@ + $check_obj` pairs. + */ + public function to_map(): array; + + /** + * Returns a new check collection containing the subset of checks based on the given check filter function. + * + * @since n.e.x.t + * + * @param callable $filter_fn Filter function that accepts a single check object and should return a boolean for + * whether to include the check in the new collection. + * @return Check_Collection New check collection, effectively a subset of this one. + */ + public function filter( callable $filter_fn ): Check_Collection; + + /** + * Returns a new check collection containing the subset of checks based on the given check slugs. + * + * If the given list is empty, the same collection will be returned without any change. + * + * @since n.e.x.t + * + * @param array $check_slugs List of slugs to limit to only those. If empty, the same collection is returned. + * @return Check_Collection New check collection, effectively a subset of this one. + */ + public function include( array $check_slugs ): Check_Collection; + + /** + * Throws an exception if any of the given check slugs are not present, or returns the same collection otherwise. + * + * @since n.e.x.t + * + * @param array $check_slugs List of slugs to limit to only those. If empty, the same collection is returned. + * @return Check_Collection The unchanged check collection. + * + * @throws Exception Thrown when any of the given check slugs is not present in the collection. + */ + public function require( array $check_slugs ): Check_Collection; +} diff --git a/includes/Checker/Check_Repository.php b/includes/Checker/Check_Repository.php index 1c1141093..7c757d04b 100644 --- a/includes/Checker/Check_Repository.php +++ b/includes/Checker/Check_Repository.php @@ -63,9 +63,8 @@ public function register_check( $slug, Check $check ); * * @since n.e.x.t * - * @param int $flags The check type flag. - * @param array $check_slugs An array of check slugs to return. - * @return array An indexed array of check instances. + * @param int $flags The check type flag. + * @return Check_Collection Check collection providing an indexed array of check instances. */ - public function get_checks( $flags = self::TYPE_ALL, array $check_slugs = array() ); + public function get_checks( $flags = self::TYPE_ALL ); } diff --git a/includes/Checker/Default_Check_Collection.php b/includes/Checker/Default_Check_Collection.php new file mode 100644 index 000000000..de81e85b9 --- /dev/null +++ b/includes/Checker/Default_Check_Collection.php @@ -0,0 +1,232 @@ + $check_obj` pairs. + * + * @since n.e.x.t + * @var array + */ + private $checks; + + /** + * List of check slugs, in the same order as `$checks` - effectively the keys of that array. + * + * @since n.e.x.t + * @var array + */ + private $slugs; + + /** + * Constructor. + * + * @since n.e.x.t + * + * @param array $checks Map of `$check_slug => $check_obj` pairs for the collection. + */ + public function __construct( array $checks ) { + $this->checks = $checks; + $this->slugs = array_keys( $this->checks ); + } + + /** + * Returns the raw indexed array representation of this collection. + * + * @since n.e.x.t + * + * @return array The indexed array of check objects. + */ + public function to_array(): array { + return array_values( $this->checks ); + } + + /** + * Returns the raw map of check slugs and their check objects as a representation of this collection. + * + * @since n.e.x.t + * + * @return array Map of `$check_slug => $check_obj` pairs. + */ + public function to_map(): array { + return $this->checks; + } + + /** + * Returns a new check collection containing the subset of checks based on the given check filter function. + * + * @since n.e.x.t + * + * @param callable $filter_fn Filter function that accepts a single check object and should return a boolean for + * whether to include the check in the new collection. + * @return Check_Collection New check collection, effectively a subset of this one. + */ + public function filter( callable $filter_fn ): Check_Collection { + return new self( + array_filter( + $this->checks, + $filter_fn + ) + ); + } + + /** + * Returns a new check collection containing the subset of checks based on the given check slugs. + * + * If the given list is empty, the same collection will be returned without any change. + * + * @since n.e.x.t + * + * @param array $check_slugs List of slugs to limit to only those. If empty, the same collection is returned. + * @return Check_Collection New check collection, effectively a subset of this one. + */ + public function include( array $check_slugs ): Check_Collection { + // Return unmodified collection if no check slugs to limit to are given. + if ( ! $check_slugs ) { + return $this; + } + + $check_slugs = array_flip( $check_slugs ); + + $checks = array(); + foreach ( $this->checks as $slug => $check ) { + if ( ! isset( $check_slugs[ $slug ] ) ) { + continue; + } + + $checks[ $slug ] = $check; + } + + return new self( $checks ); + } + + /** + * Throws an exception if any of the given check slugs are not present, or returns the same collection otherwise. + * + * @since n.e.x.t + * + * @param array $check_slugs List of slugs to limit to only those. If empty, the same collection is returned. + * @return Check_Collection The unchanged check collection. + * + * @throws Exception Thrown when any of the given check slugs is not present in the collection. + */ + public function require( array $check_slugs ): Check_Collection { + foreach ( $check_slugs as $slug ) { + if ( ! isset( $this->checks[ $slug ] ) ) { + throw new Exception( + sprintf( + /* translators: %s: The Check slug. */ + __( 'Check with the slug "%s" does not exist.', 'plugin-check' ), + $slug + ) + ); + } + } + + return $this; + } + + /** + * Counts the checks in the collection. + * + * @since n.e.x.t + * + * @return int Number of checks in the collection. + */ + public function count(): int { + return count( $this->checks ); + } + + /** + * Returns an iterator for the checks in the collection. + * + * @since n.e.x.t + * + * @return Traversable Checks iterator. + */ + public function getIterator(): Traversable { + return new ArrayIterator( $this->checks ); + } + + /** + * Checks whether a check exists with the given slug or index. + * + * @since n.e.x.t + * + * @param string|int $offset Either a check slug (string) or index (integer). + * @return bool True if a check exists at the given slug or index, false otherwise. + */ + public function offsetExists( $offset ) { + if ( is_string( $offset ) ) { + return isset( $this->checks[ $offset ] ); + } + + return isset( $this->slugs[ $offset ] ); + } + + /** + * Retrieves the check with the given slug or index. + * + * @since n.e.x.t + * + * @param string|int $offset Either a check slug (string) or index (integer). + * @return Check|null Check with the given slug or index, or null if it does not exist. + */ + public function offsetGet( $offset ) { + if ( is_string( $offset ) ) { + if ( isset( $this->checks[ $offset ] ) ) { + return $this->checks[ $offset ]; + } + return null; + } + + if ( isset( $this->slugs[ $offset ] ) ) { + return $this->checks[ $this->slugs[ $offset ] ]; + } + + return null; + } + + /** + * Sets a check in the collection. + * + * This method does nothing as the collection is read-only. + * + * @since n.e.x.t + * + * @param string|int $offset Either a check slug (string) or index (integer). + * @param mixed $value Value to set. + */ + public function offsetSet( $offset, $value ) { + // Not implemented as this is a read-only collection. + } + + /** + * Removes a check from the collection. + * + * This method does nothing as the collection is read-only. + * + * @since n.e.x.t + * + * @param string|int $offset Either a check slug (string) or index (integer). + */ + public function offsetUnset( $offset ) { + // Not implemented as this is a read-only collection. + } +} diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index 761b0fee3..aa1670c14 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -82,13 +82,10 @@ public function register_check( $slug, Check $check ) { * * @since n.e.x.t * - * @param int $flags The check type flag. - * @param array $check_slugs An array of check slugs to return. - * @return array An array of check instances. - * - * @throws Exception Thrown when invalid flag is passed, or Check slug does not exist. + * @param int $flags The check type flag. + * @return Check_Collection Check collection providing an indexed array of check instances. */ - public function get_checks( $flags = self::TYPE_ALL, array $check_slugs = array() ) { + public function get_checks( $flags = self::TYPE_ALL ) { $checks = array(); if ( $flags & self::TYPE_STATIC ) { @@ -99,34 +96,13 @@ public function get_checks( $flags = self::TYPE_ALL, array $check_slugs = array( $checks += $this->runtime_checks; } - // Filter out the specific check slugs requested. - if ( ! empty( $check_slugs ) ) { - $checks = array_map( - function ( $slug ) use ( $checks ) { - if ( ! isset( $checks[ $slug ] ) ) { - throw new Exception( - sprintf( - /* translators: %s: The Check slug. */ - __( 'Check with the slug "%s" does not exist.', 'plugin-check' ), - $slug - ) - ); - } - - return $checks[ $slug ]; - }, - $check_slugs - ); - } - // Return all checks, including experimental if requested. if ( $flags & self::INCLUDE_EXPERIMENTAL ) { - return $checks; + return new Default_Check_Collection( $checks ); } // Remove experimental checks before returning. - return array_filter( - $checks, + return ( new Default_Check_Collection( $checks ) )->filter( static function ( $check ) { return $check->get_stability() !== Check::STABILITY_EXPERIMENTAL; } diff --git a/phpmd.xml b/phpmd.xml index 0dc175f0d..6e6f9cc61 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -11,6 +11,7 @@ + @@ -19,6 +20,12 @@ + + + + + + diff --git a/tests/phpunit/Checker/Check_Categories_Tests.php b/tests/phpunit/Checker/Check_Categories_Tests.php index a6d26035b..7399a80e6 100644 --- a/tests/phpunit/Checker/Check_Categories_Tests.php +++ b/tests/phpunit/Checker/Check_Categories_Tests.php @@ -44,7 +44,8 @@ public function test_filter_checks_by_categories( array $categories, array $all_ $this->repository->register_check( $check[0], $check[1] ); } - $checks = $this->repository->get_checks(); + $checks = $this->repository->get_checks() + ->to_map(); $check_categories = new Check_Categories(); $filtered_checks = $check_categories->filter_checks_by_categories( $checks, $categories ); diff --git a/tests/phpunit/Checker/Default_Check_Collection_Tests.php b/tests/phpunit/Checker/Default_Check_Collection_Tests.php new file mode 100644 index 000000000..152e27370 --- /dev/null +++ b/tests/phpunit/Checker/Default_Check_Collection_Tests.php @@ -0,0 +1,93 @@ +checks = array( + 'static_check' => new Static_Check(), + 'runtime_check' => new Runtime_Check(), + ); + + $repository = new Default_Check_Repository(); + foreach ( $this->checks as $slug => $check ) { + $repository->register_check( $slug, $check ); + } + + $this->collection = $repository->get_checks(); + } + + public function test_to_array() { + $this->assertSame( + array_values( $this->checks ), + $this->collection->to_array() + ); + } + + public function test_to_map() { + $this->assertSame( + $this->checks, + $this->collection->to_map() + ); + } + + public function test_filter() { + $this->assertSame( + array( $this->checks['runtime_check'] ), + $this->collection->filter( + function ( $check ) { + return $check instanceof Runtime_Check_Interface; + } + )->to_array() + ); + } + + public function test_include() { + $this->assertSame( + array( $this->checks['static_check'] ), + $this->collection->include( array( 'static_check' ) )->to_array() + ); + } + + public function test_include_with_empty() { + $this->assertSame( + array_values( $this->checks ), + $this->collection->include( array() )->to_array() + ); + } + + public function test_include_with_invalid() { + $this->assertSame( + array( $this->checks['runtime_check'] ), + $this->collection->include( array( 'runtime_check', 'invalid_check' ) )->to_array() + ); + } + + public function test_require() { + $this->assertSame( + array_values( $this->checks ), + $this->collection->require( array( 'static_check' ) )->to_array() + ); + } + + public function test_require_with_invalid() { + $this->expectException( 'Exception' ); + $this->expectExceptionMessage( 'Check with the slug "invalid_check" does not exist.' ); + + $this->collection->require( array( 'static_check', 'invalid_check' ) ); + } +} diff --git a/tests/phpunit/Checker/Default_Check_Repository_Tests.php b/tests/phpunit/Checker/Default_Check_Repository_Tests.php index 8822ce622..b1a8898d9 100644 --- a/tests/phpunit/Checker/Default_Check_Repository_Tests.php +++ b/tests/phpunit/Checker/Default_Check_Repository_Tests.php @@ -16,6 +16,8 @@ class Default_Check_Repository_Tests extends WP_UnitTestCase { + private $repository; + public function set_up() { parent::set_up(); @@ -26,14 +28,14 @@ public function test_register_static_check() { $check = new Static_Check(); $this->repository->register_check( 'static_check', $check ); - $this->assertSame( array( 'static_check' => $check ), $this->repository->get_checks() ); + $this->assertSame( array( 'static_check' => $check ), $this->repository->get_checks()->to_map() ); } public function test_register_runtime_check() { $check = new Runtime_Check(); $this->repository->register_check( 'runtime_check', $check ); - $this->assertSame( array( 'runtime_check' => $check ), $this->repository->get_checks() ); + $this->assertSame( array( 'runtime_check' => $check ), $this->repository->get_checks()->to_map() ); } public function test_register_exception_thrown_for_invalid_check() { @@ -86,7 +88,7 @@ public function test_get_checks_returns_all_checks() { 'runtime_check' => $runtime_check, ); - $this->assertSame( $expected, $this->repository->get_checks() ); + $this->assertSame( $expected, $this->repository->get_checks()->to_map() ); } public function test_get_checks_returns_static_checks_via_flag() { @@ -96,7 +98,7 @@ public function test_get_checks_returns_static_checks_via_flag() { $this->repository->register_check( 'static_check', $static_check ); $this->repository->register_check( 'runtime_check', $runtime_check ); - $this->assertSame( array( 'static_check' => $static_check ), $this->repository->get_checks( Check_Repository::TYPE_STATIC ) ); + $this->assertSame( array( 'static_check' => $static_check ), $this->repository->get_checks( Check_Repository::TYPE_STATIC )->to_map() ); } public function test_get_checks_returns_runtime_checks_via_flag() { @@ -106,24 +108,7 @@ public function test_get_checks_returns_runtime_checks_via_flag() { $this->repository->register_check( 'static_check', $static_check ); $this->repository->register_check( 'runtime_check', $runtime_check ); - $this->assertSame( array( 'runtime_check' => $runtime_check ), $this->repository->get_checks( Check_Repository::TYPE_RUNTIME ) ); - } - - public function test_get_checks_returns_checks_via_slug() { - $static_check = new Static_Check(); - $runtime_check = new Runtime_Check(); - - $this->repository->register_check( 'static_check', $static_check ); - $this->repository->register_check( 'runtime_check', $runtime_check ); - - $this->assertSame( array( $static_check ), $this->repository->get_checks( Check_Repository::TYPE_ALL, array( 'static_check' ) ) ); - } - - public function test_get_checks_throws_exception_for_invalid_check_slug() { - $this->expectException( 'Exception' ); - $this->expectExceptionMessage( 'Check with the slug "invalid_check" does not exist.' ); - - $this->repository->get_checks( Check_Repository::TYPE_ALL, array( 'invalid_check' ) ); + $this->assertSame( array( 'runtime_check' => $runtime_check ), $this->repository->get_checks( Check_Repository::TYPE_RUNTIME )->to_map() ); } public function test_get_checks_returns_no_experimental_checks_by_default() { @@ -142,7 +127,7 @@ public function test_get_checks_returns_no_experimental_checks_by_default() { 'runtime_check' => $runtime_check, ); - $this->assertSame( $expected, $this->repository->get_checks() ); + $this->assertSame( $expected, $this->repository->get_checks()->to_map() ); } public function test_get_checks_returns_experimental_checks_with_flag() { @@ -163,7 +148,7 @@ public function test_get_checks_returns_experimental_checks_with_flag() { 'experimental_runtime_check' => $experimental_runtime_check, ); - $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_ALL | Check_Repository::INCLUDE_EXPERIMENTAL ) ); + $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_ALL | Check_Repository::INCLUDE_EXPERIMENTAL )->to_map() ); } public function test_get_checks_returns_experimental_static_checks_with_flag() { @@ -182,7 +167,7 @@ public function test_get_checks_returns_experimental_static_checks_with_flag() { 'experimental_static_check' => $experimental_static_check, ); - $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_STATIC | Check_Repository::INCLUDE_EXPERIMENTAL ) ); + $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_STATIC | Check_Repository::INCLUDE_EXPERIMENTAL )->to_map() ); } public function test_get_checks_returns_experimental_runtime_checks_with_flag() { @@ -201,6 +186,6 @@ public function test_get_checks_returns_experimental_runtime_checks_with_flag() 'experimental_runtime_check' => $experimental_runtime_check, ); - $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_RUNTIME | Check_Repository::INCLUDE_EXPERIMENTAL ) ); + $this->assertSame( $expected, $this->repository->get_checks( Check_Repository::TYPE_RUNTIME | Check_Repository::INCLUDE_EXPERIMENTAL )->to_map() ); } }