Skip to content

Commit 2ecfa59

Browse files
authored
Merge pull request #19 from 10up/feature/docs
Feature/docs
2 parents 4512e0b + d09fca1 commit 2ecfa59

7 files changed

Lines changed: 601 additions & 4 deletions

File tree

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,6 @@ class Demo extends AbstractTaxonomy {
120120
public function get_plural_label() {
121121
return esc_html__( 'Categories', 'tenup-plugin' );
122122
}
123-
124-
public function get_post_types() {
125-
return [ 'tenup-demo' ];
126-
}
127123
}
128124
```
129125

docs/Asset-Loading.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Asset Loading
2+
3+
## Overview
4+
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:
5+
- `dist/js/{slug}.asset.php`
6+
- `dist/css/{slug}.asset.php`
7+
- `dist/blocks/{slug}.asset.php`
8+
9+
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.
10+
11+
## Setup
12+
Prerequisites: define common constants and a dist/ directory for built assets.
13+
14+
```php
15+
// Plugin main file or bootstrap
16+
define( 'YOUR_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
17+
define( 'YOUR_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
18+
define( 'YOUR_PLUGIN_INC', YOUR_PLUGIN_PATH . 'inc/' );
19+
define( 'YOUR_PLUGIN_VERSION', '1.0.0' );
20+
```
21+
22+
Typical dist/ layout (your build may vary):
23+
```
24+
dist/
25+
├─ js/
26+
│ ├─ admin.js
27+
│ └─ admin.asset.php
28+
├─ css/
29+
│ └─ admin.css
30+
└─ blocks/
31+
├─ my-block.js
32+
└─ my-block.asset.php
33+
```
34+
35+
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):
36+
37+
```php
38+
use TenupFramework\Assets\GetAssetInfo;
39+
40+
class AssetsModule {
41+
use GetAssetInfo;
42+
43+
public function __construct() {
44+
$this->setup_asset_vars(
45+
dist_path: YOUR_PLUGIN_PATH . 'dist/',
46+
fallback_version: YOUR_PLUGIN_VERSION
47+
);
48+
}
49+
}
50+
```
51+
52+
Notes:
53+
- If you call `get_asset_info()` before `setup_asset_vars()`, a RuntimeException will be thrown.
54+
- 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.
55+
- 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'`.
56+
57+
## Enqueuing scripts
58+
```php
59+
wp_enqueue_script(
60+
'tenup_plugin_admin',
61+
YOUR_PLUGIN_URL . 'dist/js/admin.js',
62+
$this->get_asset_info( 'admin', 'dependencies' ),
63+
$this->get_asset_info( 'admin', 'version' ),
64+
true
65+
);
66+
```
67+
- dependencies: array of script handles from `admin.asset.php`
68+
- version: string used for cache busting
69+
70+
## Enqueuing styles
71+
```php
72+
wp_enqueue_style(
73+
'tenup_plugin_admin',
74+
YOUR_PLUGIN_URL . 'dist/css/admin.css',
75+
[], // CSS dependencies are uncommon; pass [] unless needed
76+
$this->get_asset_info( 'admin', 'version' )
77+
);
78+
```
79+
80+
## Working with blocks
81+
If you build blocks, pass the block slug used by your build tool:
82+
```php
83+
$deps = $this->get_asset_info( 'my-block', 'dependencies' );
84+
$ver = $this->get_asset_info( 'my-block', 'version' );
85+
$handle = 'tenup_my_block';
86+
87+
wp_register_script( $handle, YOUR_PLUGIN_URL . 'dist/blocks/my-block.js', $deps, $ver, true );
88+
```
89+
The trait automatically checks `dist/blocks/my-block.asset.php` if present.
90+
91+
## Error handling and fallbacks
92+
```php
93+
try {
94+
$deps = $this->get_asset_info( 'frontend', 'dependencies' );
95+
$ver = $this->get_asset_info( 'frontend', 'version' );
96+
} catch ( \RuntimeException $e ) {
97+
// setup_asset_vars() was not called — fall back to safe defaults
98+
$deps = [];
99+
$ver = YOUR_PLUGIN_VERSION;
100+
}
101+
```
102+
103+
## Best practices
104+
- Always call `setup_asset_vars()` early (constructor or on first enqueue).
105+
- Keep your dist path stable across environments (use constants for PATH and URL).
106+
- Use the version from `.asset.php` for reliable cache busting in production.
107+
- For admin-only assets, enqueue on `admin_enqueue_scripts`; for frontend, use `wp_enqueue_scripts`.
108+
109+
## See also
110+
- [Docs Home](README.md)
111+
- [Autoloading and Modules](Autoloading.md)
112+
- [Modules and Initialization](Modules-and-Initialization.md)
113+
- [Post Types](Post-Types.md)
114+
- [Taxonomies](Taxonomies.md)

docs/Autoloading.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Autoloading and Modules
2+
3+
## Overview
4+
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.
5+
6+
## Composer PSR-4 setup
7+
Add your project namespace and source directory in composer.json:
8+
9+
```json
10+
{
11+
"autoload": {
12+
"psr-4": {
13+
"YourVendor\\YourPlugin\\": "inc/"
14+
}
15+
}
16+
}
17+
```
18+
19+
Run `composer dump-autoload` after changes.
20+
21+
## Recommended plugin structure & bootstrap
22+
A simple plugin layout that works well with the framework:
23+
24+
```
25+
my-plugin/
26+
├─ my-plugin.php // main plugin file
27+
├─ composer.json
28+
├─ inc/ // PHP source (PSR-4 autoloaded)
29+
│ ├─ Features/
30+
│ ├─ Posts/
31+
│ └─ Taxonomies/
32+
├─ dist/ // built assets from your toolchain
33+
│ ├─ js/
34+
│ │ ├─ admin.js
35+
│ │ └─ admin.asset.php
36+
│ ├─ css/
37+
│ │ └─ admin.css
38+
│ └─ blocks/
39+
│ ├─ my-block.js
40+
│ └─ my-block.asset.php
41+
└─ readme.txt
42+
```
43+
44+
Define a few useful constants in your main plugin file (or a bootstrap class), then initialize modules:
45+
46+
```php
47+
// Plugin main file or bootstrap
48+
define( 'YOUR_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
49+
define( 'YOUR_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
50+
define( 'YOUR_PLUGIN_INC', YOUR_PLUGIN_PATH . 'inc/' );
51+
define( 'YOUR_PLUGIN_VERSION', '1.0.0' );
52+
53+
use TenupFramework\ModuleInitialization;
54+
ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC );
55+
```
56+
57+
## Initialization
58+
Call the framework’s initializer with the directory where your classes live (e.g., inc or src):
59+
60+
```php
61+
use TenupFramework\ModuleInitialization;
62+
63+
ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC );
64+
```
65+
66+
- Classes must be instantiable and implement `TenupFramework\ModuleInterface`.
67+
- The initializer sorts modules by `load_order()` (defaults to `10` via the `Module` trait) and then calls `register()` only if `can_register()` returns `true`.
68+
- A hook fires before registration: `tenup_framework_module_init__{slug}`, where slug is a sanitized class FQN.
69+
70+
### Verify discovery and environment behavior
71+
In development, you can verify discovered/initialized modules:
72+
73+
```php
74+
add_action( 'plugins_loaded', function () {
75+
$mods = TenupFramework\ModuleInitialization::instance()->get_all_classes();
76+
// For local/dev only:
77+
// error_log( print_r( array_keys( $mods ), true ) );
78+
} );
79+
```
80+
81+
Environment caching:
82+
- Discovery results are cached only in production and staging environments (per `wp_get_environment_type()`).
83+
- 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'`).
84+
- To refresh: delete that folder; it will be rebuilt automatically.
85+
- Caching is skipped entirely when the constant `VIP_GO_APP_ENVIRONMENT` is defined.
86+
87+
## Defining a Module
88+
```php
89+
namespace YourVendor\YourPlugin\Features;
90+
91+
use TenupFramework\ModuleInterface;
92+
use TenupFramework\Module;
93+
94+
class YourModule implements ModuleInterface {
95+
use Module; // provides default load_order() = 10
96+
97+
public function can_register(): bool {
98+
// Only run on frontend, for example
99+
return ! is_admin();
100+
}
101+
102+
public function register(): void {
103+
add_action( 'init', function () {
104+
// Add hooks/filters here
105+
} );
106+
}
107+
}
108+
```
109+
110+
## Best practices
111+
- Keep Modules small and focused; compose behavior via multiple classes.
112+
- Use `can_register()` to gate context-specific behavior (admin vs. frontend, REST, multisite, feature flags).
113+
- Prefer dependency injection via constructor where practical; avoid doing heavy work before `register()`.
114+
115+
116+
## See also
117+
- [Docs Home](README.md)
118+
- [Modules and Initialization](Modules-and-Initialization.md)
119+
- [Post Types](Post-Types.md)
120+
- [Taxonomies](Taxonomies.md)
121+
- [Asset Loading](Asset-Loading.md)

docs/Modules-and-Initialization.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Modules and Initialization
2+
3+
## Overview
4+
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.
5+
6+
Key interfaces and utilities:
7+
- `TenupFramework\ModuleInterface`: declares `load_order()`, `can_register()`, `register()`.
8+
- `TenupFramework\Module` trait: provides a default `load_order()` of `10` and leaves `can_register()` and `register()` abstract for your class to implement.
9+
- `TenupFramework\ModuleInitialization`: discovers, orders, and initializes your Modules.
10+
11+
## Bootstrapping
12+
Call the initializer at plugin or theme bootstrap, pointing it at the directory containing your namespaced classes (e.g., `inc/` or `src/`):
13+
14+
```php
15+
use TenupFramework\ModuleInitialization;
16+
17+
ModuleInitialization::instance()->init_classes( YOUR_PLUGIN_INC );
18+
```
19+
20+
`YOUR_PLUGIN_INC` (or your equivalent constant/path) should resolve to an existing directory. If it does not exist, a RuntimeException will be thrown.
21+
22+
## How discovery and initialization work
23+
ModuleInitialization performs the following steps:
24+
1. Validate the directory exists; otherwise throw a RuntimeException.
25+
2. Discover class names within the directory using spatie/structure-discoverer.
26+
- In production and staging environments (wp_get_environment_type), results are cached for performance using a file-based cache.
27+
- Caching is skipped entirely when the constant `VIP_GO_APP_ENVIRONMENT` is defined.
28+
3. Reflect on each discovered class and skip any that:
29+
- are not instantiable,
30+
- do not implement `TenupFramework\ModuleInterface`.
31+
4. Instantiate the class.
32+
5. Fire an action before registration for each module: `tenup_framework_module_init__{slug}`
33+
- `slug` is the sanitized class FQN (backslashes replaced with dashes, then passed through `sanitize_title`).
34+
6. Sort modules by `load_order()` (lower numbers first) and iterate in order.
35+
7. For each module, call `register()` only if `can_register()` returns true.
36+
8. Store initialized modules for later retrieval.
37+
38+
Environment cache behavior
39+
- 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'`).
40+
- When it’s used: only in `production` and `staging` environment types (`wp_get_environment_type()`).
41+
- How to clear: delete the `class-loader-cache` folder; it will be rebuilt on next discovery.
42+
- How to disable in development: use `development` or `local` environment types, or define `VIP_GO_APP_ENVIRONMENT` to skip the cache.
43+
44+
Hooks
45+
- Action: `tenup_framework_module_init__{slug}` — fires before each module’s `register()` runs.
46+
- Parameters: the module instance.
47+
- Example:
48+
```php
49+
add_action( 'tenup_framework_module_init__yourvendor-yourplugin-features-frontendtweaks', function ( $module ) {
50+
// Inspect or adjust before register()
51+
} );
52+
```
53+
54+
Load order dependencies example
55+
- If Module B depends on Module A:
56+
```php
57+
class ModuleA implements ModuleInterface { use Module; public function load_order(): int { return 5; } }
58+
class ModuleB implements ModuleInterface { use Module; public function load_order(): int { return 10; } }
59+
```
60+
Lower numbers run first. Taxonomies typically use 9 so post types (default 10) can associate afterward.
61+
62+
Utilities:
63+
- `ModuleInitialization::get_module( $classFqn )` retrieves an initialized module instance by its fully qualified class name.
64+
- `ModuleInitialization::instance()->get_all_classes()` returns all initialized module instances keyed by slug.
65+
66+
## Module lifecycle in your code
67+
Your Module should be lightweight at construction time. Use the following methods effectively:
68+
- `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.
69+
- `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).
70+
- `register(): void` — attach your WordPress hooks/filters and perform setup here.
71+
72+
### Example
73+
```php
74+
namespace YourVendor\YourPlugin\Features;
75+
76+
use TenupFramework\ModuleInterface;
77+
use TenupFramework\Module;
78+
79+
class FrontendTweaks implements ModuleInterface {
80+
use Module; // default load_order() = 10
81+
82+
public function can_register(): bool {
83+
return ! is_admin(); // only on frontend
84+
}
85+
86+
public function register(): void {
87+
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue' ] );
88+
}
89+
90+
public function enqueue(): void {
91+
// ... enqueue assets here ...
92+
}
93+
}
94+
```
95+
96+
## Troubleshooting
97+
- Directory is required — If `init_classes()` is called with an empty or non-existent directory, a RuntimeException is thrown.
98+
- Class not initialized — Ensure the class is instantiable and implements `TenupFramework\ModuleInterface`.
99+
- Order of initialization — If you have inter-module dependencies, adjust `load_order()` to ensure prerequisites are registered first.
100+
- Observability — Use the `tenup_framework_module_init__{slug}` action to inspect or modify module instances before they register.

0 commit comments

Comments
 (0)