From 068cbf0c225bcbe185911305d6e5a9741b087cd7 Mon Sep 17 00:00:00 2001 From: Miguel Colmenares Date: Mon, 17 Aug 2026 10:21:02 -0500 Subject: [PATCH 1/2] feat: Adopt silverassist/wp-plugin-kernel across all sub-plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces each sub-plugin's own duplicated LoadableInterface + Plugin bootstrap with the shared silverassist/wp-plugin-kernel package, closing the gap documented after the wp-plugin-kernel rollout (7 other plugins already migrated; this monorepo was the one holdout). - Every component now implements the kernel's LoadableInterface (get_priority(): int, should_load(): bool, init(): void) instead of the old local priority()/register() contract, plus a static instance(): self method — AbstractPlugin's load_components() calls $class::instance() with no arguments. - The 3 Core/Plugin.php files shrink to extending AbstractPlugin with just get_components(): array (a list of class-strings); the singleton access, priority sort, and per-component error isolation are all inherited, not re-typed per plugin. - contentful-tables' TableDataLoader was constructor-injected into ShortcodeRegistrar/SettingsPage before this change — since kernel-managed components can't take constructor arguments, TableDataLoader became a proper singleton and the other two now fetch it via TableDataLoader::instance() in their own constructors. - graphql-shortcode-support already used get_priority()/should_load()/ init() method names independently (it converged on nearly the same shape as the kernel before the kernel existed) — its migration was just swapping the interface import and Plugin.php's base class. - Updated 3 READMEs' directory trees and CONTRIBUTING.md's documented interface contract, which still described the old local pattern (caught by a pre-PR core-review pass). Verified: make phpcs, make phpstan, and make build all clean across all three sub-plugins. --- CHANGELOG.md | 3 + CONTRIBUTING.md | 27 +++- community-listings/README.md | 4 +- community-listings/composer.json | 1 + .../Admin/ProviderListingsMetaBox.php | 44 ++++++- .../includes/Admin/SettingsPage.php | 44 ++++++- .../Core/Interfaces/LoadableInterface.php | 41 ------- community-listings/includes/Core/Plugin.php | 78 +++--------- .../includes/Service/CptRegistrar.php | 44 ++++++- .../includes/Service/GraphQLResolver.php | 44 ++++++- .../includes/Service/RestApiFilters.php | 44 ++++++- contentful-tables/README.md | 4 +- contentful-tables/composer.json | 1 + .../includes/Admin/SettingsPage.php | 48 ++++++-- .../Core/Interfaces/LoadableInterface.php | 41 ------- contentful-tables/includes/Core/Plugin.php | 83 +++---------- .../includes/Service/GraphQLResolver.php | 44 ++++++- .../includes/Service/ShortcodeRegistrar.php | 48 ++++++-- .../includes/Service/TableDataLoader.php | 48 +++++++- graphql-shortcode-support/README.md | 4 +- graphql-shortcode-support/composer.json | 1 + .../includes/Admin/SettingsPage.php | 2 +- .../Core/Interfaces/LoadableInterface.php | 44 ------- .../includes/Core/Plugin.php | 115 +++--------------- .../Service/GraphQLShortcodeResolver.php | 2 +- 25 files changed, 456 insertions(+), 403 deletions(-) delete mode 100644 community-listings/includes/Core/Interfaces/LoadableInterface.php delete mode 100644 contentful-tables/includes/Core/Interfaces/LoadableInterface.php delete mode 100644 graphql-shortcode-support/includes/Core/Interfaces/LoadableInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d23e39f..b9fba20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Individual plugin versions are tracked separately in their respective plugin hea ## [Unreleased] +### Changed +- Adopted `silverassist/wp-plugin-kernel` in all three sub-plugins, replacing each one's own duplicated `LoadableInterface`/`Plugin` bootstrap with the shared `AbstractPlugin` — components now implement `get_priority()`/`should_load()`/`init()` (the kernel's contract) instead of the old local `priority()`/`register()`. In `contentful-tables`, `TableDataLoader` became a proper singleton (`ShortcodeRegistrar`/`SettingsPage` now fetch it via `TableDataLoader::instance()` instead of constructor injection), since kernel-managed components are instantiated with no arguments. + ### Added - **community-listings:** Admin meta box ("Provider Listings") on the `community` post type editor, city-level posts only — lets editors view/update the `provider_listings` JSON without CLI access. JSON-safe save via `$wpdb->update()`/`insert()` (avoids `update_post_meta()`'s `wp_unslash()` corrupting `\"` escapes), with byte/provider counts, client-side validation, and post-meta cache invalidation after the direct write (#2) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6d6e03..de6906a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,12 +117,31 @@ Components load with specific priorities: - **30**: UI components (admin pages) ### Interface Implementation -All loadable components must implement `LoadableInterface`: +All loadable components must implement `LoadableInterface` from `silverassist/wp-plugin-kernel`, and expose a static `instance()` method (the kernel loads components via `$class::instance()`): ```php -interface LoadableInterface { - public function priority(): int; - public function register(): void; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; + +final class YourComponent implements LoadableInterface { + private static ?self $instance = null; + + private function __construct() {} + + public static function instance(): self { + return self::$instance ??= new self(); + } + + public function get_priority(): int { + return 20; + } + + public function should_load(): bool { + return true; + } + + public function init(): void { + // Register WordPress hooks here. + } } ``` diff --git a/community-listings/README.md b/community-listings/README.md index d407fe4..07d46a1 100644 --- a/community-listings/README.md +++ b/community-listings/README.md @@ -24,9 +24,7 @@ community-listings/ ├── includes/ │ ├── Core/ │ │ ├── Activator.php # Activation/deactivation handlers -│ │ ├── Interfaces/ -│ │ │ └── LoadableInterface.php # Component contract -│ │ └── Plugin.php # Singleton bootstrap +│ │ └── Plugin.php # Singleton bootstrap (extends wp-plugin-kernel's AbstractPlugin) │ └── Service/ │ ├── CptRegistrar.php # CPT + meta field registration (priority 10) │ ├── GraphQLResolver.php # WPGraphQL do_shortcode() (priority 20) diff --git a/community-listings/composer.json b/community-listings/composer.json index 582d824..f61486d 100644 --- a/community-listings/composer.json +++ b/community-listings/composer.json @@ -5,6 +5,7 @@ "license": "proprietary", "require": { "php": ">=8.2", + "silverassist/wp-plugin-kernel": "^1.0", "silverassist/wp-settings-hub": "^1.1" }, "require-dev": { diff --git a/community-listings/includes/Admin/ProviderListingsMetaBox.php b/community-listings/includes/Admin/ProviderListingsMetaBox.php index 12778c3..68def09 100644 --- a/community-listings/includes/Admin/ProviderListingsMetaBox.php +++ b/community-listings/includes/Admin/ProviderListingsMetaBox.php @@ -12,7 +12,7 @@ namespace SilverAssist\CommunityListings\Admin; -use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; /** * Registers and handles provider_listings meta box editing. @@ -21,6 +21,33 @@ */ final class ProviderListingsMetaBox implements LoadableInterface { + /** + * Singleton instance. + * + * @since 2.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). + * + * @since 2.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 2.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * Nonce action. * @@ -64,10 +91,21 @@ final class ProviderListingsMetaBox implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 25; } + /** + * Whether this component should load. + * + * @since 2.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -75,7 +113,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_action( 'add_meta_boxes_community', array( $this, 'register_meta_box' ) ); \add_action( 'save_post_community', array( $this, 'save_provider_listings' ), 10, 3 ); \add_action( 'admin_notices', array( $this, 'render_invalid_json_notice' ) ); diff --git a/community-listings/includes/Admin/SettingsPage.php b/community-listings/includes/Admin/SettingsPage.php index e2f5d03..46afa05 100644 --- a/community-listings/includes/Admin/SettingsPage.php +++ b/community-listings/includes/Admin/SettingsPage.php @@ -12,7 +12,7 @@ namespace SilverAssist\CommunityListings\Admin; -use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; use SilverAssist\SettingsHub\SettingsHub; /** @@ -35,6 +35,33 @@ final class SettingsPage implements LoadableInterface { */ private const PLUGIN_SLUG = 'community-listings'; + /** + * Singleton instance. + * + * @since 2.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). + * + * @since 2.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 2.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * Return the loading priority. * @@ -42,10 +69,21 @@ final class SettingsPage implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 30; } + /** + * Whether this component should load. + * + * @since 2.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -53,7 +91,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_action( 'init', array( $this, 'register_with_settings_hub' ) ); } diff --git a/community-listings/includes/Core/Interfaces/LoadableInterface.php b/community-listings/includes/Core/Interfaces/LoadableInterface.php deleted file mode 100644 index 5ccd0fc..0000000 --- a/community-listings/includes/Core/Interfaces/LoadableInterface.php +++ /dev/null @@ -1,41 +0,0 @@ - */ - public function init(): void { - $this->components = array( - new CptRegistrar(), - new RestApiFilters(), - new GraphQLResolver(), - new ProviderListingsMetaBox(), - new SettingsPage(), + protected function get_components(): array { + return array( + CptRegistrar::class, + RestApiFilters::class, + GraphQLResolver::class, + ProviderListingsMetaBox::class, + SettingsPage::class, ); - - // Sort by priority ascending. - \usort( - $this->components, - static fn ( LoadableInterface $a, LoadableInterface $b ): int => $a->priority() <=> $b->priority() - ); - - // Register hooks. - foreach ( $this->components as $component ) { - $component->register(); - } } } diff --git a/community-listings/includes/Service/CptRegistrar.php b/community-listings/includes/Service/CptRegistrar.php index 7101f36..d89d80f 100644 --- a/community-listings/includes/Service/CptRegistrar.php +++ b/community-listings/includes/Service/CptRegistrar.php @@ -12,7 +12,7 @@ namespace SilverAssist\CommunityListings\Service; -use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; /** * Registers the Community custom post type and meta fields. @@ -23,6 +23,33 @@ */ final class CptRegistrar implements LoadableInterface { + /** + * Singleton instance. + * + * @since 2.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). + * + * @since 2.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 2.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * Meta fields with their types. * @@ -53,10 +80,21 @@ final class CptRegistrar implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 10; } + /** + * Whether this component should load. + * + * @since 2.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -64,7 +102,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_action( 'init', array( self::class, 'register_post_type' ) ); \add_action( 'init', array( $this, 'register_meta' ) ); \add_action( 'graphql_register_types', array( $this, 'register_graphql_meta_fields' ) ); diff --git a/community-listings/includes/Service/GraphQLResolver.php b/community-listings/includes/Service/GraphQLResolver.php index d37c18b..5fa64a5 100644 --- a/community-listings/includes/Service/GraphQLResolver.php +++ b/community-listings/includes/Service/GraphQLResolver.php @@ -12,7 +12,7 @@ namespace SilverAssist\CommunityListings\Service; -use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; /** * Applies do_shortcode() to Community content fields in WPGraphQL. @@ -23,6 +23,33 @@ */ final class GraphQLResolver implements LoadableInterface { + /** + * Singleton instance. + * + * @since 2.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). + * + * @since 2.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 2.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * Return the loading priority. * @@ -30,10 +57,21 @@ final class GraphQLResolver implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 20; } + /** + * Whether this component should load. + * + * @since 2.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -41,7 +79,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_filter( 'graphql_resolve_field', array( $this, 'resolve_shortcodes' ), 10, 9 ); \add_action( 'graphql_register_types', array( $this, 'register_rendered_content' ) ); } diff --git a/community-listings/includes/Service/RestApiFilters.php b/community-listings/includes/Service/RestApiFilters.php index 44cddeb..d810215 100644 --- a/community-listings/includes/Service/RestApiFilters.php +++ b/community-listings/includes/Service/RestApiFilters.php @@ -12,7 +12,7 @@ namespace SilverAssist\CommunityListings\Service; -use SilverAssist\CommunityListings\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; use WP_REST_Request; /** @@ -24,6 +24,33 @@ */ final class RestApiFilters implements LoadableInterface { + /** + * Singleton instance. + * + * @since 2.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). + * + * @since 2.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 2.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * Return the loading priority. * @@ -31,10 +58,21 @@ final class RestApiFilters implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 20; } + /** + * Whether this component should load. + * + * @since 2.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -42,7 +80,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_filter( 'rest_community_query', array( $this, 'filter_query' ), 10, 2 ); \add_filter( 'rest_community_collection_params', array( $this, 'register_params' ) ); } diff --git a/contentful-tables/README.md b/contentful-tables/README.md index f3c06d0..f2253f9 100644 --- a/contentful-tables/README.md +++ b/contentful-tables/README.md @@ -26,9 +26,7 @@ contentful-tables/ │ │ └── SettingsPage.php # Admin settings page (priority 30) │ ├── Core/ │ │ ├── Activator.php # Activation/deactivation handlers -│ │ ├── Interfaces/ -│ │ │ └── LoadableInterface.php # Component contract -│ │ └── Plugin.php # Singleton bootstrap +│ │ └── Plugin.php # Singleton bootstrap (extends wp-plugin-kernel's AbstractPlugin) │ ├── Service/ │ │ ├── GraphQLResolver.php # WPGraphQL do_shortcode() (priority 20) │ │ ├── ShortcodeRegistrar.php # Shortcode registration (priority 20) diff --git a/contentful-tables/composer.json b/contentful-tables/composer.json index 36ff023..effdeed 100644 --- a/contentful-tables/composer.json +++ b/contentful-tables/composer.json @@ -5,6 +5,7 @@ "license": "proprietary", "require": { "php": ">=8.2", + "silverassist/wp-plugin-kernel": "^1.0", "silverassist/wp-settings-hub": "^1.1" }, "require-dev": { diff --git a/contentful-tables/includes/Admin/SettingsPage.php b/contentful-tables/includes/Admin/SettingsPage.php index 69548eb..5c8281f 100644 --- a/contentful-tables/includes/Admin/SettingsPage.php +++ b/contentful-tables/includes/Admin/SettingsPage.php @@ -12,8 +12,8 @@ namespace SilverAssist\ContentfulTables\Admin; -use SilverAssist\ContentfulTables\Core\Interfaces\LoadableInterface; use SilverAssist\ContentfulTables\Service\TableDataLoader; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; use SilverAssist\SettingsHub\SettingsHub; /** @@ -25,6 +25,15 @@ */ final class SettingsPage implements LoadableInterface { + /** + * Singleton instance. + * + * @since 4.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + /** * Shared data loader. * @@ -35,14 +44,26 @@ final class SettingsPage implements LoadableInterface { private TableDataLoader $data_loader; /** - * Constructor. + * Prevent direct instantiation — use instance(). Fetches the shared + * TableDataLoader singleton rather than receiving it via constructor + * injection, since kernel-managed components are instantiated with + * $class::instance() and no arguments. * - * @since 4.0.0 + * @since 4.3.0 + */ + private function __construct() { + $this->data_loader = TableDataLoader::instance(); + } + + /** + * Return the singleton instance. + * + * @since 4.3.0 * - * @param TableDataLoader $data_loader Shared data loader instance. + * @return self */ - public function __construct( TableDataLoader $data_loader ) { - $this->data_loader = $data_loader; + public static function instance(): self { + return self::$instance ??= new self(); } /** @@ -52,10 +73,21 @@ public function __construct( TableDataLoader $data_loader ) { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 30; } + /** + * Whether this component should load. + * + * @since 4.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -63,7 +95,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_action( 'init', array( $this, 'register_with_settings_hub' ) ); } diff --git a/contentful-tables/includes/Core/Interfaces/LoadableInterface.php b/contentful-tables/includes/Core/Interfaces/LoadableInterface.php deleted file mode 100644 index 71fac75..0000000 --- a/contentful-tables/includes/Core/Interfaces/LoadableInterface.php +++ /dev/null @@ -1,41 +0,0 @@ - */ - private static ?self $instance = null; - - /** - * Registered loadable components. - * - * @since 4.0.0 - * - * @var LoadableInterface[] - */ - private array $components = array(); - - /** - * Prevent direct instantiation. - * - * @since 4.0.0 - */ - private function __construct() {} - - /** - * Return the singleton instance. - * - * @since 4.0.0 - * - * @return self Plugin instance. - */ - public static function instance(): self { - if ( null === self::$instance ) { - self::$instance = new self(); - } - return self::$instance; - } - - /** - * Initialise all plugin components. - * - * @since 4.0.0 - * - * @return void - */ - public function init(): void { - // Build shared data loader. - $data_loader = new TableDataLoader(); - - // Register components by priority. - $this->components = array( - $data_loader, - new ShortcodeRegistrar( $data_loader ), - new GraphQLResolver(), - new SettingsPage( $data_loader ), + protected function get_components(): array { + return array( + TableDataLoader::class, + ShortcodeRegistrar::class, + GraphQLResolver::class, + SettingsPage::class, ); - - // Sort by priority ascending. - \usort( - $this->components, - static fn ( LoadableInterface $a, LoadableInterface $b ): int => $a->priority() <=> $b->priority() - ); - - // Register hooks. - foreach ( $this->components as $component ) { - $component->register(); - } } } diff --git a/contentful-tables/includes/Service/GraphQLResolver.php b/contentful-tables/includes/Service/GraphQLResolver.php index 0c3619f..e893d32 100644 --- a/contentful-tables/includes/Service/GraphQLResolver.php +++ b/contentful-tables/includes/Service/GraphQLResolver.php @@ -12,7 +12,7 @@ namespace SilverAssist\ContentfulTables\Service; -use SilverAssist\ContentfulTables\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; /** * Applies do_shortcode() to WPGraphQL content fields. @@ -23,6 +23,33 @@ */ final class GraphQLResolver implements LoadableInterface { + /** + * Singleton instance. + * + * @since 4.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). + * + * @since 4.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 4.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * GraphQL types whose content fields should be processed. * @@ -48,10 +75,21 @@ final class GraphQLResolver implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 20; } + /** + * Whether this component should load. + * + * @since 4.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -59,7 +97,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_filter( 'graphql_resolve_field', array( $this, 'resolve_shortcodes' ), 10, 9 ); \add_action( 'graphql_register_types', array( $this, 'register_rendered_content' ) ); } diff --git a/contentful-tables/includes/Service/ShortcodeRegistrar.php b/contentful-tables/includes/Service/ShortcodeRegistrar.php index a9d0345..bcffb58 100644 --- a/contentful-tables/includes/Service/ShortcodeRegistrar.php +++ b/contentful-tables/includes/Service/ShortcodeRegistrar.php @@ -12,12 +12,12 @@ namespace SilverAssist\ContentfulTables\Service; -use SilverAssist\ContentfulTables\Core\Interfaces\LoadableInterface; use SilverAssist\ContentfulTables\View\CardsRenderer; use SilverAssist\ContentfulTables\View\ChartRenderer; use SilverAssist\ContentfulTables\View\FormRenderer; use SilverAssist\ContentfulTables\View\TableRenderer; use SilverAssist\ContentfulTables\View\TocRenderer; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; /** * Registers all shortcodes and delegates rendering to view classes. @@ -28,6 +28,15 @@ */ final class ShortcodeRegistrar implements LoadableInterface { + /** + * Singleton instance. + * + * @since 4.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + /** * Shared data loader. * @@ -38,14 +47,26 @@ final class ShortcodeRegistrar implements LoadableInterface { private TableDataLoader $data_loader; /** - * Constructor. + * Prevent direct instantiation — use instance(). Fetches the shared + * TableDataLoader singleton rather than receiving it via constructor + * injection, since kernel-managed components are instantiated with + * $class::instance() and no arguments. * - * @since 4.0.0 + * @since 4.3.0 + */ + private function __construct() { + $this->data_loader = TableDataLoader::instance(); + } + + /** + * Return the singleton instance. + * + * @since 4.3.0 * - * @param TableDataLoader $data_loader Shared data loader instance. + * @return self */ - public function __construct( TableDataLoader $data_loader ) { - $this->data_loader = $data_loader; + public static function instance(): self { + return self::$instance ??= new self(); } /** @@ -55,10 +76,21 @@ public function __construct( TableDataLoader $data_loader ) { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 20; } + /** + * Whether this component should load. + * + * @since 4.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register all shortcodes and related hooks. * @@ -66,7 +98,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { // Register shortcodes with both underscore and hyphen variants. \add_shortcode( 'contentful_table', array( $this, 'render_table_shortcode' ) ); \add_shortcode( 'contentful-table', array( $this, 'render_table_shortcode' ) ); diff --git a/contentful-tables/includes/Service/TableDataLoader.php b/contentful-tables/includes/Service/TableDataLoader.php index c9318be..27e0de8 100644 --- a/contentful-tables/includes/Service/TableDataLoader.php +++ b/contentful-tables/includes/Service/TableDataLoader.php @@ -12,8 +12,8 @@ namespace SilverAssist\ContentfulTables\Service; -use SilverAssist\ContentfulTables\Core\Interfaces\LoadableInterface; use SilverAssist\ContentfulTables\Utils\CsvParser; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; /** * Loads table, chart, and card data from files, post meta, and database. @@ -24,6 +24,37 @@ */ final class TableDataLoader implements LoadableInterface { + /** + * Singleton instance. + * + * @since 4.3.0 + * + * @var self|null + */ + private static ?self $instance = null; + + /** + * Prevent direct instantiation — use instance(). Other components that + * need the shared loaded data call TableDataLoader::instance() rather + * than receiving it via constructor injection, so this class can be a + * kernel-managed LoadableInterface component (AbstractPlugin's + * load_components() calls $class::instance() with no arguments). + * + * @since 4.3.0 + */ + private function __construct() {} + + /** + * Return the singleton instance. + * + * @since 4.3.0 + * + * @return self + */ + public static function instance(): self { + return self::$instance ??= new self(); + } + /** * Loaded table data keyed by entry ID. * @@ -58,10 +89,21 @@ final class TableDataLoader implements LoadableInterface { * * @return int Loading priority. */ - public function priority(): int { + public function get_priority(): int { return 10; } + /** + * Whether this component should load. + * + * @since 4.3.0 + * + * @return bool + */ + public function should_load(): bool { + return true; + } + /** * Register WordPress hooks. * @@ -69,7 +111,7 @@ public function priority(): int { * * @return void */ - public function register(): void { + public function init(): void { \add_action( 'init', array( $this, 'load_all_data' ) ); } diff --git a/graphql-shortcode-support/README.md b/graphql-shortcode-support/README.md index 51d021f..df817a8 100644 --- a/graphql-shortcode-support/README.md +++ b/graphql-shortcode-support/README.md @@ -116,9 +116,7 @@ graphql-shortcode-support/ ├── README.md └── includes/ ├── Core/ - │ ├── Interfaces/ - │ │ └── LoadableInterface.php # Component contract - │ ├── Plugin.php # Singleton bootstrap + │ ├── Plugin.php # Singleton bootstrap (extends wp-plugin-kernel's AbstractPlugin) │ └── Activator.php # Activation/deactivation ├── Service/ │ └── GraphQLShortcodeResolver.php # Core shortcode resolver diff --git a/graphql-shortcode-support/composer.json b/graphql-shortcode-support/composer.json index 8d13397..926d192 100644 --- a/graphql-shortcode-support/composer.json +++ b/graphql-shortcode-support/composer.json @@ -12,6 +12,7 @@ "minimum-stability": "stable", "require": { "php": ">=8.2", + "silverassist/wp-plugin-kernel": "^1.0", "silverassist/wp-settings-hub": "^1.1" }, "require-dev": { diff --git a/graphql-shortcode-support/includes/Admin/SettingsPage.php b/graphql-shortcode-support/includes/Admin/SettingsPage.php index 9a41fe7..85b5e09 100644 --- a/graphql-shortcode-support/includes/Admin/SettingsPage.php +++ b/graphql-shortcode-support/includes/Admin/SettingsPage.php @@ -11,7 +11,7 @@ namespace SilverAssist\GraphQLShortcodeSupport\Admin; -use SilverAssist\GraphQLShortcodeSupport\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; use SilverAssist\SettingsHub\SettingsHub; // Prevent direct access. diff --git a/graphql-shortcode-support/includes/Core/Interfaces/LoadableInterface.php b/graphql-shortcode-support/includes/Core/Interfaces/LoadableInterface.php deleted file mode 100644 index 3dee3ff..0000000 --- a/graphql-shortcode-support/includes/Core/Interfaces/LoadableInterface.php +++ /dev/null @@ -1,44 +0,0 @@ -initialized ) { - return; - } - - $this->load_components(); - - $this->initialized = true; - } - - /** - * Get loading priority. - * - * @return int - */ - public function get_priority(): int { - return 10; - } - - /** - * Should this component load? - * - * @return bool - */ - public function should_load(): bool { - return true; - } +class Plugin extends AbstractPlugin { /** * Get components to load. * - * @return array> + * @since 1.1.0 + * + * @return array */ - private function get_components(): array { + protected function get_components(): array { return [ - \SilverAssist\GraphQLShortcodeSupport\Service\GraphQLShortcodeResolver::class, - \SilverAssist\GraphQLShortcodeSupport\Admin\SettingsPage::class, + GraphQLShortcodeResolver::class, + SettingsPage::class, ]; } - - /** - * Load all components by priority. - * - * @return void - */ - private function load_components(): void { - $components = []; - - foreach ( $this->get_components() as $class ) { - if ( method_exists( $class, 'instance' ) ) { - $instance = $class::instance(); - if ( $instance->should_load() ) { - $components[] = $instance; - } - } - } - - // Sort by priority (lower first). - usort( $components, fn( $a, $b ) => $a->get_priority() <=> $b->get_priority() ); - - // Initialize all. - foreach ( $components as $component ) { - $component->init(); - } - } } diff --git a/graphql-shortcode-support/includes/Service/GraphQLShortcodeResolver.php b/graphql-shortcode-support/includes/Service/GraphQLShortcodeResolver.php index 6b13e7c..a2a410d 100644 --- a/graphql-shortcode-support/includes/Service/GraphQLShortcodeResolver.php +++ b/graphql-shortcode-support/includes/Service/GraphQLShortcodeResolver.php @@ -12,7 +12,7 @@ namespace SilverAssist\GraphQLShortcodeSupport\Service; -use SilverAssist\GraphQLShortcodeSupport\Core\Interfaces\LoadableInterface; +use SilverAssist\PluginKernel\Interfaces\LoadableInterface; // Prevent direct access. \defined( 'ABSPATH' ) || exit; From 4e91a8c7c68aeb2fac3cc4c629c510ce6fb4aca0 Mon Sep 17 00:00:00 2001 From: Miguel Colmenares Date: Mon, 17 Aug 2026 21:58:56 -0500 Subject: [PATCH 2/2] chore: Bump GitHub Actions to Node 24 releases Clears the 'Node.js 20 is deprecated' warnings now surfacing on every CI job step (actions/checkout, actions/cache, actions/upload-artifact were still pinned to their Node 20 majors). --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 2 +- CHANGELOG.md | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38e204a..93feffd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: php-version: [8.2, 8.3, 8.4] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: done - name: Cache Composer dependencies - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | **/vendor @@ -56,7 +56,7 @@ jobs: run: make build - name: Upload build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: plugin-packages-php${{ matrix.php-version }} path: dist/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9652dcf..dfd4312 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index b9fba20..f980c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Individual plugin versions are tracked separately in their respective plugin hea ## [Unreleased] ### Changed +- Bumped `actions/checkout` (v4→v7), `actions/cache` (v4→v6), and `actions/upload-artifact` (v4→v7) in both workflows to their Node 24 releases, clearing the "Node.js 20 is deprecated" warnings GitHub Actions now emits on every job step - Adopted `silverassist/wp-plugin-kernel` in all three sub-plugins, replacing each one's own duplicated `LoadableInterface`/`Plugin` bootstrap with the shared `AbstractPlugin` — components now implement `get_priority()`/`should_load()`/`init()` (the kernel's contract) instead of the old local `priority()`/`register()`. In `contentful-tables`, `TableDataLoader` became a proper singleton (`ShortcodeRegistrar`/`SettingsPage` now fetch it via `TableDataLoader::instance()` instead of constructor injection), since kernel-managed components are instantiated with no arguments. ### Added