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..1e323f2 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -23,31 +23,59 @@ 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, from `yii3/debug` 0.1.0 -`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, - ], - ], -]; -``` +### Yii2, from `yii2-extensions/debug` 0.2.0 -Enabling the flag without `php-forge/inertia` installed fails with an explicit container error. The collector receives -the host `CapturePolicy` redaction callbacks automatically. +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. +`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. -### Yii2, one registration +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. 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 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. +### Earlier debugger releases + +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: -Inside the existing `YII_DEBUG` configuration guard: +```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}; @@ -55,23 +83,15 @@ 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(); ``` -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. - -`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. - -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. +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 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. */