Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' ];
}
}
```

Expand Down
114 changes: 114 additions & 0 deletions docs/Asset-Loading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Asset Loading

## 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
// 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
```

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
);
}
}
```

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',
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

## 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;
}
```

## 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`.

## 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)
121 changes: 121 additions & 0 deletions docs/Autoloading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Autoloading and Modules

## Overview
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.

## 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
// 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; // provides default load_order() = 10

public function can_register(): bool {
// Only run on frontend, for example
return ! is_admin();
}

public function register(): void {
add_action( 'init', function () {
// Add hooks/filters here
} );
}
}
```

## 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()`.


## 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)
100 changes: 100 additions & 0 deletions docs/Modules-and-Initialization.md
Original file line number Diff line number Diff line change
@@ -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.
Loading