Use cases and output profiles
This document matches the current PHP layer: Use_Case, Use_Case_Registry, Use_Case_Storage, and built-in implementations such as Sandbox_Use_Case and Open_Graph_Image_Use_Case.
Terminology
Section titled “Terminology”| Term | Meaning |
|---|---|
| Use-case spec | Advisory dimensions (recommended_settings()), settings schema, and mapping sources for the editor. |
| Open Graph request context | Internal bucket in Open_Graph_Image_Use_Case for eligibility: singular, front_page, or non_singular — not a separate PHP “source context” API. |
Field values at render time come from the post (or preview) passed to Field_Resolver::collect_raw(); Open Graph settings (post types, front page, archives) live in the assignment settings object.
Layers
Section titled “Layers”- Use case — Defines schema, mapping sources, and
init()hooks (for examplewp_head, generation filters). - Registry — Stores registered types (slug → label, class, …) and boots active classes from the loader manifest option when
coverkit_initruns. - Storage — Reads/writes per-template assignments in post meta and rebuilds the loader manifest when that meta changes.
- Renderer —
Renderer::generate( string $generation_key, $template_id, ?int $post_id = null, array $options = array() )loads a template, applies field data, and runs the generator. Field wiring is in block attributes /{field}placeholders. Assignments live in template post meta_coverkit_use_cases(Use_Case_Storage::META_KEY).
Boot order
Section titled “Boot order”plugins_loadedpriority1:init()constructsUse_Case_Registry(constructorrequire_once’sincludes/use-cases/bootstrap.php, which schedulescoverkit_register_use_case()oncoverkit_initat priority5only; it does not register immediately).initpriority15:fire_coverkit_init()runsdo_action( 'coverkit_init' ).coverkit_initpriority5: built-in and plugin code callcoverkit_register_use_case()so types exist before boot.coverkit_initpriority10:Use_Case_Registry::boot()reads the optionUse_Case_Registry::LOADER_MANIFEST_OPTION(coverkit_usecases, list of active slugs), resolves each slug’s class from the registry, instantiates once, and callsUse_Case::maybe_init()(which runsinit()).- When template meta
_coverkit_use_caseschanges,Use_Case_Storage::rebuild_loader_manifest()rescans templates and rewritescoverkit_usecases.
Open Graph output is registered from Open_Graph_Image_Use_Case::init() (wp_head), not from the registry class.
| Path | Role |
|---|---|
includes/class-coverkit-use-case.php | Abstract Use_Case. |
includes/class-coverkit-use-case-registry.php | Use_Case_Registry singleton; type registration + manifest boot + reset() for tests. |
includes/class-coverkit-use-case-storage.php | Canonical _coverkit_use_cases meta, sanitization, loader manifest rebuild. |
includes/use-cases/class-coverkit-*-use-case.php | Concrete use cases; autoload: CoverKit\Foo_Bar_Use_Case → includes/use-cases/class-coverkit-foo-bar-use-case.php. |
Autoload rule: in coverkit.php, CoverKit\Use_Cases\Foo_Bar_Use_Case → includes/use-cases/class-coverkit-foo-bar-use-case.php.
Registering a custom use case
Section titled “Registering a custom use case”Register on coverkit_init at priority less than 10 so the slug exists before Use_Case_Registry::boot() runs.
Required args: label (string). Optional: class (Use_Case subclass), description, single (bool; install-wide singleton — at most one template site-wide may have active: true for this slug; default false), supports, public, allowed_widths. When class is omitted, the registry assigns a per-slug default subclass of Minimal_Use_Case (base Use_Case behavior only).
add_action( 'coverkit_init', static function (): void { \CoverKit\coverkit_register_use_case( 'my_packaging', array( 'label' => __( 'Packaging shot', 'my-plugin' ), 'description' => __( 'Square catalog image from product data.', 'my-plugin' ), 'class' => \MyPlugin\Packaging_Use_Case::class, ) ); }, 5);Label-only registration (no custom PHP class):
\CoverKit\coverkit_register_use_case( 'catalog_thumb', array( 'label' => __( 'Catalog thumbnail', 'my-plugin' ), ));Extending Use_Case
Section titled “Extending Use_Case”namespace MyPlugin;
use CoverKit\Use_Case;
final class Packaging_Use_Case extends Use_Case {
/** * @return array<string, mixed> */ protected static function recommended_settings(): array { return array( 'dimensions' => array( 'width' => 1080, 'height' => 1080 ), 'formats' => array( 'jpg', 'webp' ), ); }
/** * @return array<string, array<string, mixed>> */ protected static function use_case_settings_schema(): array { return array( 'format' => array( 'type' => 'string', 'label' => __( 'Format', 'my-plugin' ), 'control' => 'select', 'default' => 'jpg', 'options' => array( 'jpg' => __( 'JPG', 'my-plugin' ) ), ), ); }
/** * @return array<string, array<string, mixed>> */ protected static function use_case_mapping_sources(): array { return array( 'post_title' => array( 'required' => true ), 'site_logo' => array( 'recommended' => true ), ); }
protected function init(): void { // Optional: \add_action(), \add_filter(), … }}The slug returned by get_slug() comes from the registry entry for your class (the $slug argument to coverkit_register_use_case()).
Boot once, resolve templates at output time
Section titled “Boot once, resolve templates at output time”Use_Case_Registry::boot() instantiates each active slug once per request and calls maybe_init() (which runs init() at most once per class). Keep init() cheap: register WordPress hooks only. Resolve which templates are active inside those hook callbacks, not during boot.
| Pattern | When | API |
|---|---|---|
| First eligible | One output per request (login background, Open Graph meta) | Use_Case::find_active_template_id() or scan templates and apply custom eligibility (post type, archive context, etc.) |
| All active | One output per active template (multiple dashboard widgets, admin panels) | Use_Case::find_active_template_ids() and loop |
Both helpers read published templates with active: true for the use case slug (ascending ID). Use_Case_Storage::find_active_published_templates() backs the list; results are cached per request. Third parties may adjust the list via coverkit_use_case_active_template_ids.
Install-wide singletons (single: true at registration) are enforced when assignments are saved; at most one template should be active site-wide for those slugs.
Example — register one dashboard widget per active template:
protected function init(): void { \add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widgets' ) );}
public function register_dashboard_widgets(): void { foreach ( static::find_active_template_ids() as $template_id ) { $widget_id = 'my_use_case_' . $template_id; \wp_add_dashboard_widget( $widget_id, $this->widget_title_for( $template_id ), function () use ( $template_id ): void { $this->render_widget( $template_id ); } ); }}Mapping source catalog (editor field picker)
Section titled “Mapping source catalog (editor field picker)”Use_Case::get_mapping_sources() builds the list of mappable data sources for the block editor.
- Base catalog —
Use_Case::default_mapping_sources()holds shared built-ins (post fields, site fields, featured image, taxonomies, etc.). Entries are not markedrequiredhere so each use case controls requirements. - Per-class delta — Override
use_case_mapping_sources()so entries shallow-merge onto an existing id: for each key,array_merge( base[id], delta[id] )so the delta overrides individual keys (for examplerequired,recommended,label) while keeping base keys such asformatterwhen the delta omits them. - First-party prune —
Use_Case::filter_declared_mapping_sources( $sources )runs next (default: no-op). Subclasses override it to drop keys from the merged list without registering a WordPress filter when a use case needs a shorter picker. - Public extension — unchanged:
coverkit_use_case_mapping_sourcesandcoverkit_use_case_{$slug}_mapping_sourcesstill wrap the result for third-party plugins.
Resolver keys should match Field_Resolver::collect_builtin_raw(); use a 'field' entry in the source definition when the mapping id differs from the resolver key.
Built-in use cases
Section titled “Built-in use cases”Sandbox (sandbox)
Section titled “Sandbox (sandbox)”- Class:
CoverKit\Use_Cases\Sandbox_Use_Case(file:class-coverkit-sandbox-use-case.php). - Editor-only: registers no front-end hooks; exposes every built-in setting control and the full mapping catalog for testing and extension authors.
- Recommended: 300×300 (square, cropped); JPG and WebP.
- Mapping delta:
post_titlerequired;featured_image,post_link,post_excerpt,author,site_logorecommended. - Extensibility: demonstrates per-use-case field formatting via
coverkit_use_case_sandbox_format_field_value.
Featured image (featured_image)
Section titled “Featured image (featured_image)”- Class:
CoverKit\Use_Cases\Featured_Image_Use_Case. - Registered with
'public' => trueso front-end image URLs use the same anonymous access rules as Open Graph (published, viewable posts only). - Recommended: canvas-native dimensions (no fixed crop in the output profile); formats
png,jpg,gif,webp. - Mapping delta:
featured_imagerecommended (original_thumbnail_idattachment is unchanged and can feed mappings). - Runtime: Replaces
post_thumbnail_htmlandpost_thumbnail_urlon the front end when the post matches assignmentpost_typesettings; classic editor meta box and block editor Summary panel show a CoverKit preview via REST (/featured-image/preview/...and/use-case/featured_image/...). - Extensibility:
coverkit_featured_image_template_id,coverkit_featured_image_url,coverkit_featured_image_html_enabled.
Open Graph image (opengraph)
Section titled “Open Graph image (opengraph)”- Class:
CoverKit\Use_Cases\Open_Graph_Image_Use_Case(file:class-coverkit-open-graph-image-use-case.php). Slug must stay aligned with REST andRenderercallers using theopengraphgeneration key. - Recommended: 1200×630 (
1.91:1), formats includepng,jpg,gif,webp. - Mapping delta:
post_titlerequired;post_excerpt,author,site_logo,featured_imagerecommended. - Runtime: On
wp_head, prints oneog:imageblock for the first eligible published template in ascending ID order. Eligibility uses assignment settings: non-emptypost_typelist for singular views,apply_front_pagefor the front page,apply_non_singularfor archives/search (see class docblocks). Image URL uses the public use-case REST shapecoverkit/v1/use-case/opengraph/{template_id}/{post_id}.{ext}('public' => trueinincludes/use-cases/bootstrap.php). - Extensibility:
coverkit_opengraph_meta_enabled,coverkit_opengraph_request_post_id,coverkit_opengraph_template_id. Third-party SEO plugin integration is deferred and should be added only inside this class when implemented (seeinit()docblock).
Template meta: _coverkit_use_cases
Section titled “Template meta: _coverkit_use_cases”Per template, post meta _coverkit_use_cases (Use_Case_Storage::META_KEY) is an object map: use case id → assignment (active, settings, mappings, …). When assignments change, Use_Case_Storage::rebuild_loader_manifest() rebuilds coverkit_usecases so the registry can boot each active use case class once per request.
Optional summary meta _coverkit_active_use_cases (Use_Case_Storage::SUMMARY_META_KEY) stores a slug list for editor/debug reads.
tests/php/UseCaseRegistryTest.php— registry registration, boot from manifest,reset()behavior.tests/php/UseCaseSettingsSchemaTest.php— settings schema filters and sanitization paths.tests/php/SidebarImageUseCaseTest.php— sidebar mapping delta and schema.tests/php/OpenGraphImageUseCaseTest.php— Open Graph slug/schema/mapping,coverkit_pre_generateslug guard, and template eligibility (reflection on private helper + stubbed post meta).
Related
Section titled “Related”- php-backend.md — class-level index and field pipeline.
- hooks-and-extension-points.md — mapping, settings, and generation filters.