From 7c812608c2173b19c1a5411e4de7f2ffe7d6811d Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 19 Sep 2026 14:11:27 -0300 Subject: [PATCH 1/3] docs: describe the automatic debugger wiring `yii2-extensions/debug` and `yii3/debug` provide for this package. --- CHANGELOG.md | 2 ++ docs/debugging.md | 46 +++++++++++++--------------------------------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9950bb4..e732859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.5.1 Under development +- docs: describe the automatic debugger wiring `yii2-extensions/debug` and `yii3/debug` provide for this package. + ## 0.5.0 September 18, 2026 - build!: require `php-forge/debug` `^0.3`. diff --git a/docs/debugging.md b/docs/debugging.md index bd8cea4..1286f25 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -23,43 +23,23 @@ The package ships one collector and one panel, `PHPForge\Inertia\Debug\InertiaCo `PHPForge\Inertia\Debug\InertiaPanel`; the host never reimplements collection or presentation. What differs between frameworks is only how the dispatcher reaches the `PHPForge\Inertia\Protocol` service. -### Yii3, one flag and no application code +### Yii3, nothing to add -`yii3/debug` registers the collector and panel behind a flag and attaches the collector as a listener; the container -autowires `Psr\EventDispatcher\EventDispatcherInterface` into `Protocol`, so the application adds nothing else. +`yii3/debug` registers the collector and the panel in its packaged parameters as soon as this package is installed, +routes `ProtocolResultCreated` to that collector from its packaged `events-web` group, and builds the collector with +the host `CapturePolicy` redaction callbacks. The container autowires `Psr\EventDispatcher\EventDispatcherInterface` +into `Protocol`, so the application adds nothing. Disable both packaged entries with `enabled => false` when the +capture is unwanted. -```php -return [ - 'yii3/debug' => [ - 'extensions' => [ - 'inertia' => true, - ], - ], -]; -``` - -Enabling the flag without `php-forge/inertia` installed fails with an explicit container error. The collector receives -the host `CapturePolicy` redaction callbacks automatically. - -### Yii2, one registration +### Yii2, nothing to add Yii2 has no framework-native PSR-14 dispatcher, and its DI container does not autowire optional constructor arguments. -`Protocol` emits exactly one event type, so `InertiaCollector` is its own single-listener dispatcher and the -application writes no PSR-14 code. - -Inside the existing `YII_DEBUG` configuration guard: - -```php -use PHPForge\Inertia\Debug\{InertiaCollector, InertiaPanel}; -use PHPForge\Inertia\Protocol; - -$inertiaCollector = new InertiaCollector(); - -// Keep the component ID already used by the application. -$config['components']['inertia']['protocol'] = Protocol::create(eventDispatcher: $inertiaCollector); -$config['modules']['debug']['collectors']['inertia'] = $inertiaCollector; -$config['modules']['debug']['panels']['inertia'] = new InertiaPanel(); -``` +`Protocol` emits exactly one event type, so `InertiaCollector` is its own single-listener dispatcher. +`yii2-extensions/debug` registers the collector and the panel once this package is installed, builds the collector +with the module capture policy, and hands it to the `inertia` component through +`yii\inertia\Manager::$eventDispatcher` before the request runs. A component that configures `eventDispatcher` or +`protocol` itself keeps what it configures; disable the packaged entries with `enabled => false` when the capture is +unwanted. If the application already owns a real PSR-14 dispatcher, register the collector as a listener on it and inject that dispatcher instead; never replace a populated dispatcher with an empty one. From 66104efea03907cd31c91887aa8c091cc1405391 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 19 Sep 2026 14:34:26 -0300 Subject: [PATCH 2/3] docs: standardize exception message casing in docblocks. --- src/Debug/InertiaCollector.php | 2 +- src/Debug/InertiaPanel.php | 2 +- src/Event/ProtocolResultCreated.php | 4 ++++ src/PageInput.php | 2 +- src/Prop/DeferredProp.php | 2 +- src/Prop/MergeProp.php | 2 +- src/Prop/OnceProp.php | 2 +- src/Prop/ScrollMetadata.php | 2 +- src/Protocol.php | 6 +++--- src/Resolution/PropDefinition.php | 2 +- src/Resolution/PropResolver.php | 4 ++-- src/ResolvedPageObserver.php | 5 +++++ src/Support/DotArray.php | 13 +++++++++++++ src/Support/JsonValue.php | 7 +++++-- 14 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/Debug/InertiaCollector.php b/src/Debug/InertiaCollector.php index 9f504bc..be897de 100644 --- a/src/Debug/InertiaCollector.php +++ b/src/Debug/InertiaCollector.php @@ -81,7 +81,7 @@ public function __invoke(ProtocolResultCreated $event): void /** * Returns the sanitized diagnostics for the protocol result observed in the active cycle. * - * @throws InvalidArgumentException If the observed result is not a supported Inertia protocol result. + * @throws InvalidArgumentException if the observed result is not a supported Inertia protocol result. * * @return array|null Sanitized diagnostics, or `null` when no protocol result was observed. */ diff --git a/src/Debug/InertiaPanel.php b/src/Debug/InertiaPanel.php index 017bbdf..61e7019 100644 --- a/src/Debug/InertiaPanel.php +++ b/src/Debug/InertiaPanel.php @@ -45,7 +45,7 @@ final class InertiaPanel extends Panel * * @param array $data Decoded, already-sanitized Inertia diagnostics. * - * @throws InvalidArgumentException If the capture declares an unsupported result type or malformed values. + * @throws InvalidArgumentException if the capture declares an unsupported result type or malformed values. * * @return PanelView Summary, overview, props table, and raw payload disclosure. */ diff --git a/src/Event/ProtocolResultCreated.php b/src/Event/ProtocolResultCreated.php index 9579803..1d4b162 100644 --- a/src/Event/ProtocolResultCreated.php +++ b/src/Event/ProtocolResultCreated.php @@ -12,5 +12,9 @@ */ final readonly class ProtocolResultCreated { + /** + * @param RequestContext $request Validated request context the protocol operation was resolved for. + * @param ProtocolResult $result Protocol result produced for the request. + */ public function __construct(public RequestContext $request, public ProtocolResult $result) {} } diff --git a/src/PageInput.php b/src/PageInput.php index ed125c2..d59d3ea 100644 --- a/src/PageInput.php +++ b/src/PageInput.php @@ -306,7 +306,7 @@ private static function validateErrors(array $errors): void * @param mixed $key The candidate key; must be a non-empty string without control characters or empty segments. * @param string $label Human-readable label used in the error message (for example, `'prop'` or `'shared prop'`). * - * @throws InvalidPageInputException When `$key` fails validation. + * @throws InvalidPageInputException when `$key` fails validation. */ private static function validateKey(mixed $key, string $label): void { diff --git a/src/Prop/DeferredProp.php b/src/Prop/DeferredProp.php index 29d51fc..9c91ada 100644 --- a/src/Prop/DeferredProp.php +++ b/src/Prop/DeferredProp.php @@ -104,7 +104,7 @@ public function value(): Closure|PropValue * * @param string $group The group name to validate. * - * @throws InvalidPropException If the group name is invalid. + * @throws InvalidPropException if the group name is invalid. */ private static function validateGroup(string $group): void { diff --git a/src/Prop/MergeProp.php b/src/Prop/MergeProp.php index c465ab3..62f5e0f 100644 --- a/src/Prop/MergeProp.php +++ b/src/Prop/MergeProp.php @@ -277,7 +277,7 @@ private static function unique(array $items): array * * @param string $path The path string to validate. * - * @throws InvalidPropException If the path is invalid. + * @throws InvalidPropException if the path is invalid. */ private static function validatePath(string $path): void { diff --git a/src/Prop/OnceProp.php b/src/Prop/OnceProp.php index 042fe75..6cee09b 100644 --- a/src/Prop/OnceProp.php +++ b/src/Prop/OnceProp.php @@ -136,7 +136,7 @@ public function value(): Closure|PropValue /** * Validates the cache key format. * - * @throws InvalidPropException When `$key` is empty or contains control characters or commas. + * @throws InvalidPropException when `$key` is empty or contains control characters or commas. */ private static function validateKey(string $key): void { diff --git a/src/Prop/ScrollMetadata.php b/src/Prop/ScrollMetadata.php index a1cecde..b23ca26 100644 --- a/src/Prop/ScrollMetadata.php +++ b/src/Prop/ScrollMetadata.php @@ -19,7 +19,7 @@ * @param int|string|null $nextPage The next page cursor, or `null` if there is no next page. * @param int|string|null $currentPage The current page cursor, or `null` if there is no current page. * - * @throws InvalidPropException When `$pageName` is empty or contains control characters. + * @throws InvalidPropException when `$pageName` is empty or contains control characters. */ public function __construct( public string $pageName, diff --git a/src/Protocol.php b/src/Protocol.php index f316a64..5a76061 100644 --- a/src/Protocol.php +++ b/src/Protocol.php @@ -71,7 +71,7 @@ public static function create( * @param RequestContext $request Validated request context from the framework adapter. * @param string $absoluteUrl Absolute HTTP/HTTPS URL of the target location. * - * @throws InvalidArgumentException When `$absoluteUrl` is not a valid absolute HTTP/HTTPS URL. + * @throws InvalidArgumentException when `$absoluteUrl` is not a valid absolute HTTP/HTTPS URL. * * @return LocationResult|RedirectResult Returns a {@see LocationResult} for Inertia requests, or a * {@see RedirectResult} for non-Inertia requests. @@ -144,7 +144,7 @@ public function page( * @param string $url Absolute or root-relative redirect target URL. * @param int $statusCode HTTP redirect status code (`301`, `302`, `303`, `307`, or `308`). * - * @throws InvalidArgumentException When `$url` is invalid or `$statusCode` is not an allowed redirect status. + * @throws InvalidArgumentException when `$url` is invalid or `$statusCode` is not an allowed redirect status. */ public function redirect( RequestContext $request, @@ -184,7 +184,7 @@ public function redirect( * @param RequestContext $request Validated request context from the framework adapter. * @param string $url Absolute or root-relative redirect target URL. * - * @throws InvalidArgumentException When the request's absolute URL cannot be parsed into scheme and host. + * @throws InvalidArgumentException when the request's absolute URL cannot be parsed into scheme and host. * * @return string Returns the absolute redirect URL. */ diff --git a/src/Resolution/PropDefinition.php b/src/Resolution/PropDefinition.php index 0e07686..4371dfd 100644 --- a/src/Resolution/PropDefinition.php +++ b/src/Resolution/PropDefinition.php @@ -72,7 +72,7 @@ public function deferred(): DeferredProp|null * * @param mixed $value Raw prop value, which may be a {@see PropValue} wrapper chain or a plain value. * - * @throws InvalidPropException When the wrapper chain exceeds 64 levels. + * @throws InvalidPropException when the wrapper chain exceeds 64 levels. * * @return PropDefinition Flattened definition of the prop value and its semantics. */ diff --git a/src/Resolution/PropResolver.php b/src/Resolution/PropResolver.php index da05b9a..c91e9c9 100644 --- a/src/Resolution/PropResolver.php +++ b/src/Resolution/PropResolver.php @@ -291,7 +291,7 @@ private function collectOnceMetadata(OnceProp|null $once, string $path): void * @param string $path Dot-notation path of the prop. * @param mixed $value The resolved value of the prop. * - * @throws InvalidPropException When scroll metadata is not a {@see ScrollMetadata}. + * @throws InvalidPropException when scroll metadata is not a {@see ScrollMetadata}. */ private function collectResolvedMetadata(PropDefinition $definition, string $path, mixed $value): void { @@ -543,7 +543,7 @@ private function pathMatchesPartialRequest(string $path): bool * @param string $path Dot-notation path used for error messages and metadata registration. * @param bool $parentWasResolved Whether the parent array was itself resolved, bypassing partial filters. * - * @throws PropResolutionException When a closure throws and the prop is not deferred. + * @throws PropResolutionException when a closure throws and the prop is not deferred. * * @return ResolvedProp The resolved prop value and inclusion status. */ diff --git a/src/ResolvedPageObserver.php b/src/ResolvedPageObserver.php index efac265..1c44754 100644 --- a/src/ResolvedPageObserver.php +++ b/src/ResolvedPageObserver.php @@ -18,6 +18,11 @@ */ public function __construct(private Closure $callback) {} + /** + * Forwards the page payload and its shared-prop keys to the configured callback. + * + * @param Page $page Resolved page whose payload and shared-prop keys are forwarded. + */ public function observe(Page $page): void { ($this->callback)($page->toArray(), $page->sharedProps()); diff --git a/src/Support/DotArray.php b/src/Support/DotArray.php index 7e65862..6ff73d6 100644 --- a/src/Support/DotArray.php +++ b/src/Support/DotArray.php @@ -17,6 +17,9 @@ */ final class DotArray { + /** + * Prevents direct instantiation. + */ private function __construct() {} /** @@ -24,6 +27,8 @@ private function __construct() {} * * @param array $values The flat, dot-notated key-value map to expand. * + * @throws InvalidPageInputException when a path segment already holds a non-array value. + * * @return array The expanded nested associative array. */ public static function expand(array $values): array @@ -42,6 +47,10 @@ public static function expand(array $values): array * * @param array $values The current result array to write into. * @param non-empty-list $segments Remaining path segments to traverse. + * @param mixed $value The value to write at the resolved path. + * @param string $path The original dot-notated key, reported in exception messages. + * + * @throws InvalidPageInputException when a segment along the path already holds a non-array value. * * @return array The updated result array with the value written at the given dot-notated path. */ @@ -73,6 +82,10 @@ private static function assign(array $values, array $segments, mixed $value, str * * @param array $values The current nested array level. * @param non-empty-list $segments Remaining path segments below the root. + * @param mixed $value The value to write at the final segment. + * @param string $path The original dot-notated key, reported in exception messages. + * + * @throws InvalidPageInputException when an intermediate segment already holds a non-array value. * * @return array The updated nested array with the value inserted at the final segment. */ diff --git a/src/Support/JsonValue.php b/src/Support/JsonValue.php index 7fd0b8c..5ce2926 100644 --- a/src/Support/JsonValue.php +++ b/src/Support/JsonValue.php @@ -27,6 +27,9 @@ final class JsonValue */ private const int MAX_DEPTH = 512; + /** + * Prevents direct instantiation. + */ private function __construct() {} /** @@ -36,10 +39,10 @@ private function __construct() {} * strings, and types that have no JSON representation. * * @param mixed $value The prop value to normalize. - * @param string $path Dot-notation path used in exception messages to identify the failing prop. + * @param string $path Dot-notation path used in exception messages to identify the failing prop. * @param int $depth Current recursion depth; throws when it exceeds 512. * - * @throws InvalidPropException When the value cannot be represented as JSON. + * @throws InvalidPropException when the value cannot be represented as JSON. * * @return mixed Normalized JSON-compatible value. */ From af991fcd1306c6fd4c1bbd5af1c0c56f1a7be603 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 19 Sep 2026 14:47:39 -0300 Subject: [PATCH 3/3] Appky fixed Coderabbitai review. --- docs/debugging.md | 60 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/debugging.md b/docs/debugging.md index 1286f25..1e323f2 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -23,7 +23,7 @@ The package ships one collector and one panel, `PHPForge\Inertia\Debug\InertiaCo `PHPForge\Inertia\Debug\InertiaPanel`; the host never reimplements collection or presentation. What differs between frameworks is only how the dispatcher reaches the `PHPForge\Inertia\Protocol` service. -### Yii3, nothing to add +### Yii3, from `yii3/debug` 0.1.0 `yii3/debug` registers the collector and the panel in its packaged parameters as soon as this package is installed, routes `ProtocolResultCreated` to that collector from its packaged `events-web` group, and builds the collector with @@ -31,7 +31,7 @@ the host `CapturePolicy` redaction callbacks. The container autowires `Psr\Event into `Protocol`, so the application adds nothing. Disable both packaged entries with `enabled => false` when the capture is unwanted. -### Yii2, nothing to add +### Yii2, from `yii2-extensions/debug` 0.2.0 Yii2 has no framework-native PSR-14 dispatcher, and its DI container does not autowire optional constructor arguments. `Protocol` emits exactly one event type, so `InertiaCollector` is its own single-listener dispatcher. @@ -42,16 +42,56 @@ with the module capture policy, and hands it to the `inertia` component through unwanted. If the application already owns a real PSR-14 dispatcher, register the collector as a listener on it and inject that -dispatcher instead; never replace a populated dispatcher with an empty one. +dispatcher instead; never replace a populated dispatcher with an empty one. Inject the dispatcher into the **actual** +`Protocol` service, not into a duplicate diagnostic-only one. Omitting it is safe: the protocol behaves normally and +the panel simply stays empty. -`yii2-extensions/debug` no longer ships an Inertia collector or panel, so the `inertia` ID is free: the module wraps -the portable objects in its generic adapters and groups them under Extensions. Pass `$policy->redact(...)` and -`$policy->redactUrl(...)` to `InertiaCollector` to apply the host redaction rules; the default keeps captured values -unchanged. Retain the existing module bootstrap, routing, access rules, and asset configuration. The public -`yii\inertia\Manager::$protocol` property accepts the instrumented core service. +### Earlier debugger releases -Inject the dispatcher into the **actual** Protocol service, not into a duplicate diagnostic-only one. Omitting it is -safe: the protocol behaves normally and the panel simply stays empty. +Earlier releases package no Inertia entry: they wrap the portable objects in their generic adapters and group them +under Extensions, and the `inertia` ID is free for the application to register. Without that registration the panel +stays empty, and without the redaction callbacks `InertiaCollector` keeps the captured values unchanged. + +Yii3, in the application `params`, `events-web`, and `di-web` groups: + +```php +use PHPForge\Debug\Capture\CapturePolicy; +use PHPForge\Inertia\Debug\{InertiaCollector, InertiaPanel}; +use PHPForge\Inertia\Event\ProtocolResultCreated; + +// params.php +'yii3/debug' => [ + 'collectors' => ['inertia' => InertiaCollector::class], + 'panels' => ['inertia' => InertiaPanel::class], +], + +// events-web.php +ProtocolResultCreated::class => [InertiaCollector::class], + +// di-web.php +InertiaCollector::class => static fn(CapturePolicy $policy): InertiaCollector => new InertiaCollector( + $policy->redact(...), + $policy->redactUrl(...), +), +``` + +Yii2, inside the `YII_DEBUG` configuration guard, keeping the component ID the application already uses: + +```php +use PHPForge\Inertia\Debug\{InertiaCollector, InertiaPanel}; +use PHPForge\Inertia\Protocol; + +$inertiaCollector = new InertiaCollector(); + +$config['components']['inertia']['protocol'] = Protocol::create(eventDispatcher: $inertiaCollector); +$config['modules']['debug']['collectors']['inertia'] = $inertiaCollector; +$config['modules']['debug']['panels']['inertia'] = new InertiaPanel(); +``` + +Pass the host `PHPForge\Debug\Capture\CapturePolicy` closures, `$policy->redact(...)` and `$policy->redactUrl(...)`, +to the `InertiaCollector` constructor to apply the module redaction rules. Retain the existing module bootstrap, +routing, access rules, and asset configuration; the public `yii\inertia\Manager::$protocol` property accepts the +instrumented core service. ## Lifecycle, privacy, and errors