diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..2207b31
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,21 @@
+
+name: CI
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+
+jobs:
+ php-tests:
+ if: github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Run PHP Tests in src directory
+ uses: alleyinteractive/action-test-php@develop
+ with:
+ skip-services: 'true'
+ wordpress-version: 'false'
diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
deleted file mode 100644
index c336407..0000000
--- a/.github/workflows/coding-standards.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Coding Standards
-
-on:
- push:
- branches:
- - main
- pull_request:
- schedule:
- - cron: '0 0 * * *'
-
-jobs:
- coding-standards:
- uses: alleyinteractive/.github/.github/workflows/php-coding-standards.yml@main
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
deleted file mode 100644
index e293dd9..0000000
--- a/.github/workflows/unit-test.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Testing Suite
-
-on:
- push:
- branches:
- - main
- pull_request:
- schedule:
- - cron: '0 0 * * *'
-
-jobs:
- unit-test:
- uses: alleyinteractive/.github/.github/workflows/php-tests.yml@main
diff --git a/.gitignore b/.gitignore
index a854579..d2e965f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ build
vendor
composer.lock
node_modules
+.phpunit.cache
# Log files
*.log
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 053e5df..5faece9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
All notable changes to Cache Collector will be documented in this file.
+## 1.1.0 - 2024-07-16
+
+- Fix type error when purging against an unknown term.
+
+## v1.0.1 - 2023-12-07
+
+- Fix to register the post type on `init`.
+
+## v1.0.0 - 2023-12-02
+
+- Initial release.
+
## 0.1.1 - 2022-12-07
- Fix bug with post type registration being called before `init`.
diff --git a/composer.json b/composer.json
index 77b5bb4..d4d1d77 100644
--- a/composer.json
+++ b/composer.json
@@ -15,13 +15,14 @@
],
"homepage": "https://github.com/alleyinteractive/cache-collector",
"require": {
- "php": "^8.0"
+ "php": "^8.1"
},
"require-dev": {
- "alleyinteractive/alley-coding-standards": "^1.0",
+ "alleyinteractive/alley-coding-standards": "^2.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
- "mantle-framework/testkit": "^0.9",
- "nunomaduro/collision": "^5.0"
+ "mantle-framework/testkit": "^1.0",
+ "php-stubs/wp-cli-stubs": "^2.10",
+ "szepeviktor/phpstan-wordpress": "^1.3"
},
"suggest": {
"psr/log": "For logging messages to when purging the cache"
@@ -46,9 +47,11 @@
"scripts": {
"phpcbf": "phpcbf .",
"phpcs": "phpcs .",
+ "phpstan": "phpstan --memory-limit=512M",
"phpunit": "phpunit",
"test": [
"@phpcs",
+ "@phpstan",
"@phpunit"
]
}
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..1a2b4db
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,24 @@
+includes:
+ - vendor/szepeviktor/phpstan-wordpress/extension.neon
+
+parameters:
+ # Level 9 is the highest level
+ level: max
+
+ paths:
+ - src/
+ - plugin.php
+
+ scanFiles:
+ - %rootDir%/../../php-stubs/wordpress-stubs/wordpress-stubs.php
+ - %rootDir%/../../php-stubs/wp-cli-stubs/wp-cli-stubs.php
+ - %rootDir%/../../php-stubs/wp-cli-stubs/wp-cli-commands-stubs.php
+ - %rootDir%/../../php-stubs/wp-cli-stubs/wp-cli-i18n-stubs.php
+
+# ignoreErrors:
+# - '#PHPDoc tag @var#'
+#
+# excludePaths:
+# - ./*/*/FileToBeExcluded.php
+#
+# checkMissingIterableValueType: false
diff --git a/phpunit.xml b/phpunit.xml
index 0595632..9dc4274 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,15 +1,15 @@
+
-
-
- tests
-
-
+
+
+ tests
+
+
diff --git a/plugin.php b/plugin.php
index 31baf4d..66761aa 100644
--- a/plugin.php
+++ b/plugin.php
@@ -3,11 +3,12 @@
* Plugin Name: cache-collector
* Plugin URI: https://github.com/alleyinteractive/cache-collector
* Description: Dynamic cache key collector for easy purging.
- * Version: 0.1.0
+ * Version: 1.1.0
* Author: Sean Fisher
* Author URI: https://github.com/alleyinteractive/cache-collector
* Requires at least: 5.9
- * Tested up to: 5.9
+ * Requires PHP: 8.1
+ * Tested up to: 6.6
*
* @package cache-collector
*/
@@ -23,7 +24,7 @@
/**
* Instantiate the plugin.
*/
-function cache_collector_setup() {
+function cache_collector_setup(): void {
add_action( 'init', [ Cache_Collector::class, 'register_post_type' ] );
// Register the post/term purge actions.
diff --git a/src/class-cache-collector.php b/src/class-cache-collector.php
index ea423c7..61fe624 100644
--- a/src/class-cache-collector.php
+++ b/src/class-cache-collector.php
@@ -56,54 +56,58 @@ class Cache_Collector {
*
* Array of arrays with the key and group as the values.
*
- * @var array>
+ * @var array>
*/
protected array $pending_keys = [];
/**
* Create a new Cache_Collector instance for a post.
*
- * @param WP_Post|int $post Post object/ID.
- * @param array ...$args Arguments to pass to the constructor.
- * @return static
+ * @param WP_Post|int $post Post object/ID.
+ * @param LoggerInterface|null $logger Logger to use.
+ * @return self
*
* @throws InvalidArgumentException If the post is invalid.
*/
- public static function for_post( WP_Post|int $post, array ...$args ): static {
+ public static function for_post( WP_Post|int $post, ?LoggerInterface $logger = null ): self {
if ( is_numeric( $post ) ) {
$post_id = $post;
$post = get_post( $post );
if ( empty( $post ) ) {
- throw new InvalidArgumentException( "Invalid post ID: {$post_id}" );
+ throw new InvalidArgumentException( 'Invalid post ID: ' . ( (int) $post_id ) );
}
}
- return new static( "post-{$post->ID}", $post, ...$args );
+ return new self( "post-{$post->ID}", $post, $logger );
}
/**
* Create a new Cache_Collector instance for a term.
*
- * @param WP_Term|int $term Term object/ID.
- * @param array ...$args Arguments to pass to the constructor.
- * @return static
+ * @param WP_Term|int $term Term object/ID.
+ * @param LoggerInterface|null $logger Logger to use.
+ * @return self
*
* @throws InvalidArgumentException If the term is invalid.
*/
- public static function for_term( WP_Term|int $term, array ...$args ) {
+ public static function for_term( WP_Term|int $term, ?LoggerInterface $logger = null ): self {
if ( is_numeric( $term ) ) {
$term_id = $term;
$term = get_term( $term );
+ if ( is_wp_error( $term ) ) {
+ throw new InvalidArgumentException( esc_html( 'Invalid term: ' . $term->get_error_message() ) );
+ }
+
if ( empty( $term ) ) {
- throw new InvalidArgumentException( "Invalid term ID: {$term_id}" );
+ throw new InvalidArgumentException( 'Invalid term ID: ' . ( (int) $term_id ) );
}
}
- return new static( "term-{$term->term_id}", $term, ...$args );
+ return new self( "term-{$term->term_id}", $term, $logger );
}
/**
@@ -111,11 +115,11 @@ public static function for_term( WP_Term|int $term, array ...$args ) {
*
* @param int $post_id Post ID.
*/
- public static function on_post_update( int $post_id ) {
+ public static function on_post_update( int $post_id ): void {
$post = get_post( $post_id );
if ( $post ) {
- static::for_post( $post )->purge();
+ self::for_post( $post )->purge();
}
}
@@ -124,18 +128,18 @@ public static function on_post_update( int $post_id ) {
*
* @param int[] $ids Term ID.
*/
- public static function on_term_update( array $ids ) {
+ public static function on_term_update( array $ids ): void {
foreach ( $ids as $id ) {
- static::for_term( $id )->purge();
+ self::for_term( $id )->purge();
}
}
/**
* Register the post type for the cache collector.
*/
- public static function register_post_type() {
+ public static function register_post_type(): void {
register_post_type( // phpcs:ignore WordPress.NamingConventions.ValidPostTypeSlug.NotStringLiteral
- static::POST_TYPE,
+ self::POST_TYPE,
[
'public' => false,
'publicly_queryable' => false,
@@ -150,7 +154,7 @@ public static function register_post_type() {
* it is not older than the threshold, it will check if the keys in the
* collection it is storing are expired.
*/
- public static function cleanup() {
+ public static function cleanup(): void {
$page = 1;
$limit = 100;
@@ -177,7 +181,7 @@ public static function cleanup() {
],
],
'paged' => $page++,
- 'post_type' => static::POST_TYPE,
+ 'post_type' => self::POST_TYPE,
'posts_per_page' => 100,
'suppress_filters' => false,
]
@@ -195,7 +199,9 @@ public static function cleanup() {
continue;
}
- ( new static( $collection, $post ) )->save();
+ if ( is_string( $collection ) ) {
+ ( new self( $collection, $post ) )->save();
+ }
}
}
}
@@ -241,10 +247,10 @@ public function register( string $key, string $group = '', int $ttl = 0, string
}
if ( ! in_array( $type, [ self::CACHE_OBJECT_CACHE, self::CACHE_TRANSIENT ], true ) ) {
- throw new InvalidArgumentException( "Invalid cache type: {$type}." );
+ throw new InvalidArgumentException( esc_html( "Invalid cache type: {$type}." ) );
}
- $pending_key = $key . static::DELIMITER . $group;
+ $pending_key = $key . self::DELIMITER . $group;
// Include the pending key for registration.
if ( ! isset( $this->pending_keys[ $type ][ $pending_key ] ) ) {
@@ -265,7 +271,7 @@ public function save() {
$original = $storage;
// Check if any of the existing keys are expired.
- foreach ( [ static::CACHE_OBJECT_CACHE, static::CACHE_TRANSIENT ] as $type ) {
+ foreach ( [ self::CACHE_OBJECT_CACHE, self::CACHE_TRANSIENT ] as $type ) {
if ( empty( $storage[ $type ] ) ) {
continue;
}
@@ -280,7 +286,7 @@ public function save() {
}
// Append the pending keys for each cache type.
- foreach ( [ static::CACHE_OBJECT_CACHE, static::CACHE_TRANSIENT ] as $type ) {
+ foreach ( [ self::CACHE_OBJECT_CACHE, self::CACHE_TRANSIENT ] as $type ) {
if ( empty( $this->pending_keys[ $type ] ) ) {
continue;
}
@@ -302,7 +308,7 @@ public function save() {
} elseif ( empty( $storage ) ) {
// Delete the parent object if there are no keys and if the parent
// is a cache collection post.
- if ( $this->parent instanceof WP_Post && static::POST_TYPE === $this->parent->post_type ) {
+ if ( $this->parent instanceof WP_Post && self::POST_TYPE === $this->parent->post_type ) {
wp_delete_post( $this->parent->ID, true );
if ( $this->logger ) {
@@ -336,7 +342,7 @@ public function keys(): array {
foreach ( $storage as $type => $keys ) {
$collection[ $type ] = array_map(
- fn ( string $key ) => explode( static::DELIMITER, $key ),
+ fn ( string $key ) => explode( self::DELIMITER, $key ),
array_keys( $keys )
);
}
@@ -362,13 +368,13 @@ public function purge() {
$dirty = false;
- foreach ( [ static::CACHE_OBJECT_CACHE, static::CACHE_TRANSIENT ] as $type ) {
+ foreach ( [ self::CACHE_OBJECT_CACHE, self::CACHE_TRANSIENT ] as $type ) {
if ( empty( $storage[ $type ] ) ) {
continue;
}
foreach ( $storage[ $type ] as $index => $expiration ) {
- [ $key, $cache_group ] = explode( static::DELIMITER, $index );
+ [ $key, $cache_group ] = explode( self::DELIMITER, $index );
// Check if the key is expired and should be removed.
if ( $expiration && $expiration < time() ) {
@@ -381,9 +387,8 @@ public function purge() {
// Purge the cache.
$deleted = match ( $type ) {
- static::CACHE_OBJECT_CACHE => wp_cache_delete( $key, $cache_group ),
- static::CACHE_TRANSIENT => delete_transient( $key ),
- default => false,
+ self::CACHE_OBJECT_CACHE => wp_cache_delete( $key, $cache_group ),
+ self::CACHE_TRANSIENT => delete_transient( $key ),
};
if ( $this->logger ) {
@@ -461,7 +466,7 @@ public function get_parent_object( bool $create = true ): WP_Post|WP_Term|null {
'name' => $this->get_storage_name(),
'no_found_rows' => true,
'post_status' => 'publish',
- 'post_type' => static::POST_TYPE,
+ 'post_type' => self::POST_TYPE,
'posts_per_page' => 1,
'suppress_filters' => false,
]
@@ -481,7 +486,7 @@ public function get_parent_object( bool $create = true ): WP_Post|WP_Term|null {
'post_name' => $this->get_storage_name(),
'post_status' => 'publish',
'post_title' => $this->get_storage_name(),
- 'post_type' => static::POST_TYPE,
+ 'post_type' => self::POST_TYPE,
],
true
);
@@ -515,14 +520,13 @@ public function get_parent_object( bool $create = true ): WP_Post|WP_Term|null {
*
* Not intended for public API usage {@see Cache_Collector::keys()}.
*
- * @return array
+ * @return array>
*/
protected function get_storage(): array {
if ( $this->parent ) {
$keys = match ( $this->parent::class ) {
- WP_Post::class => get_post_meta( $this->parent->ID, static::META_KEY, true ),
- WP_Term::class => get_term_meta( $this->parent->term_id, static::META_KEY, true ),
- default => [],
+ WP_Post::class => get_post_meta( $this->parent->ID, self::META_KEY, true ),
+ WP_Term::class => get_term_meta( $this->parent->term_id, self::META_KEY, true ),
};
return is_array( $keys ) ? $keys : [];
@@ -534,10 +538,10 @@ protected function get_storage(): array {
/**
* Store keys in the parent post/term.
*
- * @param array $keys The keys to store.
+ * @param array> $keys Keys to store.
* @return void
*/
- protected function store_keys( array $keys ) {
+ protected function store_keys( array $keys ): void {
$this->get_parent_object();
if ( ! $this->parent ) {
@@ -545,8 +549,8 @@ protected function store_keys( array $keys ) {
}
match ( $this->parent::class ) {
- WP_Post::class => update_post_meta( $this->parent->ID, static::META_KEY, $keys ),
- WP_Term::class => update_term_meta( $this->parent->term_id, static::META_KEY, $keys ),
+ WP_Post::class => update_post_meta( $this->parent->ID, self::META_KEY, $keys ),
+ WP_Term::class => update_term_meta( $this->parent->term_id, self::META_KEY, $keys ),
};
}
}
diff --git a/src/class-cli.php b/src/class-cli.php
index 13bcf33..d562434 100644
--- a/src/class-cli.php
+++ b/src/class-cli.php
@@ -21,10 +21,9 @@ class CLI {
*
* : The name of the collection to purge.
*
- * @param array $args Positional arguments.
- * @param array $assoc_args Associative arguments.
+ * @param array $args Positional arguments.
*/
- public function purge( $args, $assoc_args ) {
+ public function purge( $args ): void {
[ $collection ] = $args;
$instance = new Cache_Collector( $collection, function_exists( 'ai_logger' ) ? ai_logger() : null );
@@ -40,14 +39,13 @@ public function purge( $args, $assoc_args ) {
*
* : The ID of the post to purge.
*
- * @param array $args Positional arguments.
- * @param array $assoc_args Associative arguments.
+ * @param array $args Positional arguments.
*/
- public function purge_post( $args, $assoc_args ) {
+ public function purge_post( $args ): void {
[ $post ] = $args;
try {
- Cache_Collector::for_post( $post )->purge();
+ Cache_Collector::for_post( (int) $post )->purge();
} catch ( Throwable $e ) {
\WP_CLI::error( 'Error purging: ' . $e->getMessage() );
}
@@ -61,14 +59,13 @@ public function purge_post( $args, $assoc_args ) {
*
* : The ID of the term to purge.
*
- * @param array $args Positional arguments.
- * @param array $assoc_args Associative arguments.
+ * @param array $args Positional arguments.
*/
- public function purge_term( $args, $assoc_args ) {
+ public function purge_term( $args ): void {
[ $term ] = $args;
try {
- Cache_Collector::for_term( $term )->purge();
+ Cache_Collector::for_term( (int) $term )->purge();
} catch ( Throwable $e ) {
\WP_CLI::error( 'Error purging: ' . $e->getMessage() );
}
diff --git a/tests/test-cache-collector.php b/tests/CacheCollectorTest.php
similarity index 99%
rename from tests/test-cache-collector.php
rename to tests/CacheCollectorTest.php
index 514ce1a..90a1df5 100644
--- a/tests/test-cache-collector.php
+++ b/tests/CacheCollectorTest.php
@@ -7,7 +7,7 @@
/**
* Visit {@see https://mantle.alley.co/testing/test-framework.html} to learn more.
*/
-class Cache_Collector_Test extends Test_Case {
+class CacheCollectorTest extends Test_Case {
public function test_register_key() {
$instance = new Cache_Collector( __FUNCTION__ );
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 1d4cbc0..cb8fe4d 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -9,4 +9,5 @@
\Mantle\Testing\manager()
// Load the main file of the plugin.
->loaded( fn () => require_once __DIR__ . '/../plugin.php' )
+ ->with_sqlite()
->install();