From 62d504ce24c39f5791554e51fb61b908ac1998c4 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 14 Aug 2025 11:32:42 +0100 Subject: [PATCH 01/14] Add wiki docs --- docs/Asset-Loading.md | 36 +++++++++++++++++++++++++++ docs/Autoloading.md | 29 ++++++++++++++++++++++ docs/Home.md | 1 + docs/Post-Types.md | 57 +++++++++++++++++++++++++++++++++++++++++++ docs/Taxonomies.md | 34 ++++++++++++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 docs/Asset-Loading.md create mode 100644 docs/Autoloading.md create mode 100644 docs/Home.md create mode 100644 docs/Post-Types.md create mode 100644 docs/Taxonomies.md diff --git a/docs/Asset-Loading.md b/docs/Asset-Loading.md new file mode 100644 index 0000000..6f0ecd8 --- /dev/null +++ b/docs/Asset-Loading.md @@ -0,0 +1,36 @@ +**Overview** +WP Framework now manages asset loading using the `GetAssetInfo` trait. Any class that registers assets must use this trait to retrieve asset details—such as dependencies and version—from a corresponding `.asset.php` file. This system also allows a fallback version when the asset file is unavailable. + +**Setup** +Within your asset-registering class, include the `GetAssetInfo` trait and initialize the asset variables by calling the `setup_asset_vars` method. You must pass in the distribution directory path and a fallback version number. For example: + +```php +$this->setup_asset_vars( + dist_path: TENUP_PLUGIN_PATH . 'dist/', + fallback_version: TENUP_PLUGIN_VERSION +); +``` + +This sets the base path for assets and ensures that a fallback version is used when the corresponding `.asset.php` file cannot be found. + +## Enqueuing Assets +Once asset variables are set up, you can enqueue your scripts by using the `get_asset_info()` method provided by the trait. This method dynamically retrieves the appropriate asset information (dependencies and version) from the `.asset.php` file. For example, to enqueue an admin script: + +```php +wp_enqueue_script( + 'tenup_plugin_admin', + TENUP_PLUGIN_URL . 'dist/js/admin.js', + $this->get_asset_info( 'admin', 'dependencies' ), + $this->get_asset_info( 'admin', 'version' ), + true +); +``` + +* **Dependencies:** `$this->get_asset_info( 'admin', 'dependencies' )` retrieves an array of script dependencies. +* **Version:** `$this->get_asset_info( 'admin', 'version' )` retrieves the asset version for cache busting. + +## Key Points + +* **Trait Usage:** Use the `GetAssetInfo` trait in any class that registers assets. +* **Asset Setup:** Call `setup_asset_vars()` with the correct dist directory and fallback version. +* **Dynamic Retrieval:** Enqueue assets using `get_asset_info()` to dynamically load dependencies and version info from the `.asset.php` file. \ No newline at end of file diff --git a/docs/Autoloading.md b/docs/Autoloading.md new file mode 100644 index 0000000..5e06f16 --- /dev/null +++ b/docs/Autoloading.md @@ -0,0 +1,29 @@ +## Overview +WP Framework follows the PSR‑4 autoloading standard. It builds on the module autoloader formerly used in WP‑Scaffold. Instead of extending a base `Module` class, you now implement the `ModuleInterface` and use the provided `Module` trait. + +## Example + +```php +namespace YourNamespace; + +use TenupFramework\ModuleInterface; +use TenupFramework\Module; + +class YourModule implements ModuleInterface { + use Module; + + public function can_register(): bool { + return true; + } + + public function register(): void { + // Register hooks and filters here. + } +} +``` + +## Key Points + +* Follow PSR‑4 naming and directory conventions. +* Use the `Module` trait for a basic implementation of `ModuleInterface`. +* Keep module registration lean—only attach what’s necessary. \ No newline at end of file diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 0000000..c599631 --- /dev/null +++ b/docs/Home.md @@ -0,0 +1 @@ +Welcome to the wp-framework wiki! diff --git a/docs/Post-Types.md b/docs/Post-Types.md new file mode 100644 index 0000000..8be3387 --- /dev/null +++ b/docs/Post-Types.md @@ -0,0 +1,57 @@ +## Overview +WP Framework provides abstract classes to help define both custom and core post types. This minimizes boilerplate while ensuring consistency. + +## Custom Post Types Example + +```php +namespace TenUpPlugin\Posts; + +use TenupFramework\PostTypes\AbstractPostType; + +class Demo extends AbstractPostType { + + public function get_name() { + return 'tenup-demo'; + } + + public function get_singular_label() { + return esc_html__( 'Demo', 'tenup-plugin' ); + } + + public function get_plural_label() { + return esc_html__( 'Demos', 'tenup-plugin' ); + } + + public function get_menu_icon() { + return 'dashicons-chart-pie'; + } +} +``` + +## Core Post Types Example +```php +namespace TenUpPlugin\Posts; + +use TenupFramework\PostTypes\AbstractCorePostType; + +class Post extends AbstractCorePostType { + + public function get_name() { + return 'post'; + } + + public function get_supported_taxonomies() { + return []; + } + + public function after_register() { + // No additional functionality. + } +} +``` + +## Key Points + +* Extend `AbstractPostType` for new content types. +* Extend `AbstractCorePostType` to modify or extend built-in types. +* Use translation functions for labels. \ No newline at end of file diff --git a/docs/Taxonomies.md b/docs/Taxonomies.md new file mode 100644 index 0000000..0618750 --- /dev/null +++ b/docs/Taxonomies.md @@ -0,0 +1,34 @@ +## Overview +WP Framework simplifies taxonomy registration by offering an abstract class to encapsulate the required functionality. + +## Example +```php +namespace TenUpPlugin\Taxonomies; + +use TenupFramework\Taxonomies\AbstractTaxonomy; + +class Demo extends AbstractTaxonomy { + + public function get_name() { + return 'tenup-demo-category'; + } + + public function get_singular_label() { + return esc_html__( 'Category', 'tenup-plugin' ); + } + + public function get_plural_label() { + return esc_html__( 'Categories', 'tenup-plugin' ); + } + + public function get_post_types() { + return [ 'tenup-demo' ]; + } +} +``` + +## Key Points + +* Define both singular and plural labels. +* Associate the taxonomy with the relevant post type(s). +* Keep taxonomy definitions modular for ease of maintenance. \ No newline at end of file From 446b73966823d1047210761c7a78ce7e00decea7 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 14 Aug 2025 13:08:50 +0100 Subject: [PATCH 02/14] =?UTF-8?q?Remove=20get=5Fpost=5Ftypes=20from=20read?= =?UTF-8?q?me=20as=20it=E2=80=99s=20confusing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 8c4e4fe..2d5b789 100644 --- a/README.md +++ b/README.md @@ -120,10 +120,6 @@ class Demo extends AbstractTaxonomy { public function get_plural_label() { return esc_html__( 'Categories', 'tenup-plugin' ); } - - public function get_post_types() { - return [ 'tenup-demo' ]; - } } ``` From 3e85b2c72c6a9c1e6d5f1cc5383c5204a08970e1 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 14 Aug 2025 13:22:20 +0100 Subject: [PATCH 03/14] Update docs --- docs/Asset-Loading.md | 114 ++++++++++++++++++++++----- docs/Autoloading.md | 112 +++++++++++++++++++++++--- docs/Home.md | 1 - docs/Modules-and-Initialization.md | 100 ++++++++++++++++++++++++ docs/Post-Types.md | 121 ++++++++++++++++++++++------- docs/README.md | 32 ++++++++ docs/Taxonomies.md | 117 +++++++++++++++++++++++----- 7 files changed, 521 insertions(+), 76 deletions(-) delete mode 100644 docs/Home.md create mode 100644 docs/Modules-and-Initialization.md create mode 100644 docs/README.md diff --git a/docs/Asset-Loading.md b/docs/Asset-Loading.md index 6f0ecd8..a37b67b 100644 --- a/docs/Asset-Loading.md +++ b/docs/Asset-Loading.md @@ -1,36 +1,114 @@ -**Overview** -WP Framework now manages asset loading using the `GetAssetInfo` trait. Any class that registers assets must use this trait to retrieve asset details—such as dependencies and version—from a corresponding `.asset.php` file. This system also allows a fallback version when the asset file is unavailable. +# Asset Loading -**Setup** -Within your asset-registering class, include the `GetAssetInfo` trait and initialize the asset variables by calling the `setup_asset_vars` method. You must pass in the distribution directory path and a fallback version number. For example: +## Overview +Use the `TenupFramework\Assets\GetAssetInfo` trait to read dependency and version metadata generated by your build (the `.asset.php` sidecar files). The trait looks for files in: +- `dist/js/{slug}.asset.php` +- `dist/css/{slug}.asset.php` +- `dist/blocks/{slug}.asset.php` + +If no sidecar is found, it falls back to the version you provide and returns an empty dependency list, allowing safe enqueues during development or when assets are missing. + +## Setup +Prerequisites: define common constants and a dist/ directory for built assets. ```php -$this->setup_asset_vars( - dist_path: TENUP_PLUGIN_PATH . 'dist/', - fallback_version: TENUP_PLUGIN_VERSION -); +// Plugin main file or bootstrap +define( 'YOUR_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); +define( 'YOUR_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +define( 'YOUR_PLUGIN_INC', YOUR_PLUGIN_PATH . 'inc/' ); +define( 'YOUR_PLUGIN_VERSION', '1.0.0' ); +``` + +Typical dist/ layout (your build may vary): +``` +dist/ +├─ js/ +│ ├─ admin.js +│ └─ admin.asset.php +├─ css/ +│ └─ admin.css +└─ blocks/ + ├─ my-block.js + └─ my-block.asset.php ``` -This sets the base path for assets and ensures that a fallback version is used when the corresponding `.asset.php` file cannot be found. +Include the trait in any class that enqueues assets and set the dist path and fallback version once (e.g., in the constructor or on first use): + +```php +use TenupFramework\Assets\GetAssetInfo; + +class AssetsModule { + use GetAssetInfo; + + public function __construct() { + $this->setup_asset_vars( + dist_path: YOUR_PLUGIN_PATH . 'dist/', + fallback_version: YOUR_PLUGIN_VERSION + ); + } +} +``` -## Enqueuing Assets -Once asset variables are set up, you can enqueue your scripts by using the `get_asset_info()` method provided by the trait. This method dynamically retrieves the appropriate asset information (dependencies and version) from the `.asset.php` file. For example, to enqueue an admin script: +Notes: +- If you call `get_asset_info()` before `setup_asset_vars()`, a RuntimeException will be thrown. +- During local development, sidecar files (e.g., `admin.asset.php`) may be missing. The trait safely falls back to your provided fallback_version and an empty dependency list, so your enqueues still work. +- If your build produces multiple variants (e.g., `admin.js` vs `admin.min.js`), you can conditionally enqueue based on `SCRIPT_DEBUG` or `wp_get_environment_type() === 'development'`. +## Enqueuing scripts ```php wp_enqueue_script( 'tenup_plugin_admin', - TENUP_PLUGIN_URL . 'dist/js/admin.js', + YOUR_PLUGIN_URL . 'dist/js/admin.js', $this->get_asset_info( 'admin', 'dependencies' ), $this->get_asset_info( 'admin', 'version' ), true ); ``` +- dependencies: array of script handles from `admin.asset.php` +- version: string used for cache busting -* **Dependencies:** `$this->get_asset_info( 'admin', 'dependencies' )` retrieves an array of script dependencies. -* **Version:** `$this->get_asset_info( 'admin', 'version' )` retrieves the asset version for cache busting. +## Enqueuing styles +```php +wp_enqueue_style( + 'tenup_plugin_admin', + YOUR_PLUGIN_URL . 'dist/css/admin.css', + [], // CSS dependencies are uncommon; pass [] unless needed + $this->get_asset_info( 'admin', 'version' ) +); +``` + +## Working with blocks +If you build blocks, pass the block slug used by your build tool: +```php +$deps = $this->get_asset_info( 'my-block', 'dependencies' ); +$ver = $this->get_asset_info( 'my-block', 'version' ); +$handle = 'tenup_my_block'; + +wp_register_script( $handle, YOUR_PLUGIN_URL . 'dist/blocks/my-block.js', $deps, $ver, true ); +``` +The trait automatically checks `dist/blocks/my-block.asset.php` if present. + +## Error handling and fallbacks +```php +try { + $deps = $this->get_asset_info( 'frontend', 'dependencies' ); + $ver = $this->get_asset_info( 'frontend', 'version' ); +} catch ( \RuntimeException $e ) { + // setup_asset_vars() was not called — fall back to safe defaults + $deps = []; + $ver = YOUR_PLUGIN_VERSION; +} +``` -## Key Points +## Best practices +- Always call `setup_asset_vars()` early (constructor or on first enqueue). +- Keep your dist path stable across environments (use constants for PATH and URL). +- Use the version from `.asset.php` for reliable cache busting in production. +- For admin-only assets, enqueue on `admin_enqueue_scripts`; for frontend, use `wp_enqueue_scripts`. -* **Trait Usage:** Use the `GetAssetInfo` trait in any class that registers assets. -* **Asset Setup:** Call `setup_asset_vars()` with the correct dist directory and fallback version. -* **Dynamic Retrieval:** Enqueue assets using `get_asset_info()` to dynamically load dependencies and version info from the `.asset.php` file. \ No newline at end of file +## See also +- [Docs Home](README.md) +- [Autoloading and Modules](Autoloading.md) +- [Modules and Initialization](Modules-and-Initialization.md) +- [Post Types](Post-Types.md) +- [Taxonomies](Taxonomies.md) diff --git a/docs/Autoloading.md b/docs/Autoloading.md index 5e06f16..af8b51c 100644 --- a/docs/Autoloading.md +++ b/docs/Autoloading.md @@ -1,29 +1,121 @@ +# Autoloading and Modules + ## Overview -WP Framework follows the PSR‑4 autoloading standard. It builds on the module autoloader formerly used in WP‑Scaffold. Instead of extending a base `Module` class, you now implement the `ModuleInterface` and use the provided `Module` trait. +WP Framework follows PSR-4 autoloading and discovers your classes at runtime to initialize Modules. Instead of extending a base class, you implement ModuleInterface and use the Module trait to participate in the lifecycle. + +## Composer PSR-4 setup +Add your project namespace and source directory in composer.json: + +```json +{ + "autoload": { + "psr-4": { + "YourVendor\\YourPlugin\\": "inc/" + } + } +} +``` + +Run `composer dump-autoload` after changes. -## Example +## Recommended plugin structure & bootstrap +A simple plugin layout that works well with the framework: + +``` +my-plugin/ +├─ my-plugin.php // main plugin file +├─ composer.json +├─ inc/ // PHP source (PSR-4 autoloaded) +│ ├─ Features/ +│ ├─ Posts/ +│ └─ Taxonomies/ +├─ dist/ // built assets from your toolchain +│ ├─ js/ +│ │ ├─ admin.js +│ │ └─ admin.asset.php +│ ├─ css/ +│ │ └─ admin.css +│ └─ blocks/ +│ ├─ my-block.js +│ └─ my-block.asset.php +└─ readme.txt +``` + +Define a few useful constants in your main plugin file (or a bootstrap class), then initialize modules: ```php -namespace YourNamespace; +// Plugin main file or bootstrap +define( 'YOUR_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); +define( 'YOUR_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +define( 'YOUR_PLUGIN_INC', YOUR_PLUGIN_PATH . 'inc/' ); +define( 'YOUR_PLUGIN_VERSION', '1.0.0' ); + +use TenupFramework\ModuleInitialization; +ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC ); +``` + +## Initialization +Call the framework’s initializer with the directory where your classes live (e.g., inc or src): + +```php +use TenupFramework\ModuleInitialization; + +ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC ); +``` + +- Classes must be instantiable and implement `TenupFramework\ModuleInterface`. +- The initializer sorts modules by `load_order()` (defaults to `10` via the `Module` trait) and then calls `register()` only if `can_register()` returns `true`. +- A hook fires before registration: `tenup_framework_module_init__{slug}`, where slug is a sanitized class FQN. + +### Verify discovery and environment behavior +In development, you can verify discovered/initialized modules: + +```php +add_action( 'plugins_loaded', function () { + $mods = TenupFramework\ModuleInitialization::instance()->get_all_classes(); + // For local/dev only: + // error_log( print_r( array_keys( $mods ), true ) ); +} ); +``` + +Environment caching: +- Discovery results are cached only in production and staging environments (per `wp_get_environment_type()`). +- Cache is stored under the directory you pass to `init_classes()`, in a "class-loader-cache" folder (e.g., `YOUR_PLUGIN_INC . 'class-loader-cache'`). +- To refresh: delete that folder; it will be rebuilt automatically. +- Caching is skipped entirely when the constant `VIP_GO_APP_ENVIRONMENT` is defined. + +## Defining a Module +```php +namespace YourVendor\YourPlugin\Features; use TenupFramework\ModuleInterface; use TenupFramework\Module; class YourModule implements ModuleInterface { - use Module; + use Module; // provides default load_order() = 10 public function can_register(): bool { - return true; + // Only run on frontend, for example + return ! is_admin(); } public function register(): void { - // Register hooks and filters here. + add_action( 'init', function () { + // Add hooks/filters here + } ); } } ``` -## Key Points +## Best practices +- Keep Modules small and focused; compose behavior via multiple classes. +- Use `can_register()` to gate context-specific behavior (admin vs. frontend, REST, multisite, feature flags). +- Prefer dependency injection via constructor where practical; avoid doing heavy work before `register()`. + -* Follow PSR‑4 naming and directory conventions. -* Use the `Module` trait for a basic implementation of `ModuleInterface`. -* Keep module registration lean—only attach what’s necessary. \ No newline at end of file +## See also +- [Docs Home](README.md) +- [Modules and Initialization](Modules-and-Initialization.md) +- [Post Types](Post-Types.md) +- [Taxonomies](Taxonomies.md) +- [Asset Loading](Asset-Loading.md) diff --git a/docs/Home.md b/docs/Home.md deleted file mode 100644 index c599631..0000000 --- a/docs/Home.md +++ /dev/null @@ -1 +0,0 @@ -Welcome to the wp-framework wiki! diff --git a/docs/Modules-and-Initialization.md b/docs/Modules-and-Initialization.md new file mode 100644 index 0000000..490e85e --- /dev/null +++ b/docs/Modules-and-Initialization.md @@ -0,0 +1,100 @@ +# Modules and Initialization + +## Overview +The WP Framework organizes functionality into small Modules. A Module is any class that implements TenupFramework\ModuleInterface and typically uses the TenupFramework\Module trait. Modules are discovered at runtime and initialized in a defined order. + +Key interfaces and utilities: +- `TenupFramework\ModuleInterface`: declares `load_order()`, `can_register()`, `register()`. +- `TenupFramework\Module` trait: provides a default `load_order()` of `10` and leaves `can_register()` and `register()` abstract for your class to implement. +- `TenupFramework\ModuleInitialization`: discovers, orders, and initializes your Modules. + +## Bootstrapping +Call the initializer at plugin or theme bootstrap, pointing it at the directory containing your namespaced classes (e.g., `inc/` or `src/`): + +```php +use TenupFramework\ModuleInitialization; + +ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC ); +``` + +`YOUR_PLUGIN_INC` (or your equivalent constant/path) should resolve to an existing directory. If it does not exist, a RuntimeException will be thrown. + +## How discovery and initialization work +ModuleInitialization performs the following steps: +1. Validate the directory exists; otherwise throw a RuntimeException. +2. Discover class names within the directory using spatie/structure-discoverer. + - In production and staging environments (wp_get_environment_type), results are cached for performance using a file-based cache. + - Caching is skipped entirely when the constant `VIP_GO_APP_ENVIRONMENT` is defined. +3. Reflect on each discovered class and skip any that: + - are not instantiable, + - do not implement `TenupFramework\ModuleInterface`. +4. Instantiate the class. +5. Fire an action before registration for each module: `tenup_framework_module_init__{slug}` + - `slug` is the sanitized class FQN (backslashes replaced with dashes, then passed through `sanitize_title`). +6. Sort modules by `load_order()` (lower numbers first) and iterate in order. +7. For each module, call `register()` only if `can_register()` returns true. +8. Store initialized modules for later retrieval. + +Environment cache behavior +- Where cache lives: under the directory you pass to `init_classes()`, in a `class-loader-cache` folder (e.g., `YOUR_PLUGIN_INC . 'class-loader-cache'`). +- When it’s used: only in `production` and `staging` environment types (`wp_get_environment_type()`). +- How to clear: delete the `class-loader-cache` folder; it will be rebuilt on next discovery. +- How to disable in development: use `development` or `local` environment types, or define `VIP_GO_APP_ENVIRONMENT` to skip the cache. + +Hooks +- Action: `tenup_framework_module_init__{slug}` — fires before each module’s `register()` runs. + - Parameters: the module instance. + - Example: + ```php + add_action( 'tenup_framework_module_init__yourvendor-yourplugin-features-frontendtweaks', function ( $module ) { + // Inspect or adjust before register() + } ); + ``` + +Load order dependencies example +- If Module B depends on Module A: + ```php + class ModuleA implements ModuleInterface { use Module; public function load_order(): int { return 5; } } + class ModuleB implements ModuleInterface { use Module; public function load_order(): int { return 10; } } + ``` + Lower numbers run first. Taxonomies typically use 9 so post types (default 10) can associate afterward. + +Utilities: +- `ModuleInitialization::get_module( $classFqn )` retrieves an initialized module instance by its fully qualified class name. +- `ModuleInitialization::instance()->get_all_classes()` returns all initialized module instances keyed by slug. + +## Module lifecycle in your code +Your Module should be lightweight at construction time. Use the following methods effectively: +- `load_order(): int` — controls initialization order (default = 10 via Module trait). Override to run earlier/later. For example, taxonomy modules may run at 9 so they are available before post types. +- `can_register(): bool` — return true only when the module should register hooks in the current context (e.g., only in admin, only on frontend, only if a feature flag is enabled). +- `register(): void` — attach your WordPress hooks/filters and perform setup here. + +### Example +```php +namespace YourVendor\YourPlugin\Features; + +use TenupFramework\ModuleInterface; +use TenupFramework\Module; + +class FrontendTweaks implements ModuleInterface { + use Module; // default load_order() = 10 + + public function can_register(): bool { + return ! is_admin(); // only on frontend + } + + public function register(): void { + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue' ] ); + } + + public function enqueue(): void { + // ... enqueue assets here ... + } +} +``` + +## Troubleshooting +- Directory is required — If `init_classes()` is called with an empty or non-existent directory, a RuntimeException is thrown. +- Class not initialized — Ensure the class is instantiable and implements `TenupFramework\ModuleInterface`. +- Order of initialization — If you have inter-module dependencies, adjust `load_order()` to ensure prerequisites are registered first. +- Observability — Use the `tenup_framework_module_init__{slug}` action to inspect or modify module instances before they register. diff --git a/docs/Post-Types.md b/docs/Post-Types.md index 8be3387..a1b3502 100644 --- a/docs/Post-Types.md +++ b/docs/Post-Types.md @@ -1,57 +1,120 @@ +# Post Types + ## Overview -WP Framework provides abstract classes to help define both custom and core post types. This minimizes boilerplate while ensuring consistency. +WP Framework provides base classes for implementing both custom and core post types with minimal boilerplate and a consistent API. Post type classes are also Modules, so they participate in the standard lifecycle (load_order → can_register → register). -## Custom Post Types Example +- Custom post types: extend `TenupFramework\PostTypes\AbstractPostType` +- Core post types: extend `TenupFramework\PostTypes\AbstractCorePostType` +## Quick start (custom post type) ```php namespace TenUpPlugin\Posts; use TenupFramework\PostTypes\AbstractPostType; class Demo extends AbstractPostType { - - public function get_name() { - return 'tenup-demo'; - } - - public function get_singular_label() { - return esc_html__( 'Demo', 'tenup-plugin' ); - } - - public function get_plural_label() { - return esc_html__( 'Demos', 'tenup-plugin' ); + public function get_name() { return 'tenup-demo'; } + public function get_singular_label() { return esc_html__( 'Demo', 'tenup-plugin' ); } + public function get_plural_label() { return esc_html__( 'Demos', 'tenup-plugin' ); } + public function get_menu_icon() { return 'dashicons-chart-pie'; } + + // Optional: run only in specific contexts + public function can_register() { return true; } + + // Optional: fine-tune options beyond defaults + public function get_options() { + $options = parent::get_options(); + $options['supports'] = [ 'title', 'editor', 'thumbnail', 'excerpt' ]; + $options['rewrite'] = [ 'slug' => 'demos', 'with_front' => false ]; + $options['has_archive'] = true; + return $options; } - public function get_menu_icon() { - return 'dashicons-chart-pie'; - } + // Declare related taxonomies (registered separately via taxonomy classes) + public function get_supported_taxonomies() { return [ 'tenup-demo-category' ]; } } ``` -## Core Post Types Example +## Working with core post types +If you need to attach taxonomies or behaviors to built-in types (post, page, attachment), extend `AbstractCorePostType`: ```php namespace TenUpPlugin\Posts; use TenupFramework\PostTypes\AbstractCorePostType; class Post extends AbstractCorePostType { - - public function get_name() { - return 'post'; - } + public function get_name() { return 'post'; } public function get_supported_taxonomies() { - return []; + return [ 'tenup-demo-category' ]; } public function after_register() { - // No additional functionality. + // Add metaboxes, filters, etc. } } ``` - -## Key Points - -* Extend `AbstractPostType` for new content types. -* Extend `AbstractCorePostType` to modify or extend built-in types. -* Use translation functions for labels. \ No newline at end of file +Notes: +- Core post types are already registered by WordPress. `AbstractCorePostType::register()` does not call `register_post_type()`; it only registers taxonomies (via `get_supported_taxonomies()`) and then calls `after_register()`. +- `can_register()` returns `true` by default in `AbstractCorePostType`. + +## Taxonomy association pattern and migration +- Associations are declared in Post Type classes via `get_supported_taxonomies();` taxonomy classes should not declare post types with `get_post_types()`. +- Rationale: centralizes relationships in the object type; avoids duplication and fragile coupling; taxonomy registration stays independent of associations. +- Migration: If you previously returned post types from a taxonomy class via `get_post_types()`, remove that method and add the taxonomy slug to each relevant post type’s `get_supported_taxonomies()`. + +## API reference (AbstractPostType) +Required methods you must implement: +- `get_name()`: string — The post type slug, e.g., tenup-demo. +- `get_singular_label()`: string — Translated singular label. +- `get_plural_label()`: string — Translated plural label. +- `get_menu_icon()`: string — A dashicons class, base64 SVG, or 'none'. + +Common optional methods to override: +- `get_menu_position()`: ?int — Position in admin menu; null by default. +- `is_hierarchical()`: bool — Defaults to false. +- `get_editor_supports()`: array — Defaults to [title, editor, author, thumbnail, excerpt, revisions]. +- `get_options()`: array — Returns options passed to `register_post_type()`. See below for keys and defaults. +- `get_supported_taxonomies()`: string[] — Array of taxonomy slugs to associate via `register_taxonomy_for_object_type()`. +- `after_register()`: void — Called after the type is registered and taxonomies associated. +- `can_register()`: bool — Implement to control when this module should run; e.g., limit to admin. + +Registration methods (already implemented): +- `register()`: calls `register_post_type()`, `register_taxonomies()`, then `after_register()`; returns `true`. +- `register_post_type()`: wraps WordPress `register_post_type( $this->get_name(), $this->get_options() )`. +- `register_taxonomies()`: iterates get_supported_taxonomies() and calls `register_taxonomy_for_object_type()`. + +## Options (get_options) overview +AbstractPostType provides sensible defaults and merges in: +- `labels`: from `get_labels()` +- `public`: true +- `has_archive`: true +- `show_ui`: true +- `show_in_menu`: true +- `show_in_nav_menus`: false +- `show_in_rest`: true +- `supports`: `get_editor_supports()` +- `menu_icon`: `get_menu_icon()` +- `hierarchical`: `is_hierarchical()` +- `menu_position`: included if `get_menu_position()` returns a number + +You can override get_options() in your class to set any supported core args, including: +- `rewrite` (array|bool): slug, with_front, feeds, pages, ep_mask +- `capability_type` (string|array), capabilities (array), map_meta_cap (bool) +- `taxonomies` (array) — Note: prefer `get_supported_taxonomies()` to associate after registration +- `has_archive` (bool|string) +- `show_in_rest` (bool), rest_base, rest_namespace, rest_controller_class +- `register_meta_box_cb` (callable) +- `template` (array), template_lock (string|false) + +## Labels +`AbstractPostType::get_labels()` builds a comprehensive labels array using the singular/plural labels you provide. Ensure your `get_*_label()` methods return translated strings using your project text domain. + +## Tips +- Flush rewrite rules on activation only (not within `register()`). +- Keep post type classes focused on registration; put custom business logic in separate modules/services. +- Link taxonomies via `get_supported_taxonomies()` for clarity; implement taxonomy classes separately (see Taxonomies.md). + +## See also +- [Taxonomies](Taxonomies.md) +- [Modules and Initialization](Modules-and-Initialization.md) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8137d4e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,32 @@ +# WP Framework Documentation + +What is WP Framework? +WP Framework is a lightweight set of building blocks for structuring WordPress plugins and themes around small, composable Modules. It provides: +- Module discovery and initialization (with a predictable lifecycle) +- Base classes for custom and core post types +- Base class for taxonomies +- Asset helpers that read modern build sidecars (`.asset.php`) + +Who is this for? +- External engineers — Start here: follow the Quick Start and then read Autoloading and Modules → Modules and Initialization → Post Types/Taxonomies → Asset Loading. +- Internal engineers — Concepts: jump straight to Modules and Initialization and the specific Post Types/Taxonomies APIs. Skim the Quick Start for constants and bootstrap. +- Non‑technical stakeholders — Overview: This framework standardizes how we register types, taxonomies, and assets so teams ship features faster with less boilerplate and more consistency. + +Quick Start (at a glance) +1) Add PSR-4 autoloading in composer.json for your project namespace (inc/ or src/). +2) Define constants (`YOUR_PLUGIN_PATH`, `YOUR_PLUGIN_URL`, `YOUR_PLUGIN_INC`, `YOUR_PLUGIN_VERSION`) in your plugin/theme bootstrap. +3) Initialize modules: `TenupFramework\ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC )`. +4) Implement small classes that implement `ModuleInterface` (use the `Module` trait) and optionally extend `AbstractPostType` / `AbstractTaxonomy`. +5) Load assets via the `GetAssetInfo` trait using dist/.asset.php sidecars. + +## Table of Contents +- [Autoloading and Modules](Autoloading.md) — how classes are discovered and initialized +- [Modules and Initialization](Modules-and-Initialization.md) +- [Post Types](Post-Types.md) — building custom and core post type integrations +- [Taxonomies](Taxonomies.md) — registering and configuring taxonomies +- [Asset Loading](Asset-Loading.md) — working with dist/.asset.php for dependencies and versioning + +Conventions +- Namespaces: use your project namespace (e.g., `YourVendor\\YourPlugin`) for app code; reference framework classes via the TenupFramework namespace. +- Translation: return translated strings from label methods using the correct text domain. +- Keep Modules small and focused; guard execution in `can_register()` to keep admin/frontend/REST behaviors tidy. diff --git a/docs/Taxonomies.md b/docs/Taxonomies.md index 0618750..06c62fd 100644 --- a/docs/Taxonomies.md +++ b/docs/Taxonomies.md @@ -1,34 +1,115 @@ +# Taxonomies + ## Overview -WP Framework simplifies taxonomy registration by offering an abstract class to encapsulate the required functionality. +WP Framework provides an AbstractTaxonomy base class to register and configure taxonomies with minimal boilerplate. Taxonomy classes are also Modules and follow the standard lifecycle (load_order → can_register → register). By default, taxonomies load at order 9 so they can be available for post types that load at the default 10. + +Important: Taxonomies are associated to post types via the Post Type classes (`get_supported_taxonomies()`), not via the taxonomy class itself. -## Example +## Quick start ```php namespace TenUpPlugin\Taxonomies; use TenupFramework\Taxonomies\AbstractTaxonomy; -class Demo extends AbstractTaxonomy { +class DemoCategory extends AbstractTaxonomy { + public function get_name() { return 'tenup-demo-category'; } + public function get_singular_label() { return esc_html__( 'Category', 'tenup-plugin' ); } + public function get_plural_label() { return esc_html__( 'Categories', 'tenup-plugin' ); } - public function get_name() { - return 'tenup-demo-category'; - } + // Optional: make hierarchical like "category"; defaults to false (tags-like) + public function is_hierarchical() { return true; } - public function get_singular_label() { - return esc_html__( 'Category', 'tenup-plugin' ); + // Optional: adjust options beyond defaults + public function get_options() { + $opts = parent::get_options(); + $opts['rewrite'] = [ 'slug' => 'demo-category', 'with_front' => false ]; + return $opts; } - public function get_plural_label() { - return esc_html__( 'Categories', 'tenup-plugin' ); - } + // Optional: gate where this module runs + public function can_register() { return true; } +} +``` - public function get_post_types() { - return [ 'tenup-demo' ]; - } +Associate the taxonomy with your post type by declaring it in the post type class: +```php +// In your AbstractPostType subclass +public function get_supported_taxonomies() { return [ 'tenup-demo-category' ]; } +``` + +## API reference (AbstractTaxonomy) +Required methods: +- `get_name()`: string — Taxonomy slug, e.g., tenup-demo-category. +- `get_singular_label()`: string — Translated singular label. +- `get_plural_label()`: string — Translated plural label. + +Common optional methods: +- `is_hierarchical()`: bool — Defaults to false (tags-like). Return true for category-like. +- `get_options()`: array — Returns args passed to `register_taxonomy()`; see below for defaults and keys. +- `after_register()`: void — Called after registration; use for additional setup. +- `can_register()`: bool — Implement to control when to register (admin vs. frontend, feature flags, etc.). +- `load_order()`: int — Defaults to 9 in `AbstractTaxonomy`; override to change load priority relative to other modules. + +Registration (implemented by `AbstractTaxonomy`): +- `register()`: calls `register_taxonomy( get_name(), get_post_types(), get_options() )` and then `after_register()`. The base class returns an empty array from `get_post_types()` intentionally; association is handled by post types via `get_supported_taxonomies()`. + +## Options (get_options) defaults +AbstractTaxonomy provides sensible defaults: +- `labels`: from `get_labels()` +- `hierarchical`: `is_hierarchical()` +- `show_ui`: true +- `show_admin_column`: true +- `query_var`: true +- `show_in_rest`: true +- `public`: true + +You may override `get_options()` to set any core taxonomy args, including: +- `rewrite` (array|bool): slug, with_front, hierarchical, ep_mask +- `capabilities` (array) +- `default_term` (string|array{name: string, slug?: string, description?: string}) +- `meta_box_cb` (bool|callable) +- `show_in_menu`, show_in_nav_menus, show_tagcloud, show_in_quick_edit +- `rest_base`, rest_namespace, rest_controller_class +- `update_count_callback` (callable) + +## Labels +`AbstractTaxonomy::get_labels()` builds a robust labels array using your singular/plural labels. Ensure your label methods return translated strings using your project’s text domain. + +## Examples +- Non-hierarchical tag-like taxonomy: +```php +public function is_hierarchical() { return false; } +public function get_options() { + return [ + 'labels' => $this->get_labels(), + 'show_in_rest' => true, + 'rewrite' => [ 'slug' => 'demo-tags' ], + ]; +} +``` + +- Custom capabilities: +```php +public function get_options() { + $opts = parent::get_options(); + $opts['capabilities'] = [ + 'manage_terms' => 'manage_demo_terms', + 'edit_terms' => 'manage_demo_terms', + 'delete_terms' => 'manage_demo_terms', + 'assign_terms' => 'edit_posts', + ]; + return $opts; } ``` -## Key Points +## Association pattern and rationale +- Associations are declared in Post Type classes via `get_supported_taxonomies()`; taxonomy classes should not declare post types with `get_post_types()`. +- Rationale: centralizes relationships in the place where object-type behavior lives; avoids duplication and fragile coupling; keeps taxonomy registration independent of associations. +- Migration: If you previously returned post types from a taxonomy class via `get_post_types()`, remove that method and add the taxonomy slug to each relevant post type's `get_supported_taxonomies()`. -* Define both singular and plural labels. -* Associate the taxonomy with the relevant post type(s). -* Keep taxonomy definitions modular for ease of maintenance. \ No newline at end of file +## See also +- [Docs Home](README.md) +- [Autoloading and Modules](Autoloading.md) +- [Modules and Initialization](Modules-and-Initialization.md) +- [Post Types](Post-Types.md) +- [Asset Loading](Asset-Loading.md) From d09fca1adf5206ad440755bcd7b3d91efe7b3379 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 14 Aug 2025 13:36:44 +0100 Subject: [PATCH 04/14] Add headings --- docs/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 8137d4e..2cb283c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,18 +1,17 @@ # WP Framework Documentation -What is WP Framework? WP Framework is a lightweight set of building blocks for structuring WordPress plugins and themes around small, composable Modules. It provides: - Module discovery and initialization (with a predictable lifecycle) - Base classes for custom and core post types - Base class for taxonomies - Asset helpers that read modern build sidecars (`.asset.php`) -Who is this for? +## Who is this for? - External engineers — Start here: follow the Quick Start and then read Autoloading and Modules → Modules and Initialization → Post Types/Taxonomies → Asset Loading. - Internal engineers — Concepts: jump straight to Modules and Initialization and the specific Post Types/Taxonomies APIs. Skim the Quick Start for constants and bootstrap. - Non‑technical stakeholders — Overview: This framework standardizes how we register types, taxonomies, and assets so teams ship features faster with less boilerplate and more consistency. -Quick Start (at a glance) +## Quick Start (at a glance) 1) Add PSR-4 autoloading in composer.json for your project namespace (inc/ or src/). 2) Define constants (`YOUR_PLUGIN_PATH`, `YOUR_PLUGIN_URL`, `YOUR_PLUGIN_INC`, `YOUR_PLUGIN_VERSION`) in your plugin/theme bootstrap. 3) Initialize modules: `TenupFramework\ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC )`. @@ -26,7 +25,7 @@ Quick Start (at a glance) - [Taxonomies](Taxonomies.md) — registering and configuring taxonomies - [Asset Loading](Asset-Loading.md) — working with dist/.asset.php for dependencies and versioning -Conventions +## Conventions - Namespaces: use your project namespace (e.g., `YourVendor\\YourPlugin`) for app code; reference framework classes via the TenupFramework namespace. - Translation: return translated strings from label methods using the correct text domain. - Keep Modules small and focused; guard execution in `can_register()` to keep admin/frontend/REST behaviors tidy. From 01ae2ee1fab3b689753a4271ae70150b574d0ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Thu, 14 Aug 2025 16:45:35 +0000 Subject: [PATCH 05/14] Fix a typo --- src/Module.php | 2 +- src/ModuleInitialization.php | 2 +- src/Taxonomies/AbstractTaxonomy.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Module.php b/src/Module.php index 6ff3454..fc67ebf 100644 --- a/src/Module.php +++ b/src/Module.php @@ -15,7 +15,7 @@ trait Module { /** - * Used to alter the order in which clases are initialized. + * Used to alter the order in which classes are initialized. * * Lower number will be initialized first. * diff --git a/src/ModuleInitialization.php b/src/ModuleInitialization.php index 092ea68..5c53d4b 100644 --- a/src/ModuleInitialization.php +++ b/src/ModuleInitialization.php @@ -1,6 +1,6 @@ Date: Thu, 11 Sep 2025 17:32:10 +0100 Subject: [PATCH 06/14] Add Rector for upgrades --- composer.json | 8 +- composer.lock | 207 +++++++++++++++++++++++++++++++++++++++++++++++++- phpstan.neon | 6 ++ rector.php | 27 +++++++ 4 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 rector.php diff --git a/composer.json b/composer.json index a97cfc4..813c5bb 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,9 @@ "10up/phpcs-composer": "^3.0", "phpcompatibility/php-compatibility": "dev-develop as 9.99.99", "phpunit/php-code-coverage": "^9.2", - "slevomat/coding-standard": "^8.15" + "slevomat/coding-standard": "^8.15", + "rector/rector": "^2.0", + "tomasvotruba/type-coverage": "^2.0" }, "scripts": { "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit", @@ -52,6 +54,10 @@ "static": [ "Composer\\Config::disableProcessTimeout", "phpstan --memory-limit=1G" + ], + "rector": [ + "./vendor/bin/rector", + "composer run lint-fix" ] }, "config": { diff --git a/composer.lock b/composer.lock index 7f69bb0..f386d5f 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": "5c674b2bd34e9e2105a937d69259c184", + "content-hash": "88da233f8a52d5119878cc443defdcdd", "packages": [ { "name": "amphp/amp", @@ -2329,6 +2329,95 @@ ], "time": "2025-02-12T12:17:51+00:00" }, + { + "name": "nette/utils", + "version": "v4.0.8", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.8" + }, + "time": "2025-08-06T21:43:34+00:00" + }, { "name": "nikic/php-parser", "version": "v5.4.0", @@ -3573,6 +3662,65 @@ ], "time": "2024-12-05T13:48:26+00:00" }, + { + "name": "rector/rector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "5844a718acb40f40afcd110394270afa55509fd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/5844a718acb40f40afcd110394270afa55509fd0", + "reference": "5844a718acb40f40afcd110394270afa55509fd0", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.6" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.0.10" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-03-03T17:35:18+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -4854,6 +5002,63 @@ ], "time": "2024-03-03T12:36:25+00:00" }, + { + "name": "tomasvotruba/type-coverage", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/type-coverage.git", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2 || ^4.0", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Measure type coverage of your project", + "keywords": [ + "phpstan-extension", + "static analysis" + ], + "support": { + "issues": "https://github.com/TomasVotruba/type-coverage/issues", + "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-01-07T00:10:26+00:00" + }, { "name": "wp-coding-standards/wpcs", "version": "3.1.0", diff --git a/phpstan.neon b/phpstan.neon index 4d9795d..b6c7924 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,7 @@ includes: - vendor/szepeviktor/phpstan-wordpress/extension.neon - vendor/phpstan/phpstan-deprecation-rules/rules.neon + - vendor/tomasvotruba/type-coverage/config/extension.neon parameters: paths: @@ -16,3 +17,8 @@ parameters: - '#^Function remove_filter invoked with [34567] parameters, 2-3 required\.$#' # Remove issues that come from using array as a type rather than string[] or array etc. - '#no value type specified in iterable type array#' + type_coverage: + return: 99 + param: 99 + property: 99 + constant: 99 diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..fac46ff --- /dev/null +++ b/rector.php @@ -0,0 +1,27 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/fixtures', + ]) + ->withSkip([ + __DIR__ . '/**/node_modules/**', + __DIR__ . '/**/vendor/**', + __DIR__ . '/**/dist/**', + + ]) + ->withPhpSets(php83: true) + ->withTypeCoverageLevel(49) + ->withSkip([ + Rector\Php81\Rector\Array_\FirstClassCallableRector::class, + Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector::class, + Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector::class, + Rector\Php53\Rector\Ternary\TernaryToElvisRector::class, + Rector\Php81\Rector\Property\ReadOnlyPropertyRector::class, + ]); From c70f6cdc8ddd829cdcd473813fa1813f28aac740 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 11 Sep 2025 17:39:43 +0100 Subject: [PATCH 07/14] Add types --- fixtures/classes/PostTypes/Demo.php | 14 +++--- fixtures/classes/PostTypes/Page.php | 6 +-- fixtures/classes/PostTypes/Post.php | 6 +-- fixtures/classes/Standalone/Standalone.php | 2 +- fixtures/classes/Taxonomies/Demo.php | 8 ++-- src/Assets/GetAssetInfo.php | 10 ++--- src/Module.php | 12 ++--- src/ModuleInitialization.php | 44 +++++++------------ src/ModuleInterface.php | 12 ++--- src/PostTypes/AbstractCorePostType.php | 23 +++------- src/PostTypes/AbstractPostType.php | 51 ++++++---------------- src/Taxonomies/AbstractTaxonomy.php | 35 +++++---------- 12 files changed, 73 insertions(+), 150 deletions(-) diff --git a/fixtures/classes/PostTypes/Demo.php b/fixtures/classes/PostTypes/Demo.php index 410cf46..8e5c515 100644 --- a/fixtures/classes/PostTypes/Demo.php +++ b/fixtures/classes/PostTypes/Demo.php @@ -21,7 +21,7 @@ class Demo extends AbstractPostType { * * @return string */ - public function get_name() { + public function get_name(): string { return 'tenup-demo'; } @@ -30,7 +30,7 @@ public function get_name() { * * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Demo', 'tenup-plugin' ); } @@ -39,7 +39,7 @@ public function get_singular_label() { * * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Demos', 'tenup-plugin' ); } @@ -52,7 +52,7 @@ public function get_plural_label() { * * @return string */ - public function get_menu_icon() { + public function get_menu_icon(): string { return 'dashicons-chart-pie'; } @@ -61,7 +61,7 @@ public function get_menu_icon() { * * @return bool */ - public function can_register() { + public function can_register(): bool { return true; } @@ -71,7 +71,7 @@ public function can_register() { * * @return array */ - public function get_supported_taxonomies() { + public function get_supported_taxonomies(): array { return [ 'tenup-tax-demo', ]; @@ -82,7 +82,7 @@ public function get_supported_taxonomies() { * * @return void */ - public function after_register() { + public function after_register(): void { // Register any hooks/filters you need. } } diff --git a/fixtures/classes/PostTypes/Page.php b/fixtures/classes/PostTypes/Page.php index fa360d4..7815534 100644 --- a/fixtures/classes/PostTypes/Page.php +++ b/fixtures/classes/PostTypes/Page.php @@ -24,7 +24,7 @@ class Page extends AbstractCorePostType { * * @return string */ - public function get_name() { + public function get_name(): string { return 'page'; } @@ -34,7 +34,7 @@ public function get_name() { * * @return array */ - public function get_supported_taxonomies() { + public function get_supported_taxonomies(): array { return []; } @@ -43,7 +43,7 @@ public function get_supported_taxonomies() { * * @return void */ - public function after_register() { + public function after_register(): void { // Do nothing. } } diff --git a/fixtures/classes/PostTypes/Post.php b/fixtures/classes/PostTypes/Post.php index ce4a28c..21c93ab 100644 --- a/fixtures/classes/PostTypes/Post.php +++ b/fixtures/classes/PostTypes/Post.php @@ -24,7 +24,7 @@ class Post extends AbstractCorePostType { * * @return string */ - public function get_name() { + public function get_name(): string { return 'post'; } @@ -36,7 +36,7 @@ public function get_name() { * * @return array */ - public function get_supported_taxonomies() { + public function get_supported_taxonomies(): array { return []; } @@ -45,7 +45,7 @@ public function get_supported_taxonomies() { * * @return void */ - public function after_register() { + public function after_register(): void { // Do nothing. } } diff --git a/fixtures/classes/Standalone/Standalone.php b/fixtures/classes/Standalone/Standalone.php index d52c2b8..94bfdb7 100644 --- a/fixtures/classes/Standalone/Standalone.php +++ b/fixtures/classes/Standalone/Standalone.php @@ -27,7 +27,7 @@ public function __construct() { * * @return void */ - public function init() { + public function init(): void { echo 'Hello from the Standalone class!'; } } diff --git a/fixtures/classes/Taxonomies/Demo.php b/fixtures/classes/Taxonomies/Demo.php index de86cc6..281dc6b 100644 --- a/fixtures/classes/Taxonomies/Demo.php +++ b/fixtures/classes/Taxonomies/Demo.php @@ -21,7 +21,7 @@ class Demo extends AbstractTaxonomy { * * @return string */ - public function get_name() { + public function get_name(): string { return 'tenup-tax-demo'; } @@ -30,7 +30,7 @@ public function get_name() { * * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Demo Term', 'tenup-plugin' ); } @@ -39,7 +39,7 @@ public function get_singular_label() { * * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Demo Terms', 'tenup-plugin' ); } @@ -48,7 +48,7 @@ public function get_plural_label() { * * @return bool */ - public function can_register() { + public function can_register(): bool { return true; } } diff --git a/src/Assets/GetAssetInfo.php b/src/Assets/GetAssetInfo.php index 5241f31..f2d13a3 100644 --- a/src/Assets/GetAssetInfo.php +++ b/src/Assets/GetAssetInfo.php @@ -23,24 +23,22 @@ trait GetAssetInfo { * * @var ?string */ - public $dist_path = null; + public ?string $dist_path = null; /** * Fallback version to use if asset file is not found * * @var ?string */ - public $fallback_version = null; + public ?string $fallback_version = null; /** * Setup asset variables * * @param string $dist_path Path to the dist directory * @param string $fallback_version Fallback version to use if asset file is not found - * - * @return void */ - public function setup_asset_vars( string $dist_path, string $fallback_version ) { + public function setup_asset_vars( string $dist_path, string $fallback_version ): void { $this->dist_path = trailingslashit( $dist_path ); $this->fallback_version = $fallback_version; } @@ -55,7 +53,7 @@ public function setup_asset_vars( string $dist_path, string $fallback_version ) * * @return string|($attribute is null ? array{version: string, dependencies: array} : $attribute is'dependencies' ? array : string) */ - public function get_asset_info( string $slug, ?string $attribute = null ) { + public function get_asset_info( string $slug, ?string $attribute = null ): string|array { if ( is_null( $this->dist_path ) || is_null( $this->fallback_version ) ) { throw new RuntimeException( 'Asset variables not set. Please run setup_asset_vars() before calling get_asset_info().' ); diff --git a/src/Module.php b/src/Module.php index fc67ebf..a4fc4c7 100644 --- a/src/Module.php +++ b/src/Module.php @@ -20,24 +20,18 @@ trait Module { * Lower number will be initialized first. * * @note This has no correlation to the `init` priority. It's just a way to allow certain classes to be initialized before others. - * - * @return int The priority of the module. */ - public function load_order() { + public function load_order(): int { return 10; } /** * Checks whether the Module should run within the current context. - * - * @return bool */ - abstract public function can_register(); + abstract public function can_register(): bool; /** * Connects the Module with WordPress using Hooks and/or Filters. - * - * @return void */ - abstract public function register(); + abstract public function register(): void; } diff --git a/src/ModuleInitialization.php b/src/ModuleInitialization.php index 5c53d4b..01271cd 100644 --- a/src/ModuleInitialization.php +++ b/src/ModuleInitialization.php @@ -24,16 +24,14 @@ class ModuleInitialization { /** * The class instance. * - * @var null|ModuleInitialization + * @var ?\TenupFramework\ModuleInitialization */ - private static $instance = null; + private static ?\TenupFramework\ModuleInitialization $instance = null; /** * Get the instance of the class. - * - * @return ModuleInitialization */ - public static function instance() { + public static function instance(): \TenupFramework\ModuleInitialization { if ( null === self::$instance ) { self::$instance = new self(); } @@ -52,7 +50,7 @@ private function __construct() { * * @var array */ - protected $classes = []; + protected array $classes = []; /** * Get all the TenupFramework plugin classes. @@ -61,7 +59,7 @@ private function __construct() { * * @return array */ - public function get_classes( $dir ) { + public function get_classes( string $dir ): array { $this->directory_check( $dir ); // Get all classes from this directory and its subdirectories. @@ -77,7 +75,8 @@ public function get_classes( $dir ) { ); } - $classes = array_filter( $class_finder->get(), fn( $cl ) => is_string( $cl ) ); + // @phpstan-ignore-next-line typeCoverage.paramTypeCoverage + $classes = array_filter( $class_finder->get(), fn( $cl ): bool => is_string( $cl ) ); // Return the classes return $classes; @@ -89,10 +88,8 @@ public function get_classes( $dir ) { * @param string $dir The directory to check. * * @throws \RuntimeException If the directory does not exist. - * - * @return bool */ - protected function directory_check( $dir ): bool { + protected function directory_check( ?string $dir ): bool { if ( empty( $dir ) ) { throw new \RuntimeException( 'Directory is required to initialize classes.' ); } @@ -109,13 +106,12 @@ protected function directory_check( $dir ): bool { * Initialize all the TenupFramework plugin classes. * * @param string $dir The directory to search for classes. - * - * @return void */ - public function init_classes( $dir = '' ) { + public function init_classes( ?string $dir = '' ): void { $this->directory_check( $dir ); $load_class_order = []; + // @phpstan-ignore-next-line argument.type foreach ( $this->get_classes( $dir ) as $class ) { // Create a slug for the class name. $slug = $this->slugify_class_name( $class ); @@ -138,7 +134,7 @@ public function init_classes( $dir = '' ) { } // Check if the class implements ModuleInterface before instantiating it - if ( ! $reflection_class->implementsInterface( 'TenupFramework\ModuleInterface' ) ) { + if ( ! $reflection_class->implementsInterface( \TenupFramework\ModuleInterface::class ) ) { continue; } @@ -181,8 +177,6 @@ public function init_classes( $dir = '' ) { * * @param string $class_name The name of the class to load. * - * @return false|ReflectionClass Returns a ReflectionClass instance if the class is loadable, or false if it is not. - * * @phpstan-ignore missingType.generics */ public function get_fully_loadable_class( string $class_name ): false|ReflectionClass { @@ -190,7 +184,7 @@ public function get_fully_loadable_class( string $class_name ): false|Reflection // Create a new reflection of the class. // @phpstan-ignore argument.type return new ReflectionClass( $class_name ); - } catch ( \Throwable $e ) { + } catch ( \Throwable ) { // This includes ReflectionException, Error due to missing parent, etc. return false; } @@ -200,10 +194,8 @@ public function get_fully_loadable_class( string $class_name ): false|Reflection * Slugify a class name. * * @param string $class_name The class name. - * - * @return string */ - protected function slugify_class_name( $class_name ) { + protected function slugify_class_name( string $class_name ): string { return sanitize_title( str_replace( '\\', '-', $class_name ) ); } @@ -211,10 +203,8 @@ protected function slugify_class_name( $class_name ) { * Get a class by its full class name, including namespace. * * @param string $class_name The class name & namespace. - * - * @return false|ModuleInterface */ - public function get_class( $class_name ) { + public function get_class( string $class_name ): false|ModuleInterface { $class_name = $this->slugify_class_name( $class_name ); if ( isset( $this->classes[ $class_name ] ) ) { @@ -229,7 +219,7 @@ public function get_class( $class_name ) { * * @return array */ - public function get_all_classes() { + public function get_all_classes(): array { return $this->classes; } @@ -237,10 +227,8 @@ public function get_all_classes() { * Get an initialized class by its full class name, including namespace. * * @param string $class_name The class name including the namespace. - * - * @return false|ModuleInterface */ - public static function get_module( $class_name ) { + public static function get_module( string $class_name ): false|ModuleInterface { return self::instance()->get_class( $class_name ); } } diff --git a/src/ModuleInterface.php b/src/ModuleInterface.php index 960ec7b..b03b7e1 100644 --- a/src/ModuleInterface.php +++ b/src/ModuleInterface.php @@ -20,22 +20,16 @@ interface ModuleInterface { * Lower number will be initialized first. * * @note This has no correlation to the `init` priority. It's just a way to allow certain classes to be initialized before others. - * - * @return int The priority of the module. */ - public function load_order(); + public function load_order(): int; /** * Checks whether the Module should run within the current context. - * - * @return bool */ - public function can_register(); + public function can_register(): bool; /** * Connects the Module with WordPress using Hooks and/or Filters. - * - * @return void */ - public function register(); + public function register(): void; } diff --git a/src/PostTypes/AbstractCorePostType.php b/src/PostTypes/AbstractCorePostType.php index 1823423..4a92884 100644 --- a/src/PostTypes/AbstractCorePostType.php +++ b/src/PostTypes/AbstractCorePostType.php @@ -22,10 +22,8 @@ abstract class AbstractCorePostType extends AbstractPostType { * Get the singular post type label. * * No-op for core post types since they are already registered by WordPress. - * - * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return ''; } @@ -33,10 +31,8 @@ public function get_singular_label() { * Get the plural post type label. * * No-op for core post types since they are already registered by WordPress. - * - * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return ''; } @@ -44,10 +40,8 @@ public function get_plural_label() { * Get the menu icon for the post type. * * No-op for core post types since they are already registered by WordPress. - * - * @return string */ - public function get_menu_icon() { + public function get_menu_icon(): string { return ''; } @@ -55,23 +49,16 @@ public function get_menu_icon() { * Checks whether the Module should run within the current context. * * True for core post types since they are already registered by WordPress. - * - * @return bool */ - public function can_register() { + public function can_register(): bool { return true; } /** * Registers a post type and associates its taxonomies. - * - * @uses $this->get_name() to get the post's type name. - * @return Bool Whether this theme has supports for this post type. */ - public function register() { + public function register(): void { $this->register_taxonomies(); $this->after_register(); - - return true; } } diff --git a/src/PostTypes/AbstractPostType.php b/src/PostTypes/AbstractPostType.php index d945df3..855ae95 100644 --- a/src/PostTypes/AbstractPostType.php +++ b/src/PostTypes/AbstractPostType.php @@ -46,24 +46,18 @@ abstract class AbstractPostType implements ModuleInterface { /** * Get the post type name. - * - * @return string */ - abstract public function get_name(); + abstract public function get_name(): string; /** * Get the singular post type label. - * - * @return string */ - abstract public function get_singular_label(); + abstract public function get_singular_label(): string; /** * Get the plural post type label. - * - * @return string */ - abstract public function get_plural_label(); + abstract public function get_plural_label(): string; /** * Get the menu icon for the post type. @@ -71,26 +65,20 @@ abstract public function get_plural_label(); * This can be a base64 encoded SVG, a dashicons class or 'none' to leave it empty so it can be filled with CSS. * * @see https://developer.wordpress.org/resource/dashicons/ - * - * @return string */ - abstract public function get_menu_icon(); + abstract public function get_menu_icon(): string; /** * Get the menu position for the post type. - * - * @return int|null */ - public function get_menu_position() { + public function get_menu_position(): int|null { return null; } /** * Is the post type hierarchical? - * - * @return bool */ - public function is_hierarchical() { + public function is_hierarchical(): bool { return false; } @@ -99,7 +87,7 @@ public function is_hierarchical() { * * @return array */ - public function get_editor_supports() { + public function get_editor_supports(): array { $supports = [ 'title', 'editor', @@ -154,7 +142,7 @@ public function get_editor_supports() { * template_lock?: string|false, * } */ - public function get_options() { + public function get_options(): array { $options = [ 'labels' => $this->get_labels(), 'public' => true, @@ -182,7 +170,7 @@ public function get_options() { * * @return array */ - public function get_labels() { + public function get_labels(): array { $plural_label = $this->get_plural_label(); $singular_label = $this->get_singular_label(); @@ -225,25 +213,18 @@ public function get_labels() { /** * Registers a post type and associates its taxonomies. - * - * @uses $this->get_name() to get the post's type name. - * @return Bool Whether this theme has supports for this post type. */ - public function register() { + public function register(): void { $this->register_post_type(); $this->register_taxonomies(); $this->after_register(); - - return true; } /** * Registers the current post type with WordPress. - * - * @return void */ - public function register_post_type() { + public function register_post_type(): void { register_post_type( $this->get_name(), $this->get_options() @@ -252,10 +233,8 @@ public function register_post_type() { /** * Registers the taxonomies declared with the current post type. - * - * @return void */ - public function register_taxonomies() { + public function register_taxonomies(): void { $taxonomies = $this->get_supported_taxonomies(); $object_type = $this->get_name(); @@ -276,16 +255,14 @@ public function register_taxonomies() { * * @return array */ - public function get_supported_taxonomies() { + public function get_supported_taxonomies(): array { return []; } /** * Run any code after the post type has been registered. - * - * @return void */ - public function after_register() { + public function after_register(): void { // Do nothing. } } diff --git a/src/Taxonomies/AbstractTaxonomy.php b/src/Taxonomies/AbstractTaxonomy.php index c3dfdf3..328b0a2 100644 --- a/src/Taxonomies/AbstractTaxonomy.php +++ b/src/Taxonomies/AbstractTaxonomy.php @@ -44,40 +44,30 @@ abstract class AbstractTaxonomy implements ModuleInterface { * Used to alter the order in which classes are initialized. * * Lower number will be initialized first. - * - * @return int */ - public function load_order() { + public function load_order(): int { return 9; } /** * Get the taxonomy name. - * - * @return string */ - abstract public function get_name(); + abstract public function get_name(): string; /** * Get the singular taxonomy label. - * - * @return string */ - abstract public function get_singular_label(); + abstract public function get_singular_label(): string; /** * Get the plural taxonomy label. - * - * @return string */ - abstract public function get_plural_label(); + abstract public function get_plural_label(): string; /** * Is the taxonomy hierarchical? - * - * @return bool */ - public function is_hierarchical() { + public function is_hierarchical(): bool { return false; } @@ -85,9 +75,8 @@ public function is_hierarchical() { * Register hooks and actions. * * @uses $this->get_name() to get the taxonomy's slug. - * @return bool */ - public function register() { + public function register(): void { \register_taxonomy( $this->get_name(), $this->get_post_types(), @@ -95,8 +84,6 @@ public function register() { ); $this->after_register(); - - return true; } /** @@ -137,7 +124,7 @@ public function register() { * _builtin?: bool, * } */ - public function get_options() { + public function get_options(): array { return [ 'labels' => $this->get_labels(), 'hierarchical' => $this->is_hierarchical(), @@ -154,7 +141,7 @@ public function get_options() { * * @return array */ - public function get_labels() { + public function get_labels(): array { $plural_label = $this->get_plural_label(); $singular_label = $this->get_singular_label(); @@ -187,16 +174,14 @@ public function get_labels() { * * @return array */ - public function get_post_types() { + public function get_post_types(): array { return []; } /** * Run any code after the taxonomy has been registered. - * - * @return void */ - public function after_register() { + public function after_register(): void { // Do nothing. } } From 29f526814f4f955f4edd136f3d1d48cbd13adfe3 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 13 Nov 2025 16:58:03 +0000 Subject: [PATCH 08/14] Allow caching to be skipped --- src/ModuleInitialization.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ModuleInitialization.php b/src/ModuleInitialization.php index 01271cd..67e8df6 100644 --- a/src/ModuleInitialization.php +++ b/src/ModuleInitialization.php @@ -68,7 +68,7 @@ public function get_classes( string $dir ): array { $class_finder->classes(); // If we are in production or staging, cache the class loader to improve performance. - if ( ! defined( 'VIP_GO_APP_ENVIRONMENT' ) && in_array( wp_get_environment_type(), [ 'production', 'staging' ], true ) ) { + if ( $this->should_use_cache() ) { $class_finder->withCache( __NAMESPACE__, new FileDiscoverCacheDriver( $dir . '/class-loader-cache' ) @@ -82,6 +82,27 @@ public function get_classes( string $dir ): array { return $classes; } + /** + * Should we set up and use the class cache? + * + * @return bool + */ + protected function should_use_cache(): bool { + if ( defined( 'VIP_GO_APP_ENVIRONMENT' ) ) { + return false; + } + + if ( ! in_array( wp_get_environment_type(), [ 'production', 'staging' ], true ) ) { + return false; + } + + if ( defined( 'TENUP_FRAMEWORK_DISABLE_CLASS_CACHE' ) && true === TENUP_FRAMEWORK_DISABLE_CLASS_CACHE ) { + return false; + } + + return true; + } + /** * Check if the directory exists. * From 95a29869dc7d2500e9c3e95308847db296b04d3d Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 13 Nov 2025 16:58:14 +0000 Subject: [PATCH 09/14] Test cache skipping is working --- tests/ModuleInitializationTest.php | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/ModuleInitializationTest.php b/tests/ModuleInitializationTest.php index ccf13db..6a38bdb 100644 --- a/tests/ModuleInitializationTest.php +++ b/tests/ModuleInitializationTest.php @@ -10,6 +10,7 @@ namespace TenupFrameworkTests; use PHPUnit\Framework\TestCase; +use function Brain\Monkey\Functions\stubs; /** * Test Class @@ -147,4 +148,75 @@ public function testIsClassFullyLoadable() { $this->assertInstanceOf( 'ReflectionClass', $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\ChildClass' ) ); $this->assertFalse( $module_init->get_fully_loadable_class( '\TenupFrameworkTestClasses\Loadable\InvalidChildClass' ) ); } + + + /** + * Ensure it returns false if VIP_GO_APP_ENVIRONMENT is defined. + * + * @return void + */ + public function test_should_use_cache_returns_false_when_vip_env_is_defined() { + define( 'VIP_GO_APP_ENVIRONMENT', true ); + $module_init = \TenupFramework\ModuleInitialization::instance(); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); + $method->setAccessible( true ); + + $this->assertFalse( $method->invoke( $module_init ) ); + } + + /** + * Ensure it returns false in non-production or staging environments. + * + * @return void + */ + public function test_should_use_cache_returns_false_in_non_production_or_staging_env() { + stubs( + [ + 'wp_get_environment_type' => 'development' + ] + ); + + $module_init = \TenupFramework\ModuleInitialization::instance(); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); + $method->setAccessible( true ); + + $this->assertFalse( $method->invoke( $module_init ) ); + } + + /** + * Ensure it returns false when TENUP_FRAMEWORK_DISABLE_CLASS_CACHE is defined. + * + * @return void + */ + public function test_should_use_cache_returns_false_when_disable_class_cache_is_defined() { + define( 'TENUP_FRAMEWORK_DISABLE_CLASS_CACHE', true ); + $module_init = \TenupFramework\ModuleInitialization::instance(); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); + $method->setAccessible( true ); + + $this->assertFalse( $method->invoke( $module_init ) ); + } + + /** + * Ensure it returns true under default conditions. + * + * @return void + */ + public function test_should_use_cache_returns_true_under_default_conditions() { + stubs( + [ + 'wp_get_environment_type' => 'production' + ] + ); + + $module_init = \TenupFramework\ModuleInitialization::instance(); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); + $method->setAccessible( true ); + + $this->assertTrue( $method->invoke( $module_init ) ); + } } From e96eddea0db381d7b34c628d98c0f1af51d9f65c Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 13 Nov 2025 16:58:25 +0000 Subject: [PATCH 10/14] Update docs --- docs/Modules-and-Initialization.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Modules-and-Initialization.md b/docs/Modules-and-Initialization.md index 490e85e..e00a34d 100644 --- a/docs/Modules-and-Initialization.md +++ b/docs/Modules-and-Initialization.md @@ -40,6 +40,7 @@ Environment cache behavior - When it’s used: only in `production` and `staging` environment types (`wp_get_environment_type()`). - How to clear: delete the `class-loader-cache` folder; it will be rebuilt on next discovery. - How to disable in development: use `development` or `local` environment types, or define `VIP_GO_APP_ENVIRONMENT` to skip the cache. +- How to disable for hosts that don't support file-based caching: `define( 'TENUP_FRAMEWORK_DISABLE_CLASS_CACHE', true );` to skip caching altogether. Hooks - Action: `tenup_framework_module_init__{slug}` — fires before each module’s `register()` runs. From d612e517b1a72b13f24fadc16b09310d9139d770 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 13 Nov 2025 17:01:56 +0000 Subject: [PATCH 11/14] Linting fixes --- tests/ModuleInitializationTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/ModuleInitializationTest.php b/tests/ModuleInitializationTest.php index 6a38bdb..2984b67 100644 --- a/tests/ModuleInitializationTest.php +++ b/tests/ModuleInitializationTest.php @@ -158,8 +158,8 @@ public function testIsClassFullyLoadable() { public function test_should_use_cache_returns_false_when_vip_env_is_defined() { define( 'VIP_GO_APP_ENVIRONMENT', true ); $module_init = \TenupFramework\ModuleInitialization::instance(); - $reflection = new \ReflectionClass( $module_init ); - $method = $reflection->getMethod( 'should_use_cache' ); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); $method->setAccessible( true ); $this->assertFalse( $method->invoke( $module_init ) ); @@ -173,13 +173,13 @@ public function test_should_use_cache_returns_false_when_vip_env_is_defined() { public function test_should_use_cache_returns_false_in_non_production_or_staging_env() { stubs( [ - 'wp_get_environment_type' => 'development' + 'wp_get_environment_type' => 'development', ] ); $module_init = \TenupFramework\ModuleInitialization::instance(); - $reflection = new \ReflectionClass( $module_init ); - $method = $reflection->getMethod( 'should_use_cache' ); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); $method->setAccessible( true ); $this->assertFalse( $method->invoke( $module_init ) ); @@ -193,8 +193,8 @@ public function test_should_use_cache_returns_false_in_non_production_or_staging public function test_should_use_cache_returns_false_when_disable_class_cache_is_defined() { define( 'TENUP_FRAMEWORK_DISABLE_CLASS_CACHE', true ); $module_init = \TenupFramework\ModuleInitialization::instance(); - $reflection = new \ReflectionClass( $module_init ); - $method = $reflection->getMethod( 'should_use_cache' ); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); $method->setAccessible( true ); $this->assertFalse( $method->invoke( $module_init ) ); @@ -208,13 +208,13 @@ public function test_should_use_cache_returns_false_when_disable_class_cache_is_ public function test_should_use_cache_returns_true_under_default_conditions() { stubs( [ - 'wp_get_environment_type' => 'production' + 'wp_get_environment_type' => 'production', ] ); $module_init = \TenupFramework\ModuleInitialization::instance(); - $reflection = new \ReflectionClass( $module_init ); - $method = $reflection->getMethod( 'should_use_cache' ); + $reflection = new \ReflectionClass( $module_init ); + $method = $reflection->getMethod( 'should_use_cache' ); $method->setAccessible( true ); $this->assertTrue( $method->invoke( $module_init ) ); From ea9349f5d50d306126714123ece5d6d09dd38f67 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 13 Nov 2025 17:04:44 +0000 Subject: [PATCH 12/14] Update docs --- docs/Autoloading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Autoloading.md b/docs/Autoloading.md index af8b51c..0b7038d 100644 --- a/docs/Autoloading.md +++ b/docs/Autoloading.md @@ -82,7 +82,7 @@ Environment caching: - Discovery results are cached only in production and staging environments (per `wp_get_environment_type()`). - Cache is stored under the directory you pass to `init_classes()`, in a "class-loader-cache" folder (e.g., `YOUR_PLUGIN_INC . 'class-loader-cache'`). - To refresh: delete that folder; it will be rebuilt automatically. -- Caching is skipped entirely when the constant `VIP_GO_APP_ENVIRONMENT` is defined. +- Caching is skipped entirely when the constant `VIP_GO_APP_ENVIRONMENT` is defined or when `TENUP_FRAMEWORK_DISABLE_CLASS_CACHE` is set to `true`. Use `define( 'TENUP_FRAMEWORK_DISABLE_CLASS_CACHE', true )` in environments that don't support writable file systems. ## Defining a Module ```php From 3db410b545289d99cb1f91a3502aa574b0c4f958 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Thu, 13 Nov 2025 17:16:38 +0000 Subject: [PATCH 13/14] Revert "Feature/typing" --- composer.json | 8 +- composer.lock | 207 +-------------------- fixtures/classes/PostTypes/Demo.php | 14 +- fixtures/classes/PostTypes/Page.php | 6 +- fixtures/classes/PostTypes/Post.php | 6 +- fixtures/classes/Standalone/Standalone.php | 2 +- fixtures/classes/Taxonomies/Demo.php | 8 +- phpstan.neon | 6 - rector.php | 27 --- src/Assets/GetAssetInfo.php | 10 +- src/Module.php | 12 +- src/ModuleInitialization.php | 44 +++-- src/ModuleInterface.php | 12 +- src/PostTypes/AbstractCorePostType.php | 23 ++- src/PostTypes/AbstractPostType.php | 51 +++-- src/Taxonomies/AbstractTaxonomy.php | 35 +++- 16 files changed, 152 insertions(+), 319 deletions(-) delete mode 100644 rector.php diff --git a/composer.json b/composer.json index 813c5bb..a97cfc4 100644 --- a/composer.json +++ b/composer.json @@ -43,9 +43,7 @@ "10up/phpcs-composer": "^3.0", "phpcompatibility/php-compatibility": "dev-develop as 9.99.99", "phpunit/php-code-coverage": "^9.2", - "slevomat/coding-standard": "^8.15", - "rector/rector": "^2.0", - "tomasvotruba/type-coverage": "^2.0" + "slevomat/coding-standard": "^8.15" }, "scripts": { "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit", @@ -54,10 +52,6 @@ "static": [ "Composer\\Config::disableProcessTimeout", "phpstan --memory-limit=1G" - ], - "rector": [ - "./vendor/bin/rector", - "composer run lint-fix" ] }, "config": { diff --git a/composer.lock b/composer.lock index f386d5f..7f69bb0 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": "88da233f8a52d5119878cc443defdcdd", + "content-hash": "5c674b2bd34e9e2105a937d69259c184", "packages": [ { "name": "amphp/amp", @@ -2329,95 +2329,6 @@ ], "time": "2025-02-12T12:17:51+00:00" }, - { - "name": "nette/utils", - "version": "v4.0.8", - "source": { - "type": "git", - "url": "https://github.com/nette/utils.git", - "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", - "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", - "shasum": "" - }, - "require": { - "php": "8.0 - 8.5" - }, - "conflict": { - "nette/finder": "<3", - "nette/schema": "<1.2.2" - }, - "require-dev": { - "jetbrains/phpstorm-attributes": "^1.2", - "nette/tester": "^2.5", - "phpstan/phpstan-nette": "^2.0@stable", - "tracy/tracy": "^2.9" - }, - "suggest": { - "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", - "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", - "ext-json": "to use Nette\\Utils\\Json", - "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "psr-4": { - "Nette\\": "src" - }, - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", - "homepage": "https://nette.org", - "keywords": [ - "array", - "core", - "datetime", - "images", - "json", - "nette", - "paginator", - "password", - "slugify", - "string", - "unicode", - "utf-8", - "utility", - "validation" - ], - "support": { - "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.8" - }, - "time": "2025-08-06T21:43:34+00:00" - }, { "name": "nikic/php-parser", "version": "v5.4.0", @@ -3662,65 +3573,6 @@ ], "time": "2024-12-05T13:48:26+00:00" }, - { - "name": "rector/rector", - "version": "2.0.10", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "5844a718acb40f40afcd110394270afa55509fd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/5844a718acb40f40afcd110394270afa55509fd0", - "reference": "5844a718acb40f40afcd110394270afa55509fd0", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.6" - }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" - }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "keywords": [ - "automation", - "dev", - "migration", - "refactoring" - ], - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.0.10" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2025-03-03T17:35:18+00:00" - }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -5002,63 +4854,6 @@ ], "time": "2024-03-03T12:36:25+00:00" }, - { - "name": "tomasvotruba/type-coverage", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/TomasVotruba/type-coverage.git", - "reference": "d033429580f2c18bda538fa44f2939236a990e0c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c", - "reference": "d033429580f2c18bda538fa44f2939236a990e0c", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2 || ^4.0", - "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "config/extension.neon" - ] - } - }, - "autoload": { - "psr-4": { - "TomasVotruba\\TypeCoverage\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Measure type coverage of your project", - "keywords": [ - "phpstan-extension", - "static analysis" - ], - "support": { - "issues": "https://github.com/TomasVotruba/type-coverage/issues", - "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2025-01-07T00:10:26+00:00" - }, { "name": "wp-coding-standards/wpcs", "version": "3.1.0", diff --git a/fixtures/classes/PostTypes/Demo.php b/fixtures/classes/PostTypes/Demo.php index 8e5c515..410cf46 100644 --- a/fixtures/classes/PostTypes/Demo.php +++ b/fixtures/classes/PostTypes/Demo.php @@ -21,7 +21,7 @@ class Demo extends AbstractPostType { * * @return string */ - public function get_name(): string { + public function get_name() { return 'tenup-demo'; } @@ -30,7 +30,7 @@ public function get_name(): string { * * @return string */ - public function get_singular_label(): string { + public function get_singular_label() { return esc_html__( 'Demo', 'tenup-plugin' ); } @@ -39,7 +39,7 @@ public function get_singular_label(): string { * * @return string */ - public function get_plural_label(): string { + public function get_plural_label() { return esc_html__( 'Demos', 'tenup-plugin' ); } @@ -52,7 +52,7 @@ public function get_plural_label(): string { * * @return string */ - public function get_menu_icon(): string { + public function get_menu_icon() { return 'dashicons-chart-pie'; } @@ -61,7 +61,7 @@ public function get_menu_icon(): string { * * @return bool */ - public function can_register(): bool { + public function can_register() { return true; } @@ -71,7 +71,7 @@ public function can_register(): bool { * * @return array */ - public function get_supported_taxonomies(): array { + public function get_supported_taxonomies() { return [ 'tenup-tax-demo', ]; @@ -82,7 +82,7 @@ public function get_supported_taxonomies(): array { * * @return void */ - public function after_register(): void { + public function after_register() { // Register any hooks/filters you need. } } diff --git a/fixtures/classes/PostTypes/Page.php b/fixtures/classes/PostTypes/Page.php index 7815534..fa360d4 100644 --- a/fixtures/classes/PostTypes/Page.php +++ b/fixtures/classes/PostTypes/Page.php @@ -24,7 +24,7 @@ class Page extends AbstractCorePostType { * * @return string */ - public function get_name(): string { + public function get_name() { return 'page'; } @@ -34,7 +34,7 @@ public function get_name(): string { * * @return array */ - public function get_supported_taxonomies(): array { + public function get_supported_taxonomies() { return []; } @@ -43,7 +43,7 @@ public function get_supported_taxonomies(): array { * * @return void */ - public function after_register(): void { + public function after_register() { // Do nothing. } } diff --git a/fixtures/classes/PostTypes/Post.php b/fixtures/classes/PostTypes/Post.php index 21c93ab..ce4a28c 100644 --- a/fixtures/classes/PostTypes/Post.php +++ b/fixtures/classes/PostTypes/Post.php @@ -24,7 +24,7 @@ class Post extends AbstractCorePostType { * * @return string */ - public function get_name(): string { + public function get_name() { return 'post'; } @@ -36,7 +36,7 @@ public function get_name(): string { * * @return array */ - public function get_supported_taxonomies(): array { + public function get_supported_taxonomies() { return []; } @@ -45,7 +45,7 @@ public function get_supported_taxonomies(): array { * * @return void */ - public function after_register(): void { + public function after_register() { // Do nothing. } } diff --git a/fixtures/classes/Standalone/Standalone.php b/fixtures/classes/Standalone/Standalone.php index 94bfdb7..d52c2b8 100644 --- a/fixtures/classes/Standalone/Standalone.php +++ b/fixtures/classes/Standalone/Standalone.php @@ -27,7 +27,7 @@ public function __construct() { * * @return void */ - public function init(): void { + public function init() { echo 'Hello from the Standalone class!'; } } diff --git a/fixtures/classes/Taxonomies/Demo.php b/fixtures/classes/Taxonomies/Demo.php index 281dc6b..de86cc6 100644 --- a/fixtures/classes/Taxonomies/Demo.php +++ b/fixtures/classes/Taxonomies/Demo.php @@ -21,7 +21,7 @@ class Demo extends AbstractTaxonomy { * * @return string */ - public function get_name(): string { + public function get_name() { return 'tenup-tax-demo'; } @@ -30,7 +30,7 @@ public function get_name(): string { * * @return string */ - public function get_singular_label(): string { + public function get_singular_label() { return esc_html__( 'Demo Term', 'tenup-plugin' ); } @@ -39,7 +39,7 @@ public function get_singular_label(): string { * * @return string */ - public function get_plural_label(): string { + public function get_plural_label() { return esc_html__( 'Demo Terms', 'tenup-plugin' ); } @@ -48,7 +48,7 @@ public function get_plural_label(): string { * * @return bool */ - public function can_register(): bool { + public function can_register() { return true; } } diff --git a/phpstan.neon b/phpstan.neon index b6c7924..4d9795d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,6 @@ includes: - vendor/szepeviktor/phpstan-wordpress/extension.neon - vendor/phpstan/phpstan-deprecation-rules/rules.neon - - vendor/tomasvotruba/type-coverage/config/extension.neon parameters: paths: @@ -17,8 +16,3 @@ parameters: - '#^Function remove_filter invoked with [34567] parameters, 2-3 required\.$#' # Remove issues that come from using array as a type rather than string[] or array etc. - '#no value type specified in iterable type array#' - type_coverage: - return: 99 - param: 99 - property: 99 - constant: 99 diff --git a/rector.php b/rector.php deleted file mode 100644 index fac46ff..0000000 --- a/rector.php +++ /dev/null @@ -1,27 +0,0 @@ -withPaths([ - __DIR__ . '/src', - __DIR__ . '/fixtures', - ]) - ->withSkip([ - __DIR__ . '/**/node_modules/**', - __DIR__ . '/**/vendor/**', - __DIR__ . '/**/dist/**', - - ]) - ->withPhpSets(php83: true) - ->withTypeCoverageLevel(49) - ->withSkip([ - Rector\Php81\Rector\Array_\FirstClassCallableRector::class, - Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector::class, - Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector::class, - Rector\Php53\Rector\Ternary\TernaryToElvisRector::class, - Rector\Php81\Rector\Property\ReadOnlyPropertyRector::class, - ]); diff --git a/src/Assets/GetAssetInfo.php b/src/Assets/GetAssetInfo.php index f2d13a3..5241f31 100644 --- a/src/Assets/GetAssetInfo.php +++ b/src/Assets/GetAssetInfo.php @@ -23,22 +23,24 @@ trait GetAssetInfo { * * @var ?string */ - public ?string $dist_path = null; + public $dist_path = null; /** * Fallback version to use if asset file is not found * * @var ?string */ - public ?string $fallback_version = null; + public $fallback_version = null; /** * Setup asset variables * * @param string $dist_path Path to the dist directory * @param string $fallback_version Fallback version to use if asset file is not found + * + * @return void */ - public function setup_asset_vars( string $dist_path, string $fallback_version ): void { + public function setup_asset_vars( string $dist_path, string $fallback_version ) { $this->dist_path = trailingslashit( $dist_path ); $this->fallback_version = $fallback_version; } @@ -53,7 +55,7 @@ public function setup_asset_vars( string $dist_path, string $fallback_version ): * * @return string|($attribute is null ? array{version: string, dependencies: array} : $attribute is'dependencies' ? array : string) */ - public function get_asset_info( string $slug, ?string $attribute = null ): string|array { + public function get_asset_info( string $slug, ?string $attribute = null ) { if ( is_null( $this->dist_path ) || is_null( $this->fallback_version ) ) { throw new RuntimeException( 'Asset variables not set. Please run setup_asset_vars() before calling get_asset_info().' ); diff --git a/src/Module.php b/src/Module.php index a4fc4c7..fc67ebf 100644 --- a/src/Module.php +++ b/src/Module.php @@ -20,18 +20,24 @@ trait Module { * Lower number will be initialized first. * * @note This has no correlation to the `init` priority. It's just a way to allow certain classes to be initialized before others. + * + * @return int The priority of the module. */ - public function load_order(): int { + public function load_order() { return 10; } /** * Checks whether the Module should run within the current context. + * + * @return bool */ - abstract public function can_register(): bool; + abstract public function can_register(); /** * Connects the Module with WordPress using Hooks and/or Filters. + * + * @return void */ - abstract public function register(): void; + abstract public function register(); } diff --git a/src/ModuleInitialization.php b/src/ModuleInitialization.php index 01271cd..5c53d4b 100644 --- a/src/ModuleInitialization.php +++ b/src/ModuleInitialization.php @@ -24,14 +24,16 @@ class ModuleInitialization { /** * The class instance. * - * @var ?\TenupFramework\ModuleInitialization + * @var null|ModuleInitialization */ - private static ?\TenupFramework\ModuleInitialization $instance = null; + private static $instance = null; /** * Get the instance of the class. + * + * @return ModuleInitialization */ - public static function instance(): \TenupFramework\ModuleInitialization { + public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } @@ -50,7 +52,7 @@ private function __construct() { * * @var array */ - protected array $classes = []; + protected $classes = []; /** * Get all the TenupFramework plugin classes. @@ -59,7 +61,7 @@ private function __construct() { * * @return array */ - public function get_classes( string $dir ): array { + public function get_classes( $dir ) { $this->directory_check( $dir ); // Get all classes from this directory and its subdirectories. @@ -75,8 +77,7 @@ public function get_classes( string $dir ): array { ); } - // @phpstan-ignore-next-line typeCoverage.paramTypeCoverage - $classes = array_filter( $class_finder->get(), fn( $cl ): bool => is_string( $cl ) ); + $classes = array_filter( $class_finder->get(), fn( $cl ) => is_string( $cl ) ); // Return the classes return $classes; @@ -88,8 +89,10 @@ public function get_classes( string $dir ): array { * @param string $dir The directory to check. * * @throws \RuntimeException If the directory does not exist. + * + * @return bool */ - protected function directory_check( ?string $dir ): bool { + protected function directory_check( $dir ): bool { if ( empty( $dir ) ) { throw new \RuntimeException( 'Directory is required to initialize classes.' ); } @@ -106,12 +109,13 @@ protected function directory_check( ?string $dir ): bool { * Initialize all the TenupFramework plugin classes. * * @param string $dir The directory to search for classes. + * + * @return void */ - public function init_classes( ?string $dir = '' ): void { + public function init_classes( $dir = '' ) { $this->directory_check( $dir ); $load_class_order = []; - // @phpstan-ignore-next-line argument.type foreach ( $this->get_classes( $dir ) as $class ) { // Create a slug for the class name. $slug = $this->slugify_class_name( $class ); @@ -134,7 +138,7 @@ public function init_classes( ?string $dir = '' ): void { } // Check if the class implements ModuleInterface before instantiating it - if ( ! $reflection_class->implementsInterface( \TenupFramework\ModuleInterface::class ) ) { + if ( ! $reflection_class->implementsInterface( 'TenupFramework\ModuleInterface' ) ) { continue; } @@ -177,6 +181,8 @@ public function init_classes( ?string $dir = '' ): void { * * @param string $class_name The name of the class to load. * + * @return false|ReflectionClass Returns a ReflectionClass instance if the class is loadable, or false if it is not. + * * @phpstan-ignore missingType.generics */ public function get_fully_loadable_class( string $class_name ): false|ReflectionClass { @@ -184,7 +190,7 @@ public function get_fully_loadable_class( string $class_name ): false|Reflection // Create a new reflection of the class. // @phpstan-ignore argument.type return new ReflectionClass( $class_name ); - } catch ( \Throwable ) { + } catch ( \Throwable $e ) { // This includes ReflectionException, Error due to missing parent, etc. return false; } @@ -194,8 +200,10 @@ public function get_fully_loadable_class( string $class_name ): false|Reflection * Slugify a class name. * * @param string $class_name The class name. + * + * @return string */ - protected function slugify_class_name( string $class_name ): string { + protected function slugify_class_name( $class_name ) { return sanitize_title( str_replace( '\\', '-', $class_name ) ); } @@ -203,8 +211,10 @@ protected function slugify_class_name( string $class_name ): string { * Get a class by its full class name, including namespace. * * @param string $class_name The class name & namespace. + * + * @return false|ModuleInterface */ - public function get_class( string $class_name ): false|ModuleInterface { + public function get_class( $class_name ) { $class_name = $this->slugify_class_name( $class_name ); if ( isset( $this->classes[ $class_name ] ) ) { @@ -219,7 +229,7 @@ public function get_class( string $class_name ): false|ModuleInterface { * * @return array */ - public function get_all_classes(): array { + public function get_all_classes() { return $this->classes; } @@ -227,8 +237,10 @@ public function get_all_classes(): array { * Get an initialized class by its full class name, including namespace. * * @param string $class_name The class name including the namespace. + * + * @return false|ModuleInterface */ - public static function get_module( string $class_name ): false|ModuleInterface { + public static function get_module( $class_name ) { return self::instance()->get_class( $class_name ); } } diff --git a/src/ModuleInterface.php b/src/ModuleInterface.php index b03b7e1..960ec7b 100644 --- a/src/ModuleInterface.php +++ b/src/ModuleInterface.php @@ -20,16 +20,22 @@ interface ModuleInterface { * Lower number will be initialized first. * * @note This has no correlation to the `init` priority. It's just a way to allow certain classes to be initialized before others. + * + * @return int The priority of the module. */ - public function load_order(): int; + public function load_order(); /** * Checks whether the Module should run within the current context. + * + * @return bool */ - public function can_register(): bool; + public function can_register(); /** * Connects the Module with WordPress using Hooks and/or Filters. + * + * @return void */ - public function register(): void; + public function register(); } diff --git a/src/PostTypes/AbstractCorePostType.php b/src/PostTypes/AbstractCorePostType.php index 4a92884..1823423 100644 --- a/src/PostTypes/AbstractCorePostType.php +++ b/src/PostTypes/AbstractCorePostType.php @@ -22,8 +22,10 @@ abstract class AbstractCorePostType extends AbstractPostType { * Get the singular post type label. * * No-op for core post types since they are already registered by WordPress. + * + * @return string */ - public function get_singular_label(): string { + public function get_singular_label() { return ''; } @@ -31,8 +33,10 @@ public function get_singular_label(): string { * Get the plural post type label. * * No-op for core post types since they are already registered by WordPress. + * + * @return string */ - public function get_plural_label(): string { + public function get_plural_label() { return ''; } @@ -40,8 +44,10 @@ public function get_plural_label(): string { * Get the menu icon for the post type. * * No-op for core post types since they are already registered by WordPress. + * + * @return string */ - public function get_menu_icon(): string { + public function get_menu_icon() { return ''; } @@ -49,16 +55,23 @@ public function get_menu_icon(): string { * Checks whether the Module should run within the current context. * * True for core post types since they are already registered by WordPress. + * + * @return bool */ - public function can_register(): bool { + public function can_register() { return true; } /** * Registers a post type and associates its taxonomies. + * + * @uses $this->get_name() to get the post's type name. + * @return Bool Whether this theme has supports for this post type. */ - public function register(): void { + public function register() { $this->register_taxonomies(); $this->after_register(); + + return true; } } diff --git a/src/PostTypes/AbstractPostType.php b/src/PostTypes/AbstractPostType.php index 855ae95..d945df3 100644 --- a/src/PostTypes/AbstractPostType.php +++ b/src/PostTypes/AbstractPostType.php @@ -46,18 +46,24 @@ abstract class AbstractPostType implements ModuleInterface { /** * Get the post type name. + * + * @return string */ - abstract public function get_name(): string; + abstract public function get_name(); /** * Get the singular post type label. + * + * @return string */ - abstract public function get_singular_label(): string; + abstract public function get_singular_label(); /** * Get the plural post type label. + * + * @return string */ - abstract public function get_plural_label(): string; + abstract public function get_plural_label(); /** * Get the menu icon for the post type. @@ -65,20 +71,26 @@ abstract public function get_plural_label(): string; * This can be a base64 encoded SVG, a dashicons class or 'none' to leave it empty so it can be filled with CSS. * * @see https://developer.wordpress.org/resource/dashicons/ + * + * @return string */ - abstract public function get_menu_icon(): string; + abstract public function get_menu_icon(); /** * Get the menu position for the post type. + * + * @return int|null */ - public function get_menu_position(): int|null { + public function get_menu_position() { return null; } /** * Is the post type hierarchical? + * + * @return bool */ - public function is_hierarchical(): bool { + public function is_hierarchical() { return false; } @@ -87,7 +99,7 @@ public function is_hierarchical(): bool { * * @return array */ - public function get_editor_supports(): array { + public function get_editor_supports() { $supports = [ 'title', 'editor', @@ -142,7 +154,7 @@ public function get_editor_supports(): array { * template_lock?: string|false, * } */ - public function get_options(): array { + public function get_options() { $options = [ 'labels' => $this->get_labels(), 'public' => true, @@ -170,7 +182,7 @@ public function get_options(): array { * * @return array */ - public function get_labels(): array { + public function get_labels() { $plural_label = $this->get_plural_label(); $singular_label = $this->get_singular_label(); @@ -213,18 +225,25 @@ public function get_labels(): array { /** * Registers a post type and associates its taxonomies. + * + * @uses $this->get_name() to get the post's type name. + * @return Bool Whether this theme has supports for this post type. */ - public function register(): void { + public function register() { $this->register_post_type(); $this->register_taxonomies(); $this->after_register(); + + return true; } /** * Registers the current post type with WordPress. + * + * @return void */ - public function register_post_type(): void { + public function register_post_type() { register_post_type( $this->get_name(), $this->get_options() @@ -233,8 +252,10 @@ public function register_post_type(): void { /** * Registers the taxonomies declared with the current post type. + * + * @return void */ - public function register_taxonomies(): void { + public function register_taxonomies() { $taxonomies = $this->get_supported_taxonomies(); $object_type = $this->get_name(); @@ -255,14 +276,16 @@ public function register_taxonomies(): void { * * @return array */ - public function get_supported_taxonomies(): array { + public function get_supported_taxonomies() { return []; } /** * Run any code after the post type has been registered. + * + * @return void */ - public function after_register(): void { + public function after_register() { // Do nothing. } } diff --git a/src/Taxonomies/AbstractTaxonomy.php b/src/Taxonomies/AbstractTaxonomy.php index 328b0a2..c3dfdf3 100644 --- a/src/Taxonomies/AbstractTaxonomy.php +++ b/src/Taxonomies/AbstractTaxonomy.php @@ -44,30 +44,40 @@ abstract class AbstractTaxonomy implements ModuleInterface { * Used to alter the order in which classes are initialized. * * Lower number will be initialized first. + * + * @return int */ - public function load_order(): int { + public function load_order() { return 9; } /** * Get the taxonomy name. + * + * @return string */ - abstract public function get_name(): string; + abstract public function get_name(); /** * Get the singular taxonomy label. + * + * @return string */ - abstract public function get_singular_label(): string; + abstract public function get_singular_label(); /** * Get the plural taxonomy label. + * + * @return string */ - abstract public function get_plural_label(): string; + abstract public function get_plural_label(); /** * Is the taxonomy hierarchical? + * + * @return bool */ - public function is_hierarchical(): bool { + public function is_hierarchical() { return false; } @@ -75,8 +85,9 @@ public function is_hierarchical(): bool { * Register hooks and actions. * * @uses $this->get_name() to get the taxonomy's slug. + * @return bool */ - public function register(): void { + public function register() { \register_taxonomy( $this->get_name(), $this->get_post_types(), @@ -84,6 +95,8 @@ public function register(): void { ); $this->after_register(); + + return true; } /** @@ -124,7 +137,7 @@ public function register(): void { * _builtin?: bool, * } */ - public function get_options(): array { + public function get_options() { return [ 'labels' => $this->get_labels(), 'hierarchical' => $this->is_hierarchical(), @@ -141,7 +154,7 @@ public function get_options(): array { * * @return array */ - public function get_labels(): array { + public function get_labels() { $plural_label = $this->get_plural_label(); $singular_label = $this->get_singular_label(); @@ -174,14 +187,16 @@ public function get_labels(): array { * * @return array */ - public function get_post_types(): array { + public function get_post_types() { return []; } /** * Run any code after the taxonomy has been registered. + * + * @return void */ - public function after_register(): void { + public function after_register() { // Do nothing. } } From 8779ff318414d4772fe1ae616e6e9cf3adf596b1 Mon Sep 17 00:00:00 2001 From: Daryll Doyle Date: Tue, 18 Nov 2025 14:53:33 +0000 Subject: [PATCH 14/14] Disable chains --- src/ModuleInitialization.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ModuleInitialization.php b/src/ModuleInitialization.php index 0b4d9e3..9f9a8e3 100644 --- a/src/ModuleInitialization.php +++ b/src/ModuleInitialization.php @@ -68,6 +68,8 @@ public function get_classes( $dir ) { $class_finder = Discover::in( $dir ); // Only fetch classes. $class_finder->classes(); + // Disable inheritance chain resolution + $class_finder->withoutChains(); // If we are in production or staging, cache the class loader to improve performance. if ( $this->should_use_cache() ) {