From d77a9f48cf3ba24bb02018c082cf1a1eddc7b148 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:08:56 +0000 Subject: [PATCH 01/12] Import class names in the remaining PHPDoc references Finish the imported short-name convention for PHPDoc class references across the remaining packages and test files, and reference Telescope's scheduled-task events by their imported classes. - Convert fully and partially qualified PHPDoc class references in api-client, broadcasting, collections, container, di, encryption, facade-documenter, filesystem, fortify, hashing, json-schema, jwt, log, notifications, permission, redis, reflection, sanctum, scout, sentry, session, socialite and telescope, plus 18 test files. Executable references to the same classes use the new imports too. - Alias imports where the short name collides: BaseBroadcaster, RoleModel, PermissionModel, HasApiTokensContract and SentryLog. - ScheduleWatcher imports ScheduledTaskFinished and ScheduledTaskFailed instead of going through the Console\Events namespace. - Lcobucci's signing and verification key getters gain their titles and return Key, which every path returns and every caller requires. - SupportStringableTest's stringable() helper takes mixed input, matching its callers and the Stringable constructor. Prose, generated facade and protoc annotations, quoted class-string conditional types, the native SplPriorityQueue parent and the TRANSLATE_ALL default expression keep their qualified names. Validation: composer lint:fix, composer analyse, FacadeDocblocksTest, the FacadeDocumenter, Jwt, JsonSchema, Telescope, Sentry, Di and Cache/Redis suites, each edited test file, and the Reverb server integration test against the fixture server. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/api-client/src/PendingRequest.php | 15 ++- src/broadcasting/src/BroadcastManager.php | 3 +- src/collections/src/Collection.php | 6 +- src/collections/src/helpers.php | 13 +- src/container/src/Attributes/BindWhen.php | 5 +- src/di/src/Aop/ProxyCallVisitor.php | 5 +- src/encryption/src/Encrypter.php | 10 +- .../src/EncryptionServiceProvider.php | 2 +- src/facade-documenter/facade.php | 119 +++++++++--------- src/filesystem/src/FilesystemAdapter.php | 2 +- src/filesystem/src/FilesystemManager.php | 10 +- .../src/InteractsWithTwoFactorState.php | 3 +- src/fortify/src/PasskeyAuthenticatable.php | 3 +- src/hashing/src/HashManager.php | 2 +- src/json-schema/src/Deserializer.php | 28 +++-- src/json-schema/src/JsonSchema.php | 24 ++-- src/json-schema/src/JsonSchemaTypeFactory.php | 5 +- src/jwt/src/Providers/Lcobucci.php | 23 ++-- src/log/src/functions.php | 2 +- .../src/DatabaseNotificationCollection.php | 2 +- src/permission/src/Contracts/Permission.php | 5 +- src/permission/src/Contracts/Role.php | 5 +- src/redis/src/RedisManager.php | 2 +- src/redis/src/RedisProxy.php | 2 +- src/reflection/src/helpers.php | 4 +- src/sanctum/src/HasApiTokens.php | 2 +- src/sanctum/src/PersonalAccessToken.php | 5 +- src/sanctum/src/SanctumGuard.php | 5 +- src/scout/src/Searchable.php | 2 +- src/sentry/src/SentryServiceProvider.php | 17 +-- src/session/src/Middleware/StartSession.php | 3 +- src/session/src/SessionManager.php | 2 +- .../src/Two/Concerns/InteractsWithJwks.php | 3 +- .../src/Jobs/ProcessPendingUpdates.php | 3 +- .../src/Watchers/ScheduleWatcher.php | 15 +-- .../AuthPasswordResetServiceProviderTest.php | 3 +- tests/Cache/Redis/RedisCacheTestCase.php | 17 +-- .../ContainerResolveNonInstantiableTest.php | 2 +- .../Concerns/InteractsWithDatabaseTest.php | 3 +- .../Auth/EloquentUserProviderCacheTest.php | 3 +- .../Cache/Redis/PhpRedisCacheLockTest.php | 3 +- .../Generators/EnumMakeCommandTest.php | 6 +- .../Generators/InterfaceMakeCommandTest.php | 6 +- .../Generators/TraitMakeCommandTest.php | 6 +- ...PreventRequestForgeryServerRuntimeTest.php | 9 +- tests/Integration/Reverb/Fixtures/server.php | 3 +- tests/Redis/Operations/FlushByPatternTest.php | 3 +- tests/Routing/RouteRegistrarTest.php | 35 +++--- .../Features/LogLogsIntegrationTest.php | 7 +- .../Features/ViewEngineDecoratorTest.php | 5 +- .../Fixtures/FacebookTestProviderStub.php | 3 +- .../Fixtures/GoogleTestProviderStub.php | 3 +- tests/Support/SupportStringableTest.php | 5 +- 53 files changed, 271 insertions(+), 208 deletions(-) diff --git a/src/api-client/src/PendingRequest.php b/src/api-client/src/PendingRequest.php index ff32055478..34fee56b9c 100644 --- a/src/api-client/src/PendingRequest.php +++ b/src/api-client/src/PendingRequest.php @@ -5,7 +5,9 @@ namespace Hypervel\ApiClient; use BadMethodCallException; +use Closure; use GuzzleHttp\ClientInterface; +use GuzzleHttp\Cookie\SetCookie; use GuzzleHttp\Promise\PromiseInterface; use Hypervel\ApiClient\Concerns\HasContext; use Hypervel\Container\Container; @@ -16,19 +18,22 @@ use Hypervel\Http\Client\Request as HttpRequest; use Hypervel\Http\Client\Response as HttpResponse; use Hypervel\Pipeline\Pipeline; +use Hypervel\Support\Collection; use Hypervel\Support\Facades\Http; +use Hypervel\Support\Stringable; use Hypervel\Support\Traits\Conditionable; use Hypervel\Support\Traits\ForwardsCalls; use InvalidArgumentException; use JsonSerializable; use LogicException; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamInterface; use Throwable; /** * @template TResource of ApiResource = ApiResource * @method static baseUrl(string $url) - * @method static withBody(null|resource|\Psr\Http\Message\StreamInterface|string|\Hypervel\Support\Stringable $content, string $contentType = 'application/json') + * @method static withBody(null|resource|StreamInterface|string|Stringable $content, string $contentType = 'application/json') * @method static asJson() * @method static asForm() * @method static attach(array|string $name, resource|string $contents = '', ?string $filename = null, array $headers = []) @@ -46,15 +51,15 @@ * @method static withToken(string $token, string $type = 'Bearer') * @method static withUserAgent(bool|string $userAgent) * @method static withUrlParameters(array $parameters = []) - * @method static withCookie(\GuzzleHttp\Cookie\SetCookie $cookie) + * @method static withCookie(SetCookie $cookie) * @method static withCookies(array $cookies, string $domain) * @method static maxRedirects(int $max) * @method static withoutRedirecting() * @method static withoutVerifying() - * @method static sink(\Psr\Http\Message\StreamInterface|resource|string $to) + * @method static sink(resource|StreamInterface|string $to) * @method static timeout(float|int $seconds) * @method static connectTimeout(float|int $seconds) - * @method static retry(array|int $times, \Closure|int $sleepMilliseconds = 0, ?callable $when = null, bool $throw = true) + * @method static retry(array|int $times, Closure|int $sleepMilliseconds = 0, ?callable $when = null, bool $throw = true) * @method static withOptions(array $options) * @method static withMiddleware(callable $middleware) * @method static withRequestMiddleware(callable $middleware) @@ -69,7 +74,7 @@ * @method static throwUnless(bool|callable $condition, ?callable $callback = null) * @method static dump() * @method static dd() - * @method static stub(callable|\Hypervel\Support\Collection $callback) + * @method static stub(callable|Collection $callback) * @method static preventStrayRequests(bool $prevent = true) * @method static allowStrayRequests(array $only) * @method static truncateExceptionsAt(int $length) diff --git a/src/broadcasting/src/BroadcastManager.php b/src/broadcasting/src/BroadcastManager.php index 12f3d01347..7f51ca4b1b 100644 --- a/src/broadcasting/src/BroadcastManager.php +++ b/src/broadcasting/src/BroadcastManager.php @@ -8,6 +8,7 @@ use Closure; use GuzzleHttp\Client as GuzzleClient; use Hypervel\Broadcasting\Broadcasters\AblyBroadcaster; +use Hypervel\Broadcasting\Broadcasters\Broadcaster as BaseBroadcaster; use Hypervel\Broadcasting\Broadcasters\LogBroadcaster; use Hypervel\Broadcasting\Broadcasters\NullBroadcaster; use Hypervel\Broadcasting\Broadcasters\PusherBroadcaster; @@ -48,7 +49,7 @@ use function Hypervel\Support\enum_value; /** - * @mixin \Hypervel\Broadcasting\Broadcasters\Broadcaster + * @mixin BaseBroadcaster */ class BroadcastManager implements BroadcastingFactoryContract { diff --git a/src/collections/src/Collection.php b/src/collections/src/Collection.php index 6afa883370..bb2c83b174 100644 --- a/src/collections/src/Collection.php +++ b/src/collections/src/Collection.php @@ -27,12 +27,12 @@ * @template-covariant TValue * * @implements ArrayAccess - * @implements \Hypervel\Support\Enumerable + * @implements Enumerable */ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable, Transient { /** - * @use \Hypervel\Support\Traits\EnumeratesValues + * @use EnumeratesValues */ use EnumeratesValues; @@ -93,7 +93,7 @@ public function all(): array /** * Get a lazy collection for the items in this collection. * - * @return \Hypervel\Support\LazyCollection + * @return LazyCollection */ public function lazy(): LazyCollection { diff --git a/src/collections/src/helpers.php b/src/collections/src/helpers.php index 87b042de3d..6aef7a8c39 100644 --- a/src/collections/src/helpers.php +++ b/src/collections/src/helpers.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Hypervel\Contracts\Support\Arrayable; use Hypervel\Support\Arr; use Hypervel\Support\Collection; @@ -12,8 +13,8 @@ * @template TKey of array-key * @template TValue * - * @param null|\Hypervel\Contracts\Support\Arrayable|iterable $value - * @return \Hypervel\Support\Collection + * @param null|Arrayable|iterable $value + * @return Collection */ function collect($value = []): Collection { @@ -258,7 +259,7 @@ function last($array) * @template TValue * @template TArgs * - * @param (\Closure(TArgs): TValue)|TValue $value + * @param (Closure(TArgs): TValue)|TValue $value * @param TArgs ...$args * @return TValue */ @@ -278,9 +279,9 @@ function value($value, ...$args) * @template TDefaultArgs * * @param mixed $condition - * @param (\Closure(TArgs): TValue)|TValue $value - * @param (\Closure(TDefaultArgs): TDefault)|TDefault $default - * @return ($condition is \Closure ? TDefault|TValue : ($condition is empty ? TDefault : TValue)) + * @param (Closure(TArgs): TValue)|TValue $value + * @param (Closure(TDefaultArgs): TDefault)|TDefault $default + * @return ($condition is Closure ? TDefault|TValue : ($condition is empty ? TDefault : TValue)) */ function when($condition, $value, $default = null) { diff --git a/src/container/src/Attributes/BindWhen.php b/src/container/src/Attributes/BindWhen.php index 4b82aca2d8..f0b20ff609 100644 --- a/src/container/src/Attributes/BindWhen.php +++ b/src/container/src/Attributes/BindWhen.php @@ -6,6 +6,7 @@ use Attribute; use Closure; +use Hypervel\Contracts\Container\Container; /** * Define a binding selected by a boot-stable condition. @@ -25,7 +26,7 @@ class BindWhen /** * The condition that determines if the binding should apply. * - * @var Closure(\Hypervel\Contracts\Container\Container): bool + * @var Closure(Container): bool */ public Closure $condition; @@ -33,7 +34,7 @@ class BindWhen * Create a new attribute instance. * * @param class-string $concrete - * @param Closure(\Hypervel\Contracts\Container\Container): bool $condition + * @param Closure(Container): bool $condition */ public function __construct(string $concrete, Closure $condition) { diff --git a/src/di/src/Aop/ProxyCallVisitor.php b/src/di/src/Aop/ProxyCallVisitor.php index 6cdece0676..fb787637e3 100644 --- a/src/di/src/Aop/ProxyCallVisitor.php +++ b/src/di/src/Aop/ProxyCallVisitor.php @@ -33,6 +33,7 @@ use PhpParser\Node\Scalar\MagicConst\Method as MagicConstMethod; use PhpParser\Node\Scalar\MagicConst\Trait_ as MagicConstTrait; use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; @@ -775,7 +776,7 @@ private function uniqueVariableName(string $base, array $usedVariables): string /** * Find every named class-like and its namespace. * - * @return array}> + * @return array}> */ private function findNamedClassLikes(array $nodes): array { @@ -809,7 +810,7 @@ private function findNamedClassLikes(array $nodes): array /** * Collect function import aliases for the target namespace. * - * @param array $statements + * @param array $statements * @return array */ private function collectFunctionImports(array $statements): array diff --git a/src/encryption/src/Encrypter.php b/src/encryption/src/Encrypter.php index 3f0bd3bc9e..f8b5e81a77 100644 --- a/src/encryption/src/Encrypter.php +++ b/src/encryption/src/Encrypter.php @@ -78,7 +78,7 @@ public static function generateKey(string $cipher): string /** * Encrypt the given value. * - * @throws \Hypervel\Contracts\Encryption\EncryptException + * @throws EncryptException */ public function encrypt(#[SensitiveParameter] mixed $value, bool $serialize = true): string { @@ -116,7 +116,7 @@ public function encrypt(#[SensitiveParameter] mixed $value, bool $serialize = tr /** * Encrypt a string without serialization. * - * @throws \Hypervel\Contracts\Encryption\EncryptException + * @throws EncryptException */ public function encryptString(#[SensitiveParameter] string $value): string { @@ -126,7 +126,7 @@ public function encryptString(#[SensitiveParameter] string $value): string /** * Decrypt the given value. * - * @throws \Hypervel\Contracts\Encryption\DecryptException + * @throws DecryptException */ public function decrypt(string $payload, bool $unserialize = true): mixed { @@ -193,7 +193,7 @@ public function decrypt(string $payload, bool $unserialize = true): mixed /** * Decrypt the given string without unserialization. * - * @throws \Hypervel\Contracts\Encryption\DecryptException + * @throws DecryptException */ public function decryptString(string $payload): string { @@ -211,7 +211,7 @@ protected function hash(#[SensitiveParameter] string $iv, #[SensitiveParameter] /** * Get the JSON array from the given payload. * - * @throws \Hypervel\Contracts\Encryption\DecryptException + * @throws DecryptException */ protected function getJsonPayload(string $payload): array { diff --git a/src/encryption/src/EncryptionServiceProvider.php b/src/encryption/src/EncryptionServiceProvider.php index 1e25877c10..1410c4ff3c 100644 --- a/src/encryption/src/EncryptionServiceProvider.php +++ b/src/encryption/src/EncryptionServiceProvider.php @@ -73,7 +73,7 @@ protected function parseKey(#[SensitiveParameter] array $config): string /** * Extract the encryption key from the given configuration. * - * @throws \Hypervel\Encryption\MissingAppKeyException + * @throws MissingAppKeyException */ protected function key(#[SensitiveParameter] array $config): string { diff --git a/src/facade-documenter/facade.php b/src/facade-documenter/facade.php index c0c359062c..a5e110b719 100755 --- a/src/facade-documenter/facade.php +++ b/src/facade-documenter/facade.php @@ -30,6 +30,7 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; @@ -255,8 +256,8 @@ function debug($message) /** * Resolve the proxies for the Facade. * - * @param \ReflectionClass $class - * @return \Hypervel\Support\Collection + * @param ReflectionClass $class + * @return Collection */ function resolveProxies($class) { @@ -268,7 +269,7 @@ function resolveProxies($class) * Determine the fully qualified class name. * * @param string $class - * @param \ReflectionClass $source + * @param ReflectionClass $source * @return class-string */ function determineFqcn($class, $source) @@ -319,7 +320,7 @@ function resolveCanonicalClassName(string $name): ?string /** * Resolve a class name relative to the method that declares it. * - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method */ function resolveRelativeClassName($method, string $name): ?string { @@ -336,8 +337,8 @@ function resolveRelativeClassName($method, string $name): ?string /** * Resolve the classes referenced in the @see docblocks. * - * @param \ReflectionClass $class - * @return \Hypervel\Support\Collection + * @param ReflectionClass $class + * @return Collection */ function resolveDocSees($class) { @@ -348,8 +349,8 @@ function resolveDocSees($class) /** * Resolve the classes referenced methods in the @methods docblocks. * - * @param \ReflectionClass $class - * @return \Hypervel\Support\Collection<\PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode> + * @param ReflectionClass $class + * @return Collection */ function resolveDocMethods($class) { @@ -385,8 +386,8 @@ function resolveDocMethods($class) /** * Resolve the parameters type from the @param docblocks. * - * @param \ReflectionMethodDecorator $method - * @param \ReflectionParameter $parameter + * @param ReflectionMethodDecorator $method + * @param ReflectionParameter $parameter * @return null|string */ function resolveDocParamType($method, $parameter) @@ -420,7 +421,7 @@ function resolveDocParamType($method, $parameter) /** * Resolve the return type from the @return docblock. * - * @param \ReflectionMethodDecorator $method + * @param ReflectionMethodDecorator $method * @return null|string */ function resolveReturnDocType($method) @@ -442,7 +443,7 @@ function resolveReturnDocType($method) * Parse the given docblock. * * @param string $docblock - * @return \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode + * @return PhpDocNode */ function parseDocblock($docblock) { @@ -475,8 +476,8 @@ function parseDocblock($docblock) /** * Resolve the types from the docblock. * - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method - * @param \PHPStan\PhpDocParser\Ast\Type\TypeNode $typeNode + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method + * @param TypeNode $typeNode * @return null|string */ function resolveDocblockTypes($method, $typeNode, int $depth = 1) @@ -725,8 +726,8 @@ function resolveDocblockTypes($method, $typeNode, int $depth = 1) /** * Resolve and flatten the members of a PHPDoc union. * - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method - * @param \PHPStan\PhpDocParser\Ast\Type\TypeNode $typeNode + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method + * @param TypeNode $typeNode * @return list */ function resolveDocblockUnionMembers($method, $typeNode, int $depth): array @@ -772,7 +773,7 @@ function resolveDocblockUnionMembers($method, $typeNode, int $depth): array /** * Resolve a method template type by name. * - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method */ function resolveTemplateType($method, string $name, int $depth): ?string { @@ -823,8 +824,8 @@ function isArrayRefinementType(string $name): bool * matching constants and unioning their inferred value types. Returns 'mixed' * when the constant or class cannot be resolved. * - * @param \PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode $node - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method + * @param ConstFetchNode $node + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method * @return string */ function resolveConstFetchType($node, $method) @@ -859,8 +860,8 @@ function resolveConstFetchType($node, $method) /** * Resolve key-of<...> / value-of<...> when the inner type is a ConstFetchNode. * - * @param \PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode $node - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method + * @param ConstFetchNode $node + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method * @param bool $keyType true to resolve the key type, false to resolve the value type * @return string */ @@ -900,7 +901,7 @@ function resolveKeyOrValueOf($node, $method, $keyType) * null when the class cannot be found. * * @param string $className - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method * @return null|string */ function resolveClassConstantClass($className, $method) @@ -943,8 +944,8 @@ function inferValueType($value) * Unknown node types must return false so new PHPDoc syntax falls back to a * conservative union instead of producing a misleading conditional. * - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method - * @param \PHPStan\PhpDocParser\Ast\Type\TypeNode $typeNode + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method + * @param TypeNode $typeNode * @return bool */ function canPreserveConditionalTarget($method, $typeNode) @@ -1140,8 +1141,8 @@ function isKnownOptionalDependency($type) /** * Resolve the declared type. * - * @param \ReflectionMethodDecorator $method - * @param null|\ReflectionType $type + * @param ReflectionMethodDecorator $method + * @param null|ReflectionType $type * @return null|string */ function resolveType($method, $type) @@ -1193,7 +1194,7 @@ function resolveType($method, $type) * * @param string $docblock * @param string $tag - * @return \Hypervel\Support\Collection + * @return Collection */ function resolveDocTags($docblock, $tag) { @@ -1210,8 +1211,8 @@ function resolveDocTags($docblock, $tag) /** * Resolve method names that should be excluded from a facade docblock. * - * @param \ReflectionClass $facade - * @return \Hypervel\Support\Collection + * @param ReflectionClass $facade + * @return Collection */ function resolveIgnoredMethods($facade) { @@ -1229,9 +1230,9 @@ function resolveIgnoredMethods($facade) /** * Recursively resolve docblock mixins. * - * @param \ReflectionClass $class - * @param \Hypervel\Support\Collection $encountered - * @return \Hypervel\Support\Collection<\ReflectionClass> + * @param ReflectionClass $class + * @param Collection $encountered + * @return Collection */ function resolveDocMixins($class, $encountered = new Collection) { @@ -1253,8 +1254,8 @@ function resolveDocMixins($class, $encountered = new Collection) /** * Resolve the classes referenced methods in the @methods docblocks. * - * @param \ReflectionMethodDecorator $method - * @return \Hypervel\Support\Collection + * @param ReflectionMethodDecorator $method + * @return Collection */ function resolveDocParameters($method) { @@ -1265,7 +1266,7 @@ function resolveDocParameters($method) /** * Determine if the method is magic. * - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method + * @param MethodTagValueNode|ReflectionMethodDecorator $method * @return bool */ function isMagic($method) @@ -1276,7 +1277,7 @@ function isMagic($method) /** * Determine if the method is marked as @internal. * - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method + * @param MethodTagValueNode|ReflectionMethodDecorator $method * @return bool */ function isInternal($method) @@ -1291,7 +1292,7 @@ function isInternal($method) /** * Determine if the method is deprecated. * - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method + * @param MethodTagValueNode|ReflectionMethodDecorator $method * @return bool */ function isDeprecated($method) @@ -1306,7 +1307,7 @@ function isDeprecated($method) /** * Determine if the method is for a builtin contract. * - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method + * @param MethodTagValueNode|ReflectionMethodDecorator $method * @return bool */ function fulfillsBuiltinInterface($method) @@ -1325,7 +1326,7 @@ function fulfillsBuiltinInterface($method) /** * Resolve the methods name. * - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method + * @param MethodTagValueNode|ReflectionMethodDecorator $method * @return string */ function resolveName($method) @@ -1338,8 +1339,8 @@ function resolveName($method) /** * Resolve the classes methods. * - * @param \ReflectionClass $class - * @return \Hypervel\Support\Collection<\PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator> + * @param ReflectionClass $class + * @return Collection */ function resolveMethods($class) { @@ -1354,8 +1355,8 @@ function resolveMethods($class) /** * Determine if the given method conflicts with a Facade method. * - * @param \ReflectionClass $facade - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method + * @param ReflectionClass $facade + * @param MethodTagValueNode|ReflectionMethodDecorator $method * @return bool */ function conflictsWithFacade($facade, $method) @@ -1374,8 +1375,8 @@ function conflictsWithFacade($facade, $method) /** * Normalise the method details into a easier format to work with. * - * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode|\ReflectionMethodDecorator $method - * @return array|\PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode + * @param MethodTagValueNode|ReflectionMethodDecorator $method + * @return array|MethodTagValueNode */ function normaliseDetails($method) { @@ -1405,8 +1406,8 @@ function normaliseDetails($method) /** * Resolve the parameters for the method. * - * @param \ReflectionMethodDecorator $method - * @return \Hypervel\Support\Collection + * @param ReflectionMethodDecorator $method + * @return Collection */ function resolveParameters($method) { @@ -1423,8 +1424,8 @@ function resolveParameters($method) * the exact direct or nested trait source range holds the relevant `use` * statements. * - * @param \ReflectionClassDocblockContext|\ReflectionMethodDecorator $method - * @return \ReflectionClass + * @param ReflectionClassDocblockContext|ReflectionMethodDecorator $method + * @return ReflectionClass */ function resolveImportSource($method) { @@ -1458,7 +1459,7 @@ function resolveImportSource($method) * Resolve every direct and nested trait used by a class. * * @param array $resolved - * @return list<\ReflectionClass> + * @return list */ function resolveNestedTraits(ReflectionClass $class, array &$resolved = []): array { @@ -1480,8 +1481,8 @@ function resolveNestedTraits(ReflectionClass $class, array &$resolved = []): arr /** * Resolve the classes imports. * - * @param \ReflectionClass $class - * @return \Hypervel\Support\Collection + * @param ReflectionClass $class + * @return Collection */ function resolveClassImports($class) { @@ -1579,7 +1580,7 @@ function isIgnorablePhpToken(PhpToken $token): bool /** * Return the next significant PHP token index. * - * @param list<\PhpToken> $tokens + * @param list $tokens */ function nextSignificantPhpTokenIndex(array $tokens, int $offset): ?int { @@ -1595,7 +1596,7 @@ function nextSignificantPhpTokenIndex(array $tokens, int $offset): ?int /** * Parse the class imports from one namespace-level use statement. * - * @param list<\PhpToken> $tokens + * @param list $tokens * @return array */ function parseClassUseStatement(array $tokens): array @@ -1642,8 +1643,8 @@ function parseClassUseStatement(array $tokens): array /** * Split use-statement tokens into comma-separated imports. * - * @param list<\PhpToken> $tokens - * @return list> + * @param list $tokens + * @return list> */ function splitClassUseSegments(array $tokens): array { @@ -1671,7 +1672,7 @@ function splitClassUseSegments(array $tokens): array /** * Convert parsed use segments into an alias map. * - * @param list> $segments + * @param list> $segments * @return array */ function parseClassUseSegments(array $segments, string $prefix = ''): array @@ -1721,7 +1722,7 @@ function parseClassUseSegments(array $segments, string $prefix = ''): array * matching its representation keeps generated metadata stable after formatting. * * @param string $method - * @param \Hypervel\Support\Collection $imports + * @param Collection $imports * @return string */ function shortenImportedGlobalTypes($method, $imports) @@ -1863,7 +1864,7 @@ public function sourceClass(): ReflectionClass } /** - * @mixin \ReflectionMethod + * @mixin ReflectionMethod */ class ReflectionMethodDecorator { diff --git a/src/filesystem/src/FilesystemAdapter.php b/src/filesystem/src/FilesystemAdapter.php index ea7236e100..a2245d247b 100644 --- a/src/filesystem/src/FilesystemAdapter.php +++ b/src/filesystem/src/FilesystemAdapter.php @@ -56,7 +56,7 @@ use Throwable; /** - * @mixin \League\Flysystem\FilesystemOperator + * @mixin FilesystemOperator */ class FilesystemAdapter implements CloudFilesystemContract { diff --git a/src/filesystem/src/FilesystemManager.php b/src/filesystem/src/FilesystemManager.php index e756daed07..3ae222df0b 100644 --- a/src/filesystem/src/FilesystemManager.php +++ b/src/filesystem/src/FilesystemManager.php @@ -41,12 +41,12 @@ use function Hypervel\Support\enum_value; /** - * @method \Hypervel\Contracts\Filesystem\Filesystem|mixed when(null|\Closure|mixed $value = null, null|callable $callback = null, null|callable $default = null) - * @method \Hypervel\Contracts\Filesystem\Filesystem|mixed unless(null|\Closure|mixed $value = null, null|callable $callback = null, null|callable $default = null) + * @method Filesystem|mixed when(null|Closure|mixed $value = null, null|callable $callback = null, null|callable $default = null) + * @method Filesystem|mixed unless(null|Closure|mixed $value = null, null|callable $callback = null, null|callable $default = null) * - * @mixin \Hypervel\Contracts\Filesystem\Filesystem - * @mixin \Hypervel\Filesystem\FilesystemAdapter - * @mixin \Hypervel\Filesystem\ClientPooledFilesystem + * @mixin Filesystem + * @mixin FilesystemAdapter + * @mixin ClientPooledFilesystem */ class FilesystemManager implements FactoryContract { diff --git a/src/fortify/src/InteractsWithTwoFactorState.php b/src/fortify/src/InteractsWithTwoFactorState.php index 203e74533c..6d8c3c1662 100644 --- a/src/fortify/src/InteractsWithTwoFactorState.php +++ b/src/fortify/src/InteractsWithTwoFactorState.php @@ -6,9 +6,10 @@ use Hypervel\Container\Container; use Hypervel\Fortify\Actions\DisableTwoFactorAuthentication; +use Hypervel\Foundation\Http\FormRequest; /** - * @mixin \Hypervel\Foundation\Http\FormRequest + * @mixin FormRequest */ trait InteractsWithTwoFactorState { diff --git a/src/fortify/src/PasskeyAuthenticatable.php b/src/fortify/src/PasskeyAuthenticatable.php index df44c94bfa..f3792ee425 100644 --- a/src/fortify/src/PasskeyAuthenticatable.php +++ b/src/fortify/src/PasskeyAuthenticatable.php @@ -4,10 +4,11 @@ namespace Hypervel\Fortify; +use Hypervel\Fortify\Contracts\PasskeyUser; use Hypervel\Passkeys\PasskeyAuthenticatable as BasePasskeyAuthenticatable; /** - * @phpstan-require-implements \Hypervel\Fortify\Contracts\PasskeyUser + * @phpstan-require-implements PasskeyUser */ trait PasskeyAuthenticatable { diff --git a/src/hashing/src/HashManager.php b/src/hashing/src/HashManager.php index 05f2d36ffd..30cd418716 100644 --- a/src/hashing/src/HashManager.php +++ b/src/hashing/src/HashManager.php @@ -9,7 +9,7 @@ use SensitiveParameter; /** - * @mixin \Hypervel\Contracts\Hashing\Hasher + * @mixin Hasher */ class HashManager extends Manager implements Hasher { diff --git a/src/json-schema/src/Deserializer.php b/src/json-schema/src/Deserializer.php index 1b579ac83d..056879a5ad 100644 --- a/src/json-schema/src/Deserializer.php +++ b/src/json-schema/src/Deserializer.php @@ -4,6 +4,10 @@ namespace Hypervel\JsonSchema; +use Hypervel\JsonSchema\Types\ArrayType; +use Hypervel\JsonSchema\Types\IntegerType; +use Hypervel\JsonSchema\Types\NumberType; +use Hypervel\JsonSchema\Types\StringType; use InvalidArgumentException; use stdClass; @@ -323,9 +327,9 @@ protected function buildObject(array $schema, array $refs = []): Types\ObjectTyp * * @throws InvalidArgumentException */ - protected function buildArray(array $schema, array $refs = []): Types\ArrayType + protected function buildArray(array $schema, array $refs = []): ArrayType { - $type = new Types\ArrayType; + $type = new ArrayType; if (array_key_exists('items', $schema) && $schema['items'] !== true && $schema['items'] !== []) { $items = $this->ensureSchemaFragmentIsArray($schema['items'], 'the [items] keyword'); @@ -357,9 +361,9 @@ protected function buildArray(array $schema, array $refs = []): Types\ArrayType * * @param array $schema */ - protected function buildString(array $schema): Types\StringType + protected function buildString(array $schema): StringType { - $type = new Types\StringType; + $type = new StringType; $type = $this->applyIntegerBounds($type, $schema, 'minLength', 'maxLength'); @@ -387,9 +391,9 @@ protected function buildString(array $schema): Types\StringType * * @param array $schema */ - protected function buildInteger(array $schema): Types\IntegerType + protected function buildInteger(array $schema): IntegerType { - return $this->applyNumericBounds(new Types\IntegerType, $schema, $this->toInteger(...)); + return $this->applyNumericBounds(new IntegerType, $schema, $this->toInteger(...)); } /** @@ -397,15 +401,15 @@ protected function buildInteger(array $schema): Types\IntegerType * * @param array $schema */ - protected function buildNumber(array $schema): Types\NumberType + protected function buildNumber(array $schema): NumberType { - return $this->applyNumericBounds(new Types\NumberType, $schema); + return $this->applyNumericBounds(new NumberType, $schema); } /** * Apply the numeric bound keywords to the given integer or number type. * - * @template TType of Types\IntegerType|Types\NumberType + * @template TType of IntegerType|NumberType * * @param TType $type * @param array $schema @@ -414,7 +418,7 @@ protected function buildNumber(array $schema): Types\NumberType * * @throws InvalidArgumentException */ - protected function applyNumericBounds(Types\IntegerType|Types\NumberType $type, array $schema, ?callable $cast = null): Types\IntegerType|Types\NumberType + protected function applyNumericBounds(IntegerType|NumberType $type, array $schema, ?callable $cast = null): IntegerType|NumberType { $cast ??= static fn (int|float $value) => $value; @@ -436,7 +440,7 @@ protected function applyNumericBounds(Types\IntegerType|Types\NumberType $type, /** * Apply integer-valued minimum and maximum keywords to an array or string type. * - * @template TType of Types\ArrayType|Types\StringType + * @template TType of ArrayType|StringType * * @param TType $type * @param array $schema @@ -444,7 +448,7 @@ protected function applyNumericBounds(Types\IntegerType|Types\NumberType $type, * * @throws InvalidArgumentException */ - protected function applyIntegerBounds(Types\ArrayType|Types\StringType $type, array $schema, string $minimumKeyword, string $maximumKeyword): Types\ArrayType|Types\StringType + protected function applyIntegerBounds(ArrayType|StringType $type, array $schema, string $minimumKeyword, string $maximumKeyword): ArrayType|StringType { foreach ([$minimumKeyword => 'min', $maximumKeyword => 'max'] as $keyword => $method) { if (! array_key_exists($keyword, $schema)) { diff --git a/src/json-schema/src/JsonSchema.php b/src/json-schema/src/JsonSchema.php index d2dded713f..30775c568a 100644 --- a/src/json-schema/src/JsonSchema.php +++ b/src/json-schema/src/JsonSchema.php @@ -5,18 +5,26 @@ namespace Hypervel\JsonSchema; use Closure; +use Hypervel\JsonSchema\Types\AnyOfType; +use Hypervel\JsonSchema\Types\ArrayType; +use Hypervel\JsonSchema\Types\BooleanType; +use Hypervel\JsonSchema\Types\IntegerType; +use Hypervel\JsonSchema\Types\NumberType; +use Hypervel\JsonSchema\Types\ObjectType; +use Hypervel\JsonSchema\Types\StringType; use Hypervel\JsonSchema\Types\Type; +use Hypervel\JsonSchema\Types\UnionType; use InvalidArgumentException; /** - * @method static Types\ObjectType object(Closure|array $properties = []) - * @method static Types\AnyOfType anyOf(Closure|array $schemas) - * @method static Types\IntegerType integer() - * @method static Types\NumberType number() - * @method static Types\StringType string() - * @method static Types\BooleanType boolean() - * @method static Types\ArrayType array() - * @method static Types\UnionType union(array $types) + * @method static ObjectType object(Closure|array $properties = []) + * @method static AnyOfType anyOf(Closure|array $schemas) + * @method static IntegerType integer() + * @method static NumberType number() + * @method static StringType string() + * @method static BooleanType boolean() + * @method static ArrayType array() + * @method static UnionType union(array $types) */ class JsonSchema { diff --git a/src/json-schema/src/JsonSchemaTypeFactory.php b/src/json-schema/src/JsonSchemaTypeFactory.php index 4d7ce94563..1894b0397d 100644 --- a/src/json-schema/src/JsonSchemaTypeFactory.php +++ b/src/json-schema/src/JsonSchemaTypeFactory.php @@ -6,13 +6,14 @@ use Closure; use Hypervel\Contracts\JsonSchema\JsonSchema as JsonSchemaContract; +use Hypervel\JsonSchema\Types\Type; class JsonSchemaTypeFactory extends JsonSchema implements JsonSchemaContract { /** * Create a new object schema instance. * - * @param array|(Closure(JsonSchemaTypeFactory): array) $properties + * @param array|(Closure(JsonSchemaTypeFactory): array) $properties */ public function object(Closure|array $properties = []): Types\ObjectType { @@ -76,7 +77,7 @@ public function union(array $types): Types\UnionType /** * Create a new anyOf schema instance. * - * @param array|(Closure(JsonSchemaTypeFactory): array) $schemas + * @param array|(Closure(JsonSchemaTypeFactory): array) $schemas */ public function anyOf(Closure|array $schemas): Types\AnyOfType { diff --git a/src/jwt/src/Providers/Lcobucci.php b/src/jwt/src/Providers/Lcobucci.php index 4d0f139a8e..c8b19c6c54 100644 --- a/src/jwt/src/Providers/Lcobucci.php +++ b/src/jwt/src/Providers/Lcobucci.php @@ -19,6 +19,7 @@ use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa; +use Lcobucci\JWT\Token\Plain; use Lcobucci\JWT\Token\RegisteredClaims; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Throwable; @@ -64,7 +65,7 @@ public function __construct(string $secret, string $algo, array $keys, ?Configur /** * Create a JSON Web Token. * - * @throws \Hypervel\Jwt\Exceptions\JwtException + * @throws JwtException */ public function encode(array $payload): string { @@ -82,12 +83,12 @@ public function encode(array $payload): string /** * Decode a JSON Web Token. * - * @throws \Hypervel\Jwt\Exceptions\JwtException + * @throws JwtException */ public function decode(string $token): array { try { - /** @var \Lcobucci\JWT\Token\Plain */ + /** @var Plain */ $token = $this->config->parser()->parse($token); } catch (Throwable $exception) { throw new TokenInvalidException( @@ -177,7 +178,7 @@ protected function buildConfig(): Configuration * * Signer is rebuilt before config because buildConfig() reads $this->signer. * - * @throws \Hypervel\Jwt\Exceptions\JwtException + * @throws JwtException */ protected function onConfigurationChanged(): void { @@ -188,7 +189,7 @@ protected function onConfigurationChanged(): void /** * Get the signer instance. * - * @throws \Hypervel\Jwt\Exceptions\JwtException + * @throws JwtException */ protected function getSigner(): Signer { @@ -208,9 +209,11 @@ protected function isAsymmetric(): bool } /** - * @throws \Hypervel\Jwt\Exceptions\JwtException + * Get the key used to sign the tokens. + * + * @throws JwtException */ - protected function getSigningKey(): mixed + protected function getSigningKey(): Key { if ($this->isAsymmetric()) { if (! $privateKey = $this->getPrivateKey()) { @@ -228,9 +231,11 @@ protected function getSigningKey(): mixed } /** - * @throws \Hypervel\Jwt\Exceptions\JwtException + * Get the key used to verify the tokens. + * + * @throws JwtException */ - protected function getVerificationKey(): mixed + protected function getVerificationKey(): Key { if ($this->isAsymmetric()) { if (! $public = $this->getPublicKey()) { diff --git a/src/log/src/functions.php b/src/log/src/functions.php index 9de785a4e9..da3b33ee28 100644 --- a/src/log/src/functions.php +++ b/src/log/src/functions.php @@ -12,7 +12,7 @@ /** * Log a debug message to the logs. * - * @return ($message is null ? \Psr\Log\LoggerInterface : null) + * @return ($message is null ? LoggerInterface : null) */ function log(Arrayable|Jsonable|Stringable|array|string|null $message = null, array $context = []): ?LoggerInterface { diff --git a/src/notifications/src/DatabaseNotificationCollection.php b/src/notifications/src/DatabaseNotificationCollection.php index da61c96412..a342c75801 100644 --- a/src/notifications/src/DatabaseNotificationCollection.php +++ b/src/notifications/src/DatabaseNotificationCollection.php @@ -10,7 +10,7 @@ * @template TKey of array-key * @template TModel of DatabaseNotification * - * @extends \Hypervel\Database\Eloquent\Collection + * @extends Collection */ class DatabaseNotificationCollection extends Collection { diff --git a/src/permission/src/Contracts/Permission.php b/src/permission/src/Contracts/Permission.php index 1127c595b2..ebfd08d7db 100644 --- a/src/permission/src/Contracts/Permission.php +++ b/src/permission/src/Contracts/Permission.php @@ -6,6 +6,7 @@ use Hypervel\Database\Eloquent\Relations\BelongsToMany; use Hypervel\Permission\Exceptions\PermissionDoesNotExist; +use Hypervel\Permission\Models\Permission as PermissionModel; use UnitEnum; /** @@ -13,9 +14,9 @@ * @property string $name * @property null|string $guard_name * - * @mixin \Hypervel\Permission\Models\Permission + * @mixin PermissionModel * - * @phpstan-require-extends \Hypervel\Permission\Models\Permission + * @phpstan-require-extends PermissionModel */ interface Permission { diff --git a/src/permission/src/Contracts/Role.php b/src/permission/src/Contracts/Role.php index 4712af5828..b01b260e59 100644 --- a/src/permission/src/Contracts/Role.php +++ b/src/permission/src/Contracts/Role.php @@ -6,6 +6,7 @@ use Hypervel\Database\Eloquent\Relations\BelongsToMany; use Hypervel\Permission\Exceptions\RoleDoesNotExist; +use Hypervel\Permission\Models\Role as RoleModel; use UnitEnum; /** @@ -13,9 +14,9 @@ * @property string $name * @property null|string $guard_name * - * @mixin \Hypervel\Permission\Models\Role + * @mixin RoleModel * - * @phpstan-require-extends \Hypervel\Permission\Models\Role + * @phpstan-require-extends RoleModel */ interface Role { diff --git a/src/redis/src/RedisManager.php b/src/redis/src/RedisManager.php index 673d401c08..703429cab9 100644 --- a/src/redis/src/RedisManager.php +++ b/src/redis/src/RedisManager.php @@ -21,7 +21,7 @@ use function Hypervel\Support\enum_value; /** - * @mixin \Hypervel\Redis\RedisProxy + * @mixin RedisProxy */ class RedisManager implements FactoryContract, ConnectionContract { diff --git a/src/redis/src/RedisProxy.php b/src/redis/src/RedisProxy.php index 3d17b12a5c..ea14eb0cfa 100644 --- a/src/redis/src/RedisProxy.php +++ b/src/redis/src/RedisProxy.php @@ -34,7 +34,7 @@ * the same connection within a coroutine. * * @method bool|Redis discard() - * @mixin \Hypervel\Redis\RedisConnection + * @mixin RedisConnection */ class RedisProxy implements ConnectionContract { diff --git a/src/reflection/src/helpers.php b/src/reflection/src/helpers.php index c49e23ff90..4a8b6bb75c 100644 --- a/src/reflection/src/helpers.php +++ b/src/reflection/src/helpers.php @@ -16,7 +16,7 @@ * @param array $eager * @return TValue * - * @throws \ReflectionException + * @throws ReflectionException */ function lazy(string|Closure $class, Closure|int $callback = 0, int $options = 0, array $eager = []): object { @@ -64,7 +64,7 @@ public function typeFromParameter(Closure $callback): string * @param array $eager * @return TValue * - * @throws \ReflectionException + * @throws ReflectionException */ function proxy(string|Closure $class, Closure|int $callback = 0, int $options = 0, array $eager = []): object { diff --git a/src/sanctum/src/HasApiTokens.php b/src/sanctum/src/HasApiTokens.php index 399ca94a49..5d495aaa6c 100644 --- a/src/sanctum/src/HasApiTokens.php +++ b/src/sanctum/src/HasApiTokens.php @@ -13,7 +13,7 @@ use function Hypervel\Support\enum_value; /** - * @template TToken of \Hypervel\Sanctum\Contracts\HasAbilities = \Hypervel\Sanctum\PersonalAccessToken + * @template TToken of HasAbilities = PersonalAccessToken */ trait HasApiTokens { diff --git a/src/sanctum/src/PersonalAccessToken.php b/src/sanctum/src/PersonalAccessToken.php index fad74e3e5c..425e1e6d6d 100644 --- a/src/sanctum/src/PersonalAccessToken.php +++ b/src/sanctum/src/PersonalAccessToken.php @@ -9,6 +9,7 @@ use Hypervel\Container\Container; use Hypervel\Contracts\Auth\Authenticatable; use Hypervel\Contracts\Cache\Repository as CacheRepository; +use Hypervel\Database\Eloquent\Builder; use Hypervel\Database\Eloquent\Model; use Hypervel\Database\Eloquent\Relations\MorphTo; use Hypervel\Sanctum\Contracts\HasAbilities; @@ -22,10 +23,10 @@ * @property array $abilities * @property string $token * @property string $name - * @property \Hypervel\Database\Eloquent\Model $tokenable + * @property Model $tokenable * @property ?CarbonInterface $last_used_at * @property ?CarbonInterface $expires_at - * @method static \Hypervel\Database\Eloquent\Builder where(string $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') + * @method static Builder where(string $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') * @method static static|null find(mixed $id, array $columns = ['*']) */ class PersonalAccessToken extends Model implements HasAbilities diff --git a/src/sanctum/src/SanctumGuard.php b/src/sanctum/src/SanctumGuard.php index 98077a1308..f84468ebc0 100644 --- a/src/sanctum/src/SanctumGuard.php +++ b/src/sanctum/src/SanctumGuard.php @@ -14,6 +14,7 @@ use Hypervel\Contracts\Container\Container; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Http\Request; +use Hypervel\Sanctum\Contracts\HasApiTokens as HasApiTokensContract; use Hypervel\Sanctum\Events\TokenAuthenticated; use Hypervel\Support\Traits\Macroable; use InvalidArgumentException; @@ -111,7 +112,7 @@ public function user(): ?Authenticatable } if ($this->supportsTokens($user)) { - /** @var Authenticatable&\Hypervel\Sanctum\Contracts\HasApiTokens $tokenUser */ + /** @var Authenticatable&HasApiTokensContract $tokenUser */ $tokenUser = $user; $user = $tokenUser->withAccessToken(new TransientToken); } @@ -130,7 +131,7 @@ public function user(): ?Authenticatable $tokenable = $model::findTokenable($accessToken); if ($this->supportsTokens($tokenable)) { - /** @var Authenticatable&\Hypervel\Sanctum\Contracts\HasApiTokens $tokenable */ + /** @var Authenticatable&HasApiTokensContract $tokenable */ $user = $tokenable->withAccessToken($accessToken); if ($this->events?->hasListeners(TokenAuthenticated::class)) { diff --git a/src/scout/src/Searchable.php b/src/scout/src/Searchable.php index 918ba4fe82..d2d2c659a2 100644 --- a/src/scout/src/Searchable.php +++ b/src/scout/src/Searchable.php @@ -27,7 +27,7 @@ /** * Provides full-text search capabilities to Eloquent models. * - * @mixin \Hypervel\Database\Eloquent\Model + * @mixin Model */ trait Searchable { diff --git a/src/sentry/src/SentryServiceProvider.php b/src/sentry/src/SentryServiceProvider.php index 16aa16a47f..4673a9ad6d 100644 --- a/src/sentry/src/SentryServiceProvider.php +++ b/src/sentry/src/SentryServiceProvider.php @@ -52,6 +52,7 @@ use RuntimeException; use Sentry\ClientBuilder; use Sentry\Integration as SdkIntegration; +use Sentry\Integration\IntegrationInterface; use Sentry\Logger\DebugFileLogger; use Sentry\SentrySdk; use Sentry\Serializer\RepresentationSerializer; @@ -248,7 +249,7 @@ protected function configureAndRegisterClient(): void if ($options->hasDefaultIntegrations()) { // Remove the default error and fatal exception listeners to let the framework handle those // through the exception handler and log channel integration - $integrations = array_filter($integrations, static function (SdkIntegration\IntegrationInterface $integration): bool { + $integrations = array_filter($integrations, static function (IntegrationInterface $integration): bool { if ($integration instanceof SdkIntegration\ErrorListenerIntegration) { return false; } @@ -362,7 +363,7 @@ protected function bindEvents(): void $handler = new EventHandler($this->app, $userConfig); try { - /** @var \Hypervel\Contracts\Events\Dispatcher $dispatcher */ + /** @var Dispatcher $dispatcher */ $dispatcher = $this->app->make('events'); $handler->subscribe($dispatcher); @@ -459,7 +460,7 @@ private function bindTracingEvents(array $tracingConfig): void $handler = new TracingEventHandler($tracingConfig); try { - /** @var \Hypervel\Contracts\Events\Dispatcher $dispatcher */ + /** @var Dispatcher $dispatcher */ $dispatcher = $this->app->make('events'); $handler->subscribe($dispatcher); @@ -707,7 +708,7 @@ protected function registerAboutCommandIntegration(): void /** * Resolve the integrations from the user configuration with the container. * - * @return SdkIntegration\IntegrationInterface[] + * @return IntegrationInterface[] */ private function resolveIntegrationsFromUserConfig(array $userIntegrations): array { @@ -719,16 +720,16 @@ private function resolveIntegrationsFromUserConfig(array $userIntegrations): arr $integrations = []; foreach ($integrationsToResolve as $userIntegration) { - if ($userIntegration instanceof SdkIntegration\IntegrationInterface) { + if ($userIntegration instanceof IntegrationInterface) { $integrations[] = $userIntegration; } elseif (is_string($userIntegration)) { $resolvedIntegration = $this->app->make($userIntegration); - if (! $resolvedIntegration instanceof SdkIntegration\IntegrationInterface) { + if (! $resolvedIntegration instanceof IntegrationInterface) { throw new RuntimeException( sprintf( 'Sentry integrations must be an instance of `%s` got `%s`.', - SdkIntegration\IntegrationInterface::class, + IntegrationInterface::class, $resolvedIntegration::class ) ); @@ -739,7 +740,7 @@ private function resolveIntegrationsFromUserConfig(array $userIntegrations): arr throw new RuntimeException( sprintf( 'Sentry integrations must either be a valid container reference or an instance of `%s`.', - SdkIntegration\IntegrationInterface::class + IntegrationInterface::class ) ); } diff --git a/src/session/src/Middleware/StartSession.php b/src/session/src/Middleware/StartSession.php index 5d3172a13c..92efffa082 100644 --- a/src/session/src/Middleware/StartSession.php +++ b/src/session/src/Middleware/StartSession.php @@ -9,6 +9,7 @@ use Hypervel\Context\CoroutineContext; use Hypervel\Contracts\Cache\Factory as CacheFactoryContract; use Hypervel\Contracts\Cache\LockProvider; +use Hypervel\Contracts\Cache\Repository; use Hypervel\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract; use Hypervel\Contracts\Session\Session; use Hypervel\Http\Request; @@ -70,7 +71,7 @@ protected function handleRequestWhileBlocking(Request $request, Session $session $lockFor = $request->route()->locksFor() ?: $this->manager->defaultRouteBlockLockSeconds(); - /** @var \Hypervel\Contracts\Cache\Repository&LockProvider $store */ + /** @var LockProvider&Repository $store */ $store = $this->cache->store($this->manager->blockDriver()); $lock = $store ->lock('session:' . $session->getId(), (int) $lockFor) diff --git a/src/session/src/SessionManager.php b/src/session/src/SessionManager.php index 45c07357e3..ba89b5efeb 100644 --- a/src/session/src/SessionManager.php +++ b/src/session/src/SessionManager.php @@ -18,7 +18,7 @@ use function Hypervel\Support\enum_value; /** - * @mixin \Hypervel\Session\Store + * @mixin Store */ class SessionManager extends Manager { diff --git a/src/socialite/src/Two/Concerns/InteractsWithJwks.php b/src/socialite/src/Two/Concerns/InteractsWithJwks.php index da46e479fb..49eccb4059 100644 --- a/src/socialite/src/Two/Concerns/InteractsWithJwks.php +++ b/src/socialite/src/Two/Concerns/InteractsWithJwks.php @@ -7,12 +7,13 @@ use Firebase\JWT\JWK; use Firebase\JWT\JWT; use Firebase\JWT\SignatureInvalidException; +use Hypervel\Socialite\Two\AbstractProvider; use Psr\Http\Message\ResponseInterface; use SensitiveParameter; use UnexpectedValueException; /** - * @phpstan-require-extends \Hypervel\Socialite\Two\AbstractProvider + * @phpstan-require-extends AbstractProvider */ trait InteractsWithJwks { diff --git a/src/telescope/src/Jobs/ProcessPendingUpdates.php b/src/telescope/src/Jobs/ProcessPendingUpdates.php index 8a11e54f3d..edad1a5aed 100644 --- a/src/telescope/src/Jobs/ProcessPendingUpdates.php +++ b/src/telescope/src/Jobs/ProcessPendingUpdates.php @@ -11,6 +11,7 @@ use Hypervel\Queue\SerializesModels; use Hypervel\Support\Collection; use Hypervel\Telescope\Contracts\EntriesRepository; +use Hypervel\Telescope\EntryUpdate; class ProcessPendingUpdates implements ShouldQueue { @@ -22,7 +23,7 @@ class ProcessPendingUpdates implements ShouldQueue /** * Create a new job instance. * - * @param Collection $pendingUpdates the pending entry updates + * @param Collection $pendingUpdates the pending entry updates * @param int $attempt the number of times the job has been attempted */ public function __construct( diff --git a/src/telescope/src/Watchers/ScheduleWatcher.php b/src/telescope/src/Watchers/ScheduleWatcher.php index c2705922f2..54f7d1c0ba 100644 --- a/src/telescope/src/Watchers/ScheduleWatcher.php +++ b/src/telescope/src/Watchers/ScheduleWatcher.php @@ -4,7 +4,8 @@ namespace Hypervel\Telescope\Watchers; -use Hypervel\Console\Events; +use Hypervel\Console\Events\ScheduledTaskFailed; +use Hypervel\Console\Events\ScheduledTaskFinished; use Hypervel\Console\Scheduling\CallbackEvent; use Hypervel\Console\Scheduling\Event; use Hypervel\Context\CoroutineContext; @@ -31,21 +32,21 @@ public function register(Application $app): void $app->make(Dispatcher::class) ->listen([ - Events\ScheduledTaskFinished::class, - Events\ScheduledTaskFailed::class, + ScheduledTaskFinished::class, + ScheduledTaskFailed::class, ], [$this, 'recordCommand']); } /** * Record a scheduled command that was executed. */ - public function recordCommand(Events\ScheduledTaskFailed|Events\ScheduledTaskFinished $event): void + public function recordCommand(ScheduledTaskFailed|ScheduledTaskFinished $event): void { if (! Telescope::isRecording()) { return; } - if ($event instanceof Events\ScheduledTaskFinished) { + if ($event instanceof ScheduledTaskFinished) { $this->recordFinishedCommand($event); return; @@ -57,7 +58,7 @@ public function recordCommand(Events\ScheduledTaskFailed|Events\ScheduledTaskFin /** * Record a successfully finished scheduled command. */ - protected function recordFinishedCommand(Events\ScheduledTaskFinished $event): void + protected function recordFinishedCommand(ScheduledTaskFinished $event): void { $task = $event->task; $exitCode = $task->exitCode(); @@ -88,7 +89,7 @@ protected function recordFinishedCommand(Events\ScheduledTaskFinished $event): v /** * Record a failed scheduled command. */ - protected function recordFailedCommand(Events\ScheduledTaskFailed $event): void + protected function recordFailedCommand(ScheduledTaskFailed $event): void { $outcome = [ 'status' => 'failed', diff --git a/tests/Auth/AuthPasswordResetServiceProviderTest.php b/tests/Auth/AuthPasswordResetServiceProviderTest.php index c3c0e35c17..62dd68e176 100644 --- a/tests/Auth/AuthPasswordResetServiceProviderTest.php +++ b/tests/Auth/AuthPasswordResetServiceProviderTest.php @@ -11,6 +11,7 @@ use Hypervel\Contracts\Foundation\Application; use Hypervel\Tests\TestCase; use Mockery as m; +use Mockery\MockInterface; class AuthPasswordResetServiceProviderTest extends TestCase { @@ -39,7 +40,7 @@ public function testEventRebindRefreshesAnAlreadyResolvedPasswordManager(): void /** * Register the provider and return its event-rebind callback. * - * @return array{Application&m\MockInterface, Closure} + * @return array{Application&MockInterface, Closure} */ private function registerProvider(): array { diff --git a/tests/Cache/Redis/RedisCacheTestCase.php b/tests/Cache/Redis/RedisCacheTestCase.php index d636b7263f..d4f88b45ef 100644 --- a/tests/Cache/Redis/RedisCacheTestCase.php +++ b/tests/Cache/Redis/RedisCacheTestCase.php @@ -15,6 +15,7 @@ use Hypervel\Tests\Redis\Fixtures\FakeRedisClient; use Hypervel\Tests\Redis\Fixtures\PhpRedisConnectionStub; use Mockery as m; +use Mockery\MockInterface; use Redis; use RedisCluster; @@ -67,9 +68,9 @@ protected function setUp(): void * unexpected fallthrough to real Redis connections when expectations * don't match. * - * @return m\MockInterface|PhpRedisConnection connection with _mockClient property for setting expectations + * @return MockInterface|PhpRedisConnection connection with _mockClient property for setting expectations */ - protected function mockConnection(): m\MockInterface|PhpRedisConnection + protected function mockConnection(): MockInterface|PhpRedisConnection { // Anonymous mock - not bound to Redis extension class // This prevents fallthrough to real Redis when expectations don't match @@ -121,9 +122,9 @@ protected function mockConnection(): m\MockInterface|PhpRedisConnection * by its class definition, triggering cluster mode (sequential commands * instead of pipelines). * - * @return m\MockInterface|PhpRedisClusterConnection connection with _mockClient property for setting expectations + * @return MockInterface|PhpRedisClusterConnection connection with _mockClient property for setting expectations */ - protected function mockClusterConnection(): m\MockInterface|PhpRedisClusterConnection + protected function mockClusterConnection(): MockInterface|PhpRedisClusterConnection { // Mock that identifies as RedisCluster for instanceof checks $client = m::mock(RedisCluster::class); @@ -165,7 +166,7 @@ protected function mockClusterConnection(): m\MockInterface|PhpRedisClusterConne protected function createRedisFactory( RedisConnection $connection, string $connectionName = 'default' - ): m\MockInterface|RedisFactory { + ): MockInterface|RedisFactory { $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldReceive('withConnection') ->andReturnUsing(fn (callable $callback) => $callback($connection)); @@ -184,13 +185,13 @@ protected function createRedisFactory( /** * Create a RedisStore with a mocked connection. * - * @param m\MockInterface|RedisConnection $connection the mocked connection (from mockConnection()) + * @param MockInterface|RedisConnection $connection the mocked connection (from mockConnection()) * @param string $prefix cache key prefix * @param string $connectionName Redis connection name * @param null|string $tagMode optional tag mode ('any' or 'all'). If provided, setTagMode() is called. */ protected function createStore( - m\MockInterface|RedisConnection $connection, + MockInterface|RedisConnection $connection, string $prefix = 'prefix:', string $connectionName = 'default', ?string $tagMode = null, @@ -225,7 +226,7 @@ protected function createStore( * @param string $prefix cache key prefix * @param string $connectionName Redis connection name * @param null|string $tagMode optional tag mode ('any' or 'all') - * @return array{0: RedisStore, 1: m\MockInterface, 2: m\MockInterface} [store, clusterClient, connection] + * @return array{0: RedisStore, 1: MockInterface, 2: MockInterface} [store, clusterClient, connection] */ protected function createClusterStore( string $prefix = 'prefix:', diff --git a/tests/Container/ContainerResolveNonInstantiableTest.php b/tests/Container/ContainerResolveNonInstantiableTest.php index b5afe99443..83d9cb5eaa 100644 --- a/tests/Container/ContainerResolveNonInstantiableTest.php +++ b/tests/Container/ContainerResolveNonInstantiableTest.php @@ -66,7 +66,7 @@ public function __construct(?TestInterface $testObject = null, int $i = 0) class VariadicParentClass { /** - * @var \Hypervel\Tests\Container\ChildClass + * @var ChildClass */ public $child; diff --git a/tests/Foundation/Testing/Concerns/InteractsWithDatabaseTest.php b/tests/Foundation/Testing/Concerns/InteractsWithDatabaseTest.php index 8806db5f34..329bd9b244 100644 --- a/tests/Foundation/Testing/Concerns/InteractsWithDatabaseTest.php +++ b/tests/Foundation/Testing/Concerns/InteractsWithDatabaseTest.php @@ -4,6 +4,7 @@ namespace Hypervel\Tests\Foundation\Testing\Concerns; +use Faker\Generator; use Hypervel\Database\Connection; use Hypervel\Database\Query\Expression; use Hypervel\Database\Query\Grammars\SQLiteGrammar; @@ -254,7 +255,7 @@ public function testFactoryUsesConfiguredFakerLocale() // Trigger faker initialization by calling make() $factory->make(); - /** @var \Faker\Generator $faker */ + /** @var Generator $faker */ $faker = $fakerProperty->getValue($factory); $providerClasses = array_map(fn ($provider) => get_class($provider), $faker->getProviders()); diff --git a/tests/Integration/Auth/EloquentUserProviderCacheTest.php b/tests/Integration/Auth/EloquentUserProviderCacheTest.php index 4fdfa26388..3faad48b07 100644 --- a/tests/Integration/Auth/EloquentUserProviderCacheTest.php +++ b/tests/Integration/Auth/EloquentUserProviderCacheTest.php @@ -11,6 +11,7 @@ use Hypervel\Cache\CacheManager; use Hypervel\Cache\FileStore; use Hypervel\Cache\ModelCacheCoordinator; +use Hypervel\Contracts\Auth\Authenticatable; use Hypervel\Contracts\Cache\Repository as CacheRepository; use Hypervel\Contracts\Foundation\Application as ApplicationContract; use Hypervel\Database\Eloquent\Builder; @@ -726,7 +727,7 @@ protected function fireUserEvent(string $event, User $user): void /** * Create a provider backed by the real serializing cache manager. * - * @param class-string<\Hypervel\Contracts\Auth\Authenticatable&Model> $model + * @param class-string $model */ protected function makeRealCachedProvider(string $model = User::class): EloquentUserProvider { diff --git a/tests/Integration/Cache/Redis/PhpRedisCacheLockTest.php b/tests/Integration/Cache/Redis/PhpRedisCacheLockTest.php index cabc4a02a6..99d475c698 100644 --- a/tests/Integration/Cache/Redis/PhpRedisCacheLockTest.php +++ b/tests/Integration/Cache/Redis/PhpRedisCacheLockTest.php @@ -4,6 +4,7 @@ namespace Hypervel\Tests\Integration\Cache\Redis; +use Hypervel\Cache\RedisStore; use Hypervel\Foundation\Testing\Concerns\InteractsWithRedis; use Hypervel\Support\Facades\Cache; use Hypervel\Testbench\TestCase; @@ -154,7 +155,7 @@ protected function configureLockConnection(array $options): void */ protected function assertLockCanBeAcquiredAndReleased(): void { - /** @var \Hypervel\Cache\RedisStore $store */ + /** @var RedisStore $store */ $store = Cache::store('redis'); $store->lock('foo')->forceRelease(); diff --git a/tests/Integration/Generators/EnumMakeCommandTest.php b/tests/Integration/Generators/EnumMakeCommandTest.php index 09ce4def2f..059f9b4cc9 100644 --- a/tests/Integration/Generators/EnumMakeCommandTest.php +++ b/tests/Integration/Generators/EnumMakeCommandTest.php @@ -4,6 +4,8 @@ namespace Hypervel\Tests\Integration\Generators; +use Hypervel\Filesystem\Filesystem; + class EnumMakeCommandTest extends TestCase { protected array $files = [ @@ -50,7 +52,7 @@ public function testItCanGenerateEnumFileInEnumsFolder(): void { $enumsFolderPath = app_path('Enums'); - /** @var \Hypervel\Filesystem\Filesystem $files */ + /** @var Filesystem $files */ $files = $this->app->make('files'); $files->ensureDirectoryExists($enumsFolderPath); @@ -70,7 +72,7 @@ public function testItCanGenerateEnumFileInEnumerationsFolder(): void { $enumerationsFolderPath = app_path('Enumerations'); - /** @var \Hypervel\Filesystem\Filesystem $files */ + /** @var Filesystem $files */ $files = $this->app->make('files'); $files->ensureDirectoryExists($enumerationsFolderPath); diff --git a/tests/Integration/Generators/InterfaceMakeCommandTest.php b/tests/Integration/Generators/InterfaceMakeCommandTest.php index 085f0c939f..69ada9f58b 100644 --- a/tests/Integration/Generators/InterfaceMakeCommandTest.php +++ b/tests/Integration/Generators/InterfaceMakeCommandTest.php @@ -4,6 +4,8 @@ namespace Hypervel\Tests\Integration\Generators; +use Hypervel\Filesystem\Filesystem; + class InterfaceMakeCommandTest extends TestCase { protected array $files = [ @@ -27,7 +29,7 @@ public function testItCanGenerateInterfaceFileWhenContractsFolderExists(): void { $interfacesFolderPath = app_path('Contracts'); - /** @var \Hypervel\Filesystem\Filesystem $files */ + /** @var Filesystem $files */ $files = $this->app->make('files'); $files->ensureDirectoryExists($interfacesFolderPath); @@ -47,7 +49,7 @@ public function testItCanGenerateInterfaceFileWhenInterfacesFolderExists(): void { $interfacesFolderPath = app_path('Interfaces'); - /** @var \Hypervel\Filesystem\Filesystem $files */ + /** @var Filesystem $files */ $files = $this->app->make('files'); $files->ensureDirectoryExists($interfacesFolderPath); diff --git a/tests/Integration/Generators/TraitMakeCommandTest.php b/tests/Integration/Generators/TraitMakeCommandTest.php index 1494ee9f73..3c80f87332 100644 --- a/tests/Integration/Generators/TraitMakeCommandTest.php +++ b/tests/Integration/Generators/TraitMakeCommandTest.php @@ -4,6 +4,8 @@ namespace Hypervel\Tests\Integration\Generators; +use Hypervel\Filesystem\Filesystem; + class TraitMakeCommandTest extends TestCase { protected array $files = [ @@ -27,7 +29,7 @@ public function testItCanGenerateTraitFileWhenTraitsFolderExists(): void { $traitsFolderPath = app_path('Traits'); - /** @var \Hypervel\Filesystem\Filesystem $files */ + /** @var Filesystem $files */ $files = $this->app->make('files'); $files->ensureDirectoryExists($traitsFolderPath); @@ -47,7 +49,7 @@ public function testItCanGenerateTraitFileWhenConcernsFolderExists(): void { $traitsFolderPath = app_path('Concerns'); - /** @var \Hypervel\Filesystem\Filesystem $files */ + /** @var Filesystem $files */ $files = $this->app->make('files'); $files->ensureDirectoryExists($traitsFolderPath); diff --git a/tests/Integration/Http/Middleware/PreventRequestForgeryServerRuntimeTest.php b/tests/Integration/Http/Middleware/PreventRequestForgeryServerRuntimeTest.php index 380e63ee55..31407df0d8 100644 --- a/tests/Integration/Http/Middleware/PreventRequestForgeryServerRuntimeTest.php +++ b/tests/Integration/Http/Middleware/PreventRequestForgeryServerRuntimeTest.php @@ -10,6 +10,7 @@ use Hypervel\Support\Collection; use Hypervel\Support\Facades\Route; use Hypervel\Testbench\TestCase; +use Symfony\Component\HttpFoundation\Cookie; class PreventRequestForgeryServerRuntimeTest extends TestCase { @@ -77,11 +78,13 @@ public function testServerRuntimeAcceptsMatchingCsrfToken(): void } /** - * @param array $cookies + * Get the named cookie from the given response cookies. + * + * @param array $cookies */ - protected function cookieFromResponse(array $cookies, string $name): \Symfony\Component\HttpFoundation\Cookie + protected function cookieFromResponse(array $cookies, string $name): Cookie { return Collection::make($cookies) - ->first(fn (\Symfony\Component\HttpFoundation\Cookie $cookie): bool => $cookie->getName() === $name); + ->first(fn (Cookie $cookie): bool => $cookie->getName() === $name); } } diff --git a/tests/Integration/Reverb/Fixtures/server.php b/tests/Integration/Reverb/Fixtures/server.php index 207d856dee..fb0a88530b 100644 --- a/tests/Integration/Reverb/Fixtures/server.php +++ b/tests/Integration/Reverb/Fixtures/server.php @@ -28,6 +28,7 @@ use Hypervel\Reverb\Webhooks\Jobs\WebhookDeliveryJob; use Hypervel\Server\ServerFactory; use Hypervel\Support\Facades\Queue; +use Hypervel\Support\Testing\Fakes\QueueFake; use Hypervel\Testbench\Bootstrapper; use Hypervel\Testbench\Foundation\Application as TestbenchApplication; use Hypervel\Tests\Integration\Reverb\Fixtures\ParallelTestApplicationProvider; @@ -214,7 +215,7 @@ }); $app->make(ReverbRouter::class)->get('/_test/queued-jobs', function () { - /** @var \Hypervel\Support\Testing\Fakes\QueueFake $fake */ + /** @var QueueFake $fake */ $fake = Queue::getFacadeRoot(); $jobs = $fake->pushed(WebhookDeliveryJob::class)->map(function (WebhookDeliveryJob $job) { diff --git a/tests/Redis/Operations/FlushByPatternTest.php b/tests/Redis/Operations/FlushByPatternTest.php index d110eee12a..4f107fafe5 100644 --- a/tests/Redis/Operations/FlushByPatternTest.php +++ b/tests/Redis/Operations/FlushByPatternTest.php @@ -11,6 +11,7 @@ use Hypervel\Tests\Redis\Fixtures\PhpRedisConnectionStub; use Hypervel\Tests\TestCase; use Mockery as m; +use Mockery\MockInterface; use RedisException; /** @@ -24,7 +25,7 @@ class FlushByPatternTest extends TestCase * Returns both the connection (as a partial mock) and the client so tests * can set up unlink expectations while using FakeRedisClient for scan behavior. * - * @return array{m\MockInterface&PhpRedisConnectionStub, FakeRedisClient} + * @return array{MockInterface&PhpRedisConnectionStub, FakeRedisClient} */ private function createConnectionWithClient(FakeRedisClient $client): array { diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index 75682a2f3c..81c4e0f81b 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -12,6 +12,7 @@ use Hypervel\Routing\Contracts\CallableDispatcher as CallableDispatcherContract; use Hypervel\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract; use Hypervel\Routing\ControllerDispatcher; +use Hypervel\Routing\Route; use Hypervel\Routing\Router; use Hypervel\Routing\RouteRegistrar; use Hypervel\Tests\Routing\Fixtures\CategoryBackedEnum; @@ -173,7 +174,7 @@ public function testResourceWithTrashed() 'destroy', ]); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertTrue($route->allowsTrashedBindings()); } @@ -550,8 +551,8 @@ public function testRouteGroupChaining() $routeCollection = $this->router->getRoutes(); - $this->assertInstanceOf(\Hypervel\Routing\Route::class, $routeCollection->match(Request::create('foo', 'GET'))); - $this->assertInstanceOf(\Hypervel\Routing\Route::class, $routeCollection->match(Request::create('bar', 'GET'))); + $this->assertInstanceOf(Route::class, $routeCollection->match(Request::create('foo', 'GET'))); + $this->assertInstanceOf(Route::class, $routeCollection->match(Request::create('bar', 'GET'))); } public function testRegisteringNonApprovedAttributesThrows(): void @@ -1268,7 +1269,7 @@ public function testResourceWheres() $this->router->resource('users', RouteRegistrarControllerStub::class) ->where($wheres); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1281,7 +1282,7 @@ public function testWhereNumberRegistration() $this->router->get('/{foo}/{bar}')->whereNumber(['foo', 'bar']); $this->router->get('/api/{bar}/{foo}')->whereNumber(['bar', 'foo']); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1294,7 +1295,7 @@ public function testWhereAlphaRegistration() $this->router->get('/{foo}/{bar}')->whereAlpha(['foo', 'bar']); $this->router->get('/api/{bar}/{foo}')->whereAlpha(['bar', 'foo']); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1306,7 +1307,7 @@ public function testWhereAlphaNumericRegistration() $this->router->get('/{foo}')->whereAlphaNumeric(['1a2b3c']); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1339,7 +1340,7 @@ public function testWhereInRegistration() $this->router->get('/{foo}/{bar}')->whereIn(['foo', 'bar'], ['one', 'two']); $this->router->get('/api/{bar}/{foo}')->whereIn(['bar', 'foo'], ['one', 'two']); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1370,7 +1371,7 @@ public function testGroupWhereNumberRegistrationOnRouteRegistrar() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1388,7 +1389,7 @@ public function testGroupWhereAlphaRegistrationOnRouteRegistrar() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1402,7 +1403,7 @@ public function testGroupWhereAlphaNumericRegistrationOnRouteRegistrar() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1420,7 +1421,7 @@ public function testGroupWhereInRegistrationOnRouteRegistrar() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1438,7 +1439,7 @@ public function testGroupWhereNumberRegistrationOnRouter() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1456,7 +1457,7 @@ public function testGroupWhereAlphaRegistrationOnRouter() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1470,7 +1471,7 @@ public function testGroupWhereAlphaNumericRegistrationOnRouter() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1488,7 +1489,7 @@ public function testGroupWhereInRegistrationOnRouter() $router->get('/'); }); - /** @var \Hypervel\Routing\Route $route */ + /** @var Route $route */ foreach ($this->router->getRoutes() as $route) { $this->assertEquals($wheres, $route->wheres); } @@ -1825,7 +1826,7 @@ public function testCanSetExcludedMiddlewareForSpecifiedMethodsOnRegisteredSingl /** * Get the last route registered with the router. * - * @return \Hypervel\Routing\Route + * @return Route */ protected function getRoute() { diff --git a/tests/Sentry/Features/LogLogsIntegrationTest.php b/tests/Sentry/Features/LogLogsIntegrationTest.php index f06d292df7..a29f141695 100644 --- a/tests/Sentry/Features/LogLogsIntegrationTest.php +++ b/tests/Sentry/Features/LogLogsIntegrationTest.php @@ -11,6 +11,7 @@ use Hypervel\Support\Facades\Log; use Hypervel\Tests\Sentry\SentryTestCase; use Sentry\EventType; +use Sentry\Logs\Log as SentryLog; use Sentry\Logs\LogLevel; use function Sentry\logger; @@ -178,7 +179,11 @@ public function testLogChannelAddsContextAsAttributes(): void $this->assertEquals('bar', $log->attributes()->get('foo')->getValue()); } - /** @return \Sentry\Logs\Log[] */ + /** + * Get and flush the captured logs. + * + * @return SentryLog[] + */ private function getAndFlushCapturedLogs(): array { $logs = logger()->aggregator()->all(); diff --git a/tests/Sentry/Features/ViewEngineDecoratorTest.php b/tests/Sentry/Features/ViewEngineDecoratorTest.php index cade70cb34..defae52b0a 100644 --- a/tests/Sentry/Features/ViewEngineDecoratorTest.php +++ b/tests/Sentry/Features/ViewEngineDecoratorTest.php @@ -8,6 +8,7 @@ use Hypervel\Sentry\Tracing\ViewEngineDecorator; use Hypervel\Tests\Sentry\SentryTestCase; use Hypervel\View\Engines\EngineResolver; +use Hypervel\View\Factory; use ReflectionProperty; use Sentry\Tracing\Transaction; @@ -57,7 +58,7 @@ public function testViewNameIsAvailableDuringRender(): void file_put_contents($viewDir . '/hello.blade.php', 'Hello'); try { - /** @var \Hypervel\View\Factory $viewFactory */ + /** @var Factory $viewFactory */ $viewFactory = $this->app->make('view'); $viewFactory->addNamespace('sentrytest', $viewDir); @@ -83,7 +84,7 @@ public function testViewNamesAreIsolatedBetweenConcurrentRenders(): void file_put_contents($viewDir . '/second.blade.php', 'Second'); try { - /** @var \Hypervel\View\Factory $viewFactory */ + /** @var Factory $viewFactory */ $viewFactory = $this->app->make('view'); $viewFactory->addNamespace('sentryrace', $viewDir); diff --git a/tests/Socialite/Fixtures/FacebookTestProviderStub.php b/tests/Socialite/Fixtures/FacebookTestProviderStub.php index 41d53c011e..39ada9a2fb 100644 --- a/tests/Socialite/Fixtures/FacebookTestProviderStub.php +++ b/tests/Socialite/Fixtures/FacebookTestProviderStub.php @@ -7,11 +7,12 @@ use GuzzleHttp\Client; use Hypervel\Socialite\Two\FacebookProvider; use Mockery as m; +use Mockery\MockInterface; class FacebookTestProviderStub extends FacebookProvider { /** - * @var \GuzzleHttp\Client|\Mockery\MockInterface + * @var Client|MockInterface */ public $http; diff --git a/tests/Socialite/Fixtures/GoogleTestProviderStub.php b/tests/Socialite/Fixtures/GoogleTestProviderStub.php index ecc9d7347a..f538e42a2b 100644 --- a/tests/Socialite/Fixtures/GoogleTestProviderStub.php +++ b/tests/Socialite/Fixtures/GoogleTestProviderStub.php @@ -8,11 +8,12 @@ use Hypervel\Socialite\Two\GoogleProvider; use Hypervel\Socialite\Two\User; use Mockery as m; +use Mockery\MockInterface; class GoogleTestProviderStub extends GoogleProvider { /** - * @var \GuzzleHttp\Client|\Mockery\MockInterface + * @var Client|MockInterface */ public $http; diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index 91a40403a4..9472ccf85d 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -25,10 +25,9 @@ class SupportStringableTest extends TestCase protected Container $container; /** - * @param string $string - * @return \Hypervel\Support\Stringable + * Create a new stringable instance. */ - protected function stringable($string = '') + protected function stringable(mixed $string = ''): Stringable { return new Stringable($string); } From f269f17fe8ef1c12d45c89e390c93e236f54cb32 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:15:38 +0000 Subject: [PATCH 02/12] Remove the redundant contract branch from RuleDenormalizer RuleDenormalizer::execute() returned [$rule] for Rule and InvokableRule objects and then returned the same [$rule] for every other object, so the contract check never changed the result. Drop the branch and its imports, and describe the return value as list, which already includes both contracts. Validation: the Data suite. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/data/src/Support/Validation/RuleDenormalizer.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/data/src/Support/Validation/RuleDenormalizer.php b/src/data/src/Support/Validation/RuleDenormalizer.php index f2dd0a2e18..120dd3890c 100644 --- a/src/data/src/Support/Validation/RuleDenormalizer.php +++ b/src/data/src/Support/Validation/RuleDenormalizer.php @@ -6,8 +6,6 @@ use BackedEnum; use DateTimeInterface; -use Hypervel\Contracts\Validation\InvokableRule as InvokableRuleContract; -use Hypervel\Contracts\Validation\Rule as RuleContract; use Hypervel\Data\Attributes\Validation\CustomValidationAttribute; use Hypervel\Data\Attributes\Validation\ObjectValidationAttribute; use Hypervel\Data\Attributes\Validation\Rule; @@ -21,7 +19,7 @@ class RuleDenormalizer /** * Convert one declaration into Validator rules. * - * @return list + * @return list */ public function execute(mixed $rule, ValidationPath $path): array { @@ -57,10 +55,6 @@ public function execute(mixed $rule, ValidationPath $path): array return $this->execute($rule->get(), $path); } - if ($rule instanceof RuleContract || $rule instanceof InvokableRuleContract) { - return [$rule]; - } - return [$rule]; } From 5c0682ce298874c18c41bd4a9c4b21d36840e93d Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:23:48 +0000 Subject: [PATCH 03/12] Document the worker lifetime of exception handling configuration The exception handler is a worker-lifetime singleton, and the withExceptions() configuration object delegates to it. Its public registration and setting methods therefore change state shared by every later request and job in the worker, but only the retry and stopIgnoring() methods carried the required boot-only warning. Add tag-first warnings to every Handler method that stores callbacks, mappings, exception lists, levels or response settings, to the matching Configuration\Exceptions methods, and to ReportableHandler::stop(), which changes a callback held by the shared handler. Runtime reporting and rendering methods and the coroutine-scoped afterResponse() are unchanged. The request exception truncation warnings described the setting as applying to every HTTP client exception. It is a global default for RequestException messages that per-request truncation settings override, so RequestException::truncate(), truncateAt() and dontTruncate() and the two configuration wrappers now say so. Validation: composer lint:fix, composer analyse, FacadeDocblocksTest and the Foundation exception tests. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- .../src/Configuration/Exceptions.php | 48 +++++++++++++++++++ src/foundation/src/Exceptions/Handler.php | 39 +++++++++++++++ .../src/Exceptions/ReportableHandler.php | 3 ++ src/http/src/Client/RequestException.php | 12 ++--- 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/foundation/src/Configuration/Exceptions.php b/src/foundation/src/Configuration/Exceptions.php index f98c38de86..242c81a3db 100644 --- a/src/foundation/src/Configuration/Exceptions.php +++ b/src/foundation/src/Configuration/Exceptions.php @@ -24,6 +24,9 @@ public function __construct(public Handler $handler) /** * Register a reportable callback. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. */ public function report(callable $using): ReportableHandler { @@ -32,6 +35,9 @@ public function report(callable $using): ReportableHandler /** * Register a reportable callback. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. */ public function reportable(callable $reportUsing): ReportableHandler { @@ -40,6 +46,9 @@ public function reportable(callable $reportUsing): ReportableHandler /** * Register a renderable callback. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently rendered exception in the worker. */ public function render(callable $using): static { @@ -50,6 +59,9 @@ public function render(callable $using): static /** * Register a renderable callback. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently rendered exception in the worker. */ public function renderable(callable $renderUsing): static { @@ -60,6 +72,9 @@ public function renderable(callable $renderUsing): static /** * Register a callback to prepare the final, rendered exception response. + * + * Boot-only. The callback replaces the shared handler's response callback for + * every subsequently rendered exception in the worker. */ public function respond(callable $using): static { @@ -70,6 +85,9 @@ public function respond(callable $using): static /** * Specify the callback that should be used to throttle reportable exceptions. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. */ public function throttle(callable $throttleUsing): static { @@ -81,6 +99,9 @@ public function throttle(callable $throttleUsing): static /** * Register a new exception mapping. * + * Boot-only. The mapping persists on the shared handler and is considered for + * every subsequently reported or rendered exception in the worker. + * * @throws InvalidArgumentException */ public function map(Closure|string $from, Closure|string|null $to = null): static @@ -93,6 +114,9 @@ public function map(Closure|string $from, Closure|string|null $to = null): stati /** * Set the log level for the given exception type. * + * Boot-only. The level persists on the shared handler and applies to every + * subsequently reported exception of that type in the worker. + * * @param class-string $type * @param LogLevel::* $level */ @@ -105,6 +129,9 @@ public function level(string $type, string $level): static /** * Register a closure that should be used to build exception context data. + * + * Boot-only. The closure persists on the shared handler and runs for every + * subsequently logged exception in the worker. */ public function context(Closure $contextCallback): static { @@ -115,6 +142,9 @@ public function context(Closure $contextCallback): static /** * Indicate that the given exception type should not be reported. + * + * Boot-only. The exception types persist on the shared handler and affect + * exception reporting for every subsequent request and job in the worker. */ public function dontReport(array|string $class): static { @@ -143,6 +173,9 @@ public function dontRetry(array|string $class): static /** * Register a callback to determine if an exception should not be reported. * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. + * * @param (Closure(Throwable): bool) $dontReportWhen */ public function dontReportWhen(Closure $dontReportWhen): static @@ -171,6 +204,9 @@ public function dontRetryWhen(Closure $dontRetryWhen): static /** * Do not report duplicate exceptions. + * + * Boot-only. The setting persists on the shared handler and applies to every + * subsequent request and job in the worker. */ public function dontReportDuplicates(): static { @@ -181,6 +217,9 @@ public function dontReportDuplicates(): static /** * Indicate that the given attributes should never be flashed to the session on validation errors. + * + * Boot-only. The attributes persist on the shared handler and are omitted + * from every subsequent validation redirect in the worker. */ public function dontFlash(array|string $attributes): static { @@ -191,6 +230,9 @@ public function dontFlash(array|string $attributes): static /** * Register the callable that determines if the exception handler response should be JSON. + * + * Boot-only. The callable replaces the shared handler's JSON check for every + * subsequently rendered exception in the worker. */ public function shouldRenderJsonWhen(callable $callback): static { @@ -216,6 +258,9 @@ public function stopIgnoring(array|string $class): static /** * Set the truncation length for request exception messages. + * + * Boot-only. The global default persists for the worker lifetime across all + * coroutines; per-request truncation settings take precedence. */ public function truncateRequestExceptionsAt(int $length): static { @@ -226,6 +271,9 @@ public function truncateRequestExceptionsAt(int $length): static /** * Disable truncation of request exception messages. + * + * Boot-only. The global default persists for the worker lifetime across all + * coroutines; per-request truncation settings take precedence. */ public function dontTruncateRequestExceptions(): static { diff --git a/src/foundation/src/Exceptions/Handler.php b/src/foundation/src/Exceptions/Handler.php index 516b294b95..67a890ab48 100644 --- a/src/foundation/src/Exceptions/Handler.php +++ b/src/foundation/src/Exceptions/Handler.php @@ -222,6 +222,9 @@ public function register(): void /** * Register a reportable callback. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. */ public function reportable(callable $reportUsing): ReportableHandler { @@ -236,6 +239,9 @@ public function reportable(callable $reportUsing): ReportableHandler /** * Register a renderable callback. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently rendered exception in the worker. */ public function renderable(callable $renderUsing): static { @@ -251,6 +257,9 @@ public function renderable(callable $renderUsing): static /** * Register a new exception mapping. * + * Boot-only. The mapping persists on the shared handler and is considered for + * every subsequently reported or rendered exception in the worker. + * * @throws InvalidArgumentException */ public function map(callable|string $from, Closure|string|null $to = null): static @@ -276,6 +285,9 @@ public function map(callable|string $from, Closure|string|null $to = null): stat * Indicate that the given exception type should not be reported. * * Alias of "ignore". + * + * Boot-only. The exception types persist on the shared handler and affect + * exception reporting for every subsequent request and job in the worker. */ public function dontReport(array|string $exceptions): static { @@ -285,6 +297,9 @@ public function dontReport(array|string $exceptions): static /** * Register a callback to determine if an exception should not be reported. * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. + * * @param (callable(Throwable): bool) $dontReportWhen */ public function dontReportWhen(callable $dontReportWhen): static @@ -300,6 +315,9 @@ public function dontReportWhen(callable $dontReportWhen): static /** * Indicate that the given exception type should not be reported. + * + * Boot-only. The exception types persist on the shared handler and affect + * exception reporting for every subsequent request and job in the worker. */ public function ignore(array|string $exceptions): static { @@ -405,6 +423,9 @@ public function shouldStopRetries(Throwable $e): bool /** * Indicate that the given attributes should never be flashed to the session on validation errors. + * + * Boot-only. The attributes persist on the shared handler and are omitted + * from every subsequent validation redirect in the worker. */ public function dontFlash(array|string $attributes): static { @@ -418,6 +439,9 @@ public function dontFlash(array|string $attributes): static /** * Set the log level for the given exception type. * + * Boot-only. The level persists on the shared handler and applies to every + * subsequently reported exception of that type in the worker. + * * @param class-string $type * @param LogLevel::* $level */ @@ -625,6 +649,9 @@ protected function throttle(Throwable $e): Lottery|AdmissionPolicy|null /** * Specify the callback that should be used to throttle reportable exceptions. + * + * Boot-only. The callback persists on the shared handler and is considered + * for every subsequently reported exception in the worker. */ public function throttleUsing(callable $throttleUsing): static { @@ -718,6 +745,9 @@ protected function context(): array /** * Register a closure that should be used to build exception context data. + * + * Boot-only. The closure persists on the shared handler and runs for every + * subsequently logged exception in the worker. */ public function buildContextUsing(Closure $contextCallback): static { @@ -806,6 +836,9 @@ protected function afterResponseCallbacks(): array /** * Prepare the final, rendered response for an exception using the given callback. + * + * Boot-only. The callback replaces the shared handler's response callback for + * every subsequently rendered exception in the worker. */ public function respondUsing(callable $callback): static { @@ -956,6 +989,9 @@ protected function shouldReturnJson(Request $request, Throwable $e): bool /** * Register the callable that determines if the exception handler response should be JSON. * + * Boot-only. The callable replaces the shared handler's JSON check for every + * subsequently rendered exception in the worker. + * * @param callable(Request $request, Throwable): bool $callback */ public function shouldRenderJsonWhen(callable $callback): static @@ -1179,6 +1215,9 @@ public function renderForConsole(OutputInterface $output, Throwable $e): void /** * Do not report duplicate exceptions. + * + * Boot-only. The setting persists on the shared handler and applies to every + * subsequent request and job in the worker. */ public function dontReportDuplicates(): static { diff --git a/src/foundation/src/Exceptions/ReportableHandler.php b/src/foundation/src/Exceptions/ReportableHandler.php index af98f22e5d..8b452f4d6b 100644 --- a/src/foundation/src/Exceptions/ReportableHandler.php +++ b/src/foundation/src/Exceptions/ReportableHandler.php @@ -62,6 +62,9 @@ public function handles(Throwable $e): bool /** * Indicate that report handling should stop after invoking this callback. * + * Boot-only. The flag persists on the registered callback and stops report + * handling for every exception it subsequently handles in the worker. + * * @return $this */ public function stop(): static diff --git a/src/http/src/Client/RequestException.php b/src/http/src/Client/RequestException.php index b7c990bd42..5aabca0d1e 100644 --- a/src/http/src/Client/RequestException.php +++ b/src/http/src/Client/RequestException.php @@ -43,8 +43,8 @@ public function __construct( /** * Enable truncation of request exception messages. * - * Boot-only. The flag persists in a static property for the worker lifetime - * and applies to every HTTP client exception across all coroutines. + * Boot-only. The global default persists for the worker lifetime across all + * coroutines; per-request truncation settings take precedence. */ public static function truncate(): void { @@ -54,8 +54,8 @@ public static function truncate(): void /** * Set the truncation length for request exception messages. * - * Boot-only. The length persists in a static property for the worker - * lifetime and applies to every HTTP client exception across all coroutines. + * Boot-only. The global default persists for the worker lifetime across all + * coroutines; per-request truncation settings take precedence. */ public static function truncateAt(int $length): void { @@ -65,8 +65,8 @@ public static function truncateAt(int $length): void /** * Disable truncation of request exception messages. * - * Boot-only. The flag persists in a static property for the worker lifetime - * and applies to every HTTP client exception across all coroutines. + * Boot-only. The global default persists for the worker lifetime across all + * coroutines; per-request truncation settings take precedence. */ public static function dontTruncate(): void { From 18a1d3a1820ff8a410586e821bc1cb7675bcb26d Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:32:39 +0000 Subject: [PATCH 04/12] Throw InvalidCodeException for missing OAuth 2 authorization codes When a user declines authorization, the provider redirects back with a matching state and no code. Socialite's guidance says user() throws in that case and that applications must handle declined grants, commonly with catch (Exception). Hypervel's getCode(): string instead raised a TypeError, which that handling does not catch, so a declined login became a server error. Array and empty codes failed the same way or reached the token endpoint. getCode() now throws Two\Exceptions\InvalidCodeException, an InvalidArgumentException like the other callback validation exceptions, when the code is missing, empty or not a string. State validation still runs first, nonempty codes stay opaque, and no token request is sent. The Socialite documentation names the exception. Validation: composer lint:fix, composer analyse, the Socialite suite and a regression test covering declined, array and empty codes. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/docs/socialite.md | 2 + src/socialite/src/Two/AbstractProvider.php | 15 ++++++- .../Two/Exceptions/InvalidCodeException.php | 11 +++++ tests/Socialite/OAuthTwoTest.php | 41 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/socialite/src/Two/Exceptions/InvalidCodeException.php diff --git a/src/docs/socialite.md b/src/docs/socialite.md index 42545f2c48..a998ae5133 100644 --- a/src/docs/socialite.md +++ b/src/docs/socialite.md @@ -103,6 +103,8 @@ Route::get('/auth/callback', function () { The `redirect` method provided by the `Socialite` facade takes care of redirecting the user to the OAuth provider, while the `user` method will examine the incoming request and retrieve the user's information from the provider after they have approved the authentication request. +For OAuth 2.0 providers, after validating the state, the `user` method will throw a `Hypervel\Socialite\Two\Exceptions\InvalidCodeException` if the authorization code is missing, empty, or not a string. This typically happens when the user declines the authentication request. + ### Authentication and Storage diff --git a/src/socialite/src/Two/AbstractProvider.php b/src/socialite/src/Two/AbstractProvider.php index 29e25b91e5..434cc26504 100644 --- a/src/socialite/src/Two/AbstractProvider.php +++ b/src/socialite/src/Two/AbstractProvider.php @@ -11,6 +11,7 @@ use Hypervel\Socialite\AbstractProvider as BaseProvider; use Hypervel\Socialite\Contracts\Provider as ProviderContract; use Hypervel\Socialite\Two\Exceptions\InvalidAudienceException; +use Hypervel\Socialite\Two\Exceptions\InvalidCodeException; use Hypervel\Support\Arr; use Hypervel\Support\Str; use SensitiveParameter; @@ -146,6 +147,9 @@ protected function formatScopes(array $scopes, string $scopeSeparator): string /** * Get the User instance for the authenticated user. + * + * @throws InvalidStateException + * @throws InvalidCodeException */ public function user(): User { @@ -352,10 +356,19 @@ protected function parseApprovedScopes(#[SensitiveParameter] array $response): a /** * Get the code from the request. + * + * @throws InvalidCodeException */ protected function getCode(): string { - return $this->getRequest()->input('code'); + $code = $this->getRequest()->input('code'); + + // Declined authorization can omit the code; throw an Exception rather than letting the return type raise a TypeError. + if (! is_string($code) || $code === '') { + throw new InvalidCodeException('The authorization code is missing or invalid.'); + } + + return $code; } /** diff --git a/src/socialite/src/Two/Exceptions/InvalidCodeException.php b/src/socialite/src/Two/Exceptions/InvalidCodeException.php new file mode 100644 index 0000000000..30e16d2af1 --- /dev/null +++ b/src/socialite/src/Two/Exceptions/InvalidCodeException.php @@ -0,0 +1,11 @@ +user(); } + #[DataProvider('invalidAuthorizationCodeProvider')] + public function testExceptionIsThrownIfAuthorizationCodeIsInvalid(mixed $code): void + { + $request = m::mock(Request::class); + $request->shouldReceive('session') + ->andReturn($session = m::mock(SessionContract::class)); + $request->shouldReceive('input') + ->with('state') + ->once() + ->andReturn(str_repeat('A', 40)); + $request->shouldReceive('input') + ->with('code') + ->once() + ->andReturn($code); + + $session->expects('pull')->with('state')->andReturns(str_repeat('A', 40)); + $provider = new OAuthTwoTestProviderStub( + $request, + 'client_id', + 'client_secret', + 'redirect' + ); + $provider->http = m::mock(Client::class); + $provider->http->shouldNotReceive('post'); + + $this->expectException(InvalidCodeException::class); + $this->expectExceptionMessage('The authorization code is missing or invalid.'); + + $provider->user(); + } + + public static function invalidAuthorizationCodeProvider(): array + { + return [ + 'declined authorization' => [null], + 'array' => [['code']], + 'empty string' => [''], + ]; + } + public function testUserRefreshesToken(): void { $request = m::mock(Request::class); From 95cb58553ed385d11b57f4104e93ccdeb3c8ca90 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:40:08 +0000 Subject: [PATCH 05/12] Include nested arrays in mergeRecursive's string-key value type mergeRecursive() uses array_merge_recursive(), which nests values into arrays when string keys collide. The value type from laravel/framework #40504 (https://github.com/laravel/framework/pull/40504) declares only TValue|TMergeRecursiveValue, so a string-keyed merge was typed as if colliding scalars stayed scalar. The return type now adds array to the values unless the collection's keys are integers, which are appended and never nest. The condition sits inside the value type, so mixed-key collections keep a single collection type rather than splitting across the invariant key type. List fixtures are unchanged, and new eager and lazy fixtures cover string-key collisions and mixed keys. Validation: composer analyse (including the type fixtures) and composer lint:fix. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/collections/src/Collection.php | 2 +- src/collections/src/Enumerable.php | 2 +- types/Collections/Collection.php | 2 ++ types/Collections/LazyCollection.php | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/collections/src/Collection.php b/src/collections/src/Collection.php index bb2c83b174..0190db29ab 100644 --- a/src/collections/src/Collection.php +++ b/src/collections/src/Collection.php @@ -856,7 +856,7 @@ public function merge(mixed $items): static * @template TMergeRecursiveValue * * @param Arrayable|iterable $items - * @return static + * @return static|TMergeRecursiveValue|TValue)> */ public function mergeRecursive(mixed $items): static { diff --git a/src/collections/src/Enumerable.php b/src/collections/src/Enumerable.php index 9ee50a001a..de8571733a 100644 --- a/src/collections/src/Enumerable.php +++ b/src/collections/src/Enumerable.php @@ -658,7 +658,7 @@ public function merge(mixed $items): static; * @template TMergeRecursiveValue * * @param Arrayable|iterable $items - * @return static + * @return static|TMergeRecursiveValue|TValue)> */ public function mergeRecursive(mixed $items): static; diff --git a/types/Collections/Collection.php b/types/Collections/Collection.php index 2ab8cbec00..b0620de55c 100644 --- a/types/Collections/Collection.php +++ b/types/Collections/Collection.php @@ -673,6 +673,8 @@ function ($collection, $count) { assertType('Hypervel\Support\Collection', $collection::make([1])->mergeRecursive([2 => 'string'])); assertType('Hypervel\Support\Collection', $collection::make(['string'])->mergeRecursive(['string'])); +assertType('Hypervel\Support\Collection|int>', $collection::make(['a' => 1])->mergeRecursive(['a' => 2])); +assertType('Hypervel\Support\Collection|int>', $collection::make([1, 'a' => 1])->mergeRecursive(['a' => 2])); assertType('Hypervel\Support\Collection', $collection::make(['string' => 'string'])->combine([2])); assertType('Hypervel\Support\Collection', $collection::make([1])->combine([1])); diff --git a/types/Collections/LazyCollection.php b/types/Collections/LazyCollection.php index f11377a788..6f4ac83174 100644 --- a/types/Collections/LazyCollection.php +++ b/types/Collections/LazyCollection.php @@ -552,6 +552,8 @@ public function toArray(): array assertType('Hypervel\Support\LazyCollection', $collection::make([1])->mergeRecursive([2])); assertType('Hypervel\Support\LazyCollection', $collection::make(['string'])->mergeRecursive(['string'])); +assertType('Hypervel\Support\LazyCollection|int>', $collection::make(['a' => 1])->mergeRecursive(['a' => 2])); +assertType('Hypervel\Support\LazyCollection|int>', $collection::make([1, 'a' => 1])->mergeRecursive(['a' => 2])); assertType('Hypervel\Support\LazyCollection', $collection::make(['string' => 'string'])->combine([2])); assertType('Hypervel\Support\LazyCollection', $collection::make([1])->combine([1])); From 28a1133c99f7a9252361c5fc4ed7b88f6111d2f8 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:50:53 +0000 Subject: [PATCH 06/12] Document the queue pause and resume events The queue:pause and queue:resume commands were documented, but the events they and the workers dispatch were not. Name QueuePaused, QueueResumed, QueuesPaused and QueuesResumed, which the process making the change dispatches, and WorkerQueuePaused and WorkerQueueResumed, which running workers dispatch when they detect the change. Also cover the SIGUSR2 and SIGCONT whole-worker pause and its WorkerPausing and WorkerResuming events. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/docs/queues.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/docs/queues.md b/src/docs/queues.md index b3ad0a73bd..746a1de318 100644 --- a/src/docs/queues.md +++ b/src/docs/queues.md @@ -2990,6 +2990,10 @@ After resuming a queue, workers will begin processing new jobs from that queue i Queue workers report paused and resumed queues in their console output. +Pausing or resuming a queue dispatches the `Hypervel\Queue\Events\QueuePaused` or `Hypervel\Queue\Events\QueueResumed` event in the process that made the change. Their `connection` and `queue` properties identify the queue, and the `QueuePaused` event's `ttl` property is `null` unless the queue was paused for a limited time. Pausing or resuming every queue with the `--all` option dispatches the `QueuesPaused` or `QueuesResumed` event instead. Running workers dispatch the `WorkerQueuePaused` and `WorkerQueueResumed` events, with `connectionName` and `queue` properties, when they detect that one of their queues has been paused or resumed. + +A worker process may also be paused as a whole by sending it the `SIGUSR2` signal and resumed with `SIGCONT`. The worker dispatches the `WorkerPausing` and `WorkerResuming` events when it receives these signals. Both events provide the worker's `connectionName`, `queue`, and `workerOptions`. + #### Worker Restart and Pause Signals From ca9c4cb24a8a48c5d9d6fac9f05ce0a4b057528e Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:50:53 +0000 Subject: [PATCH 07/12] Restore the upstream description of ignoringMockOnceExceptions While reconciling laravel/framework #61078 (https://github.com/laravel/framework/pull/61078), the Artisan command test helper was found without the description Laravel gives it. Restore it without the redundant parameter and return annotations. The rest of #61078 was already covered: AfterEachTestSubscriber resets Carbon test time (including CarbonImmutable), Str factories, Sleep, Lottery and coroutine-scoped Once state, and the helper that verifies Mockery expectations immediately already exists. Hypervel keeps #[Override] on test lifecycle methods. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- tests/Console/ArtisanCommandTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Console/ArtisanCommandTest.php b/tests/Console/ArtisanCommandTest.php index ed88d1b038..88b554a19d 100644 --- a/tests/Console/ArtisanCommandTest.php +++ b/tests/Console/ArtisanCommandTest.php @@ -526,6 +526,10 @@ protected function verifyMockeryExpectationsNow(): void m::close(); } + /** + * Don't allow Mockery's InvalidCountException to be reported. Mocks setup + * in PendingCommand cause PHPUnit tearDown() to later throw the exception. + */ protected function ignoringMockOnceExceptions(callable $callback): void { try { From d2358337fd3090eb448818cee30f62065aed76e7 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:50:53 +0000 Subject: [PATCH 08/12] Read the SQLite connection URL from DB_URL The default SQLite connection read DATABASE_URL while every other connection, and every Laravel connection, reads DB_URL, so a ported application's DB_URL never reached the default SQLite connection. The database documentation described the inconsistency instead of the configuration Laravel uses. The SQLite connection now reads DB_URL, the documentation uses Laravel's wording, and the queue transaction tests no longer clear the unused DATABASE_URL for their worker subprocess. Validation: QueueTransactionTest and BatchableTransactionTest against MySQL. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/docs/database.md | 2 +- src/foundation/config/database.php | 2 +- tests/Integration/Database/Queue/BatchableTransactionTest.php | 1 - tests/Integration/Database/Queue/QueueTransactionTest.php | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/docs/database.md b/src/docs/database.md index 670662742e..7595546af2 100644 --- a/src/docs/database.md +++ b/src/docs/database.md @@ -138,7 +138,7 @@ These URLs typically follow a standard schema convention: driver://username:password@host:port/database?options ``` -For convenience, Hypervel supports these URLs as an alternative to configuring your database with multiple configuration options. If the `url` configuration option is present, it will be used to extract the database connection and credential information. In Hypervel's default database configuration file, SQLite uses the `DATABASE_URL` environment variable while MariaDB, MySQL, and PostgreSQL use the `DB_URL` environment variable. +For convenience, Hypervel supports these URLs as an alternative to configuring your database with multiple configuration options. If the `url` (or corresponding `DB_URL` environment variable) configuration option is present, it will be used to extract the database connection and credential information. ### Read and Write Connections diff --git a/src/foundation/config/database.php b/src/foundation/config/database.php index 7336b3f9cc..40a6df06a9 100644 --- a/src/foundation/config/database.php +++ b/src/foundation/config/database.php @@ -47,7 +47,7 @@ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', - 'url' => env('DATABASE_URL'), + 'url' => env('DB_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'prefix_indexes' => null, diff --git a/tests/Integration/Database/Queue/BatchableTransactionTest.php b/tests/Integration/Database/Queue/BatchableTransactionTest.php index 1ad8381210..919d446a20 100644 --- a/tests/Integration/Database/Queue/BatchableTransactionTest.php +++ b/tests/Integration/Database/Queue/BatchableTransactionTest.php @@ -47,7 +47,6 @@ public function testItCanHandleTimeoutJob(): void 'DB_CONNECTION' => config('database.default'), 'DB_DATABASE' => config('database.connections.' . config('database.default') . '.database'), 'DB_URL' => false, - 'DATABASE_URL' => false, 'DB_POOLED_URL' => false, 'QUEUE_CONNECTION' => config('queue.default'), ])->run(); diff --git a/tests/Integration/Database/Queue/QueueTransactionTest.php b/tests/Integration/Database/Queue/QueueTransactionTest.php index 09add7c8a1..6b540c88d1 100644 --- a/tests/Integration/Database/Queue/QueueTransactionTest.php +++ b/tests/Integration/Database/Queue/QueueTransactionTest.php @@ -46,7 +46,6 @@ public function testItCanHandleTimeoutJob(ShouldQueue $job): void 'DB_CONNECTION' => config('database.default'), 'DB_DATABASE' => config('database.connections.' . config('database.default') . '.database'), 'DB_URL' => false, - 'DATABASE_URL' => false, 'DB_POOLED_URL' => false, 'QUEUE_CONNECTION' => config('queue.default'), ])->run(); From 1fcf98b50b8c20bbbb4056fcbef4637f96b5ab23 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 06:15:09 +0000 Subject: [PATCH 09/12] Resolve UsePolicy from parent classes and scope the policy cache per gate Port laravel/framework #61439 (https://github.com/laravel/framework/pull/61439): a model that extends a class carrying #[UsePolicy] now resolves the parent's policy. The inherited attribute is checked last, after the model's own attribute, guessed policy names and registered parent policies, and the Gate authorization docs describe it. Hypervel had not ported Laravel's GatePolicyResolutionTest, so the whole file is ported with its fixtures, including the four new inheritance cases. It lives in tests/Auth because it needs no external service. The convention-guessing fixtures sit beside the existing AuthTestUser, whose namespace the guessed policy names are derived from. Tracing the new fallback exposed a defect in Hypervel's policy cache: it was static and keyed only by model class, but the result depends on each gate's registered policies and guess callback. Registering a parent policy after a child had resolved left the stale result in place, and two gates with different policies shared one answer. The cache now belongs to the gate's policy configuration: gates created by forUser() share it, while policy() and guessPolicyNamesUsing() detach the gate they change. It is created on first use, so forUser() does not allocate a cache it immediately replaces; warm direct and forUser() lookups stay at their previous cost. Validation: the Auth suite, composer lint:fix and composer analyse, with regression tests for configuration changes, independent gates and cache sharing through forUser(). Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/auth/src/Access/Gate.php | 69 +++++---- src/docs/authorization.md | 2 + tests/Auth/AuthAccessGateTest.php | 58 +++++-- .../Fixtures/ChildOfDummyWithUsePolicy.php | 12 ++ tests/Auth/GatePolicyResolutionTest.php | 146 ++++++++++++++++++ .../Auth/Fixtures/Models/AuthTestUser.php | 30 ++++ .../Fixtures/Models/Nested/SubTestUser.php | 30 ++++ .../Fixtures/Models/Nested/TopTestUser.php | 30 ++++ .../Policies/Nested/SubTestUserPolicy.php | 9 ++ .../Fixtures/Policies/AuthTestUserPolicy.php | 9 ++ .../Policies/Nested/TopTestUserPolicy.php | 9 ++ 11 files changed, 363 insertions(+), 41 deletions(-) create mode 100644 tests/Auth/Fixtures/ChildOfDummyWithUsePolicy.php create mode 100644 tests/Auth/GatePolicyResolutionTest.php create mode 100644 tests/Integration/Auth/Fixtures/Models/AuthTestUser.php create mode 100644 tests/Integration/Auth/Fixtures/Models/Nested/SubTestUser.php create mode 100644 tests/Integration/Auth/Fixtures/Models/Nested/TopTestUser.php create mode 100644 tests/Integration/Auth/Fixtures/Models/Policies/Nested/SubTestUserPolicy.php create mode 100644 tests/Integration/Auth/Fixtures/Policies/AuthTestUserPolicy.php create mode 100644 tests/Integration/Auth/Fixtures/Policies/Nested/TopTestUserPolicy.php diff --git a/src/auth/src/Access/Gate.php b/src/auth/src/Access/Gate.php index 7de0f87bf3..ab7d0e3f0b 100644 --- a/src/auth/src/Access/Gate.php +++ b/src/auth/src/Access/Gate.php @@ -4,6 +4,7 @@ namespace Hypervel\Auth\Access; +use ArrayObject; use Closure; use Exception; use Hypervel\Auth\Access\Events\GateEvaluated; @@ -52,18 +53,18 @@ class Gate implements GateContract protected $guessPolicyNamesUsingCallback; /** - * Cached model class to policy class mappings. + * The policy classes resolved for this gate's policy configuration. * - * Stores the resolved policy class string (or false for "no policy found") - * per model class. Persists for the worker lifetime — model-to-policy - * mappings don't change at runtime. The policy *instance* is not cached - * here; resolvePolicy() goes through the container each time. + * Stores the resolved policy class (or false for "no policy found") per + * model class. Gates created by forUser() share it; policy() and + * guessPolicyNamesUsing() replace it because they change how policies + * resolve. The policy *instance* is not cached here; resolvePolicy() + * goes through the container each time. Created on first use so that + * forUser() does not allocate a cache it immediately replaces. * - * Explicit policies ($this->policies) bypass this cache entirely. - * - * @var array + * @var null|ArrayObject */ - protected static array $policyClassCache = []; + protected ?ArrayObject $policyClassCache = null; /** * Cached guest-access results for class methods. @@ -269,6 +270,10 @@ public function policy(string $class, string $policy): static { $this->policies[$class] = $policy; + // Registrations change subclass and inherited-attribute resolution, so + // detach from the cache shared with gates keeping the previous policies. + $this->policyClassCache = null; + return $this; } @@ -601,17 +606,13 @@ public function getPolicyFor(object|string $class): mixed $class = get_class($class); } - // Explicitly registered policies bypass the cache — they're a fast - // hash lookup, and the policies array can be modified at runtime. + // Explicitly registered policies are a fast hash lookup, so they bypass the cache. if (isset($this->policies[$class])) { return $this->resolvePolicy($this->policies[$class]); } - if (! array_key_exists($class, static::$policyClassCache)) { - static::$policyClassCache[$class] = $this->resolvePolicyClass($class); - } - - $policyClass = static::$policyClassCache[$class]; + $this->policyClassCache ??= new ArrayObject; + $policyClass = $this->policyClassCache[$class] ??= $this->resolvePolicyClass($class); return $policyClass !== false ? $this->resolvePolicy($policyClass) @@ -621,9 +622,9 @@ public function getPolicyFor(object|string $class): mixed /** * Resolve the policy class for the given model class. * - * Checks the UsePolicy attribute, convention-based guessing, and - * subclass fallback. Returns the policy class string or false if - * no policy is found. + * Checks the UsePolicy attribute, convention-based guessing, subclass + * fallback, and inherited UsePolicy attributes. Returns the policy class + * string or false if no policy is found. * * @return class-string|false */ @@ -647,7 +648,7 @@ private function resolvePolicyClass(string $class): string|false } } - return false; + return $this->getPolicyFromAttribute($class, includeParents: true) ?? false; } /** @@ -656,17 +657,23 @@ private function resolvePolicyClass(string $class): string|false * @param class-string $class * @return null|class-string */ - protected function getPolicyFromAttribute(string $class): ?string + protected function getPolicyFromAttribute(string $class, bool $includeParents = false): ?string { if (! class_exists($class)) { return null; } - $attributes = (new ReflectionClass($class))->getAttributes(UsePolicy::class); + $reflection = new ReflectionClass($class); - return $attributes !== [] - ? $attributes[0]->newInstance()->class - : null; + do { + $attributes = $reflection->getAttributes(UsePolicy::class); + + if ($attributes !== []) { + return $attributes[0]->newInstance()->class; + } + } while ($includeParents && $reflection = $reflection->getParentClass()); + + return null; } /** @@ -705,8 +712,8 @@ public function guessPolicyNamesUsing(callable $callback): static $this->guessPolicyNamesUsingCallback = $callback; // A custom guess callback changes how unregistered policies are resolved, - // so any cached results from the default guesser may be stale. - static::$policyClassCache = []; + // so detach from the cache shared with gates keeping the previous guesser. + $this->policyClassCache = null; return $this; } @@ -805,7 +812,7 @@ protected function formatAbilityToMethod(string $ability): string */ public function forUser(mixed $user): static { - return new static( + $gate = new static( $this->container, fn () => $user, $this->abilities, @@ -814,6 +821,11 @@ public function forUser(mixed $user): static $this->afterCallbacks, $this->guessPolicyNamesUsingCallback, ); + + // The new gate has the same policy configuration, so it shares resolved policies. + $gate->policyClassCache = $this->policyClassCache ??= new ArrayObject; + + return $gate; } /** @@ -1124,7 +1136,6 @@ protected function buildScopeSelection( */ public static function flushState(): void { - static::$policyClassCache = []; static::$guestMethodCache = []; static::$guestCallbackCache = null; static::$abilityMethodCache = []; diff --git a/src/docs/authorization.md b/src/docs/authorization.md index 6235621270..845993f098 100644 --- a/src/docs/authorization.md +++ b/src/docs/authorization.md @@ -410,6 +410,8 @@ class Order extends Model } ``` +Models that extend a class with the `UsePolicy` attribute use its policy as well, unless a policy is registered or discovered for the child model or the child model declares its own `UsePolicy` attribute. + ## Writing Policies diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index 1a4ffae83d..f3826c9a08 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -14,6 +14,7 @@ use Hypervel\Events\Dispatcher as EventDispatcher; use Hypervel\Support\Testing\Fakes\EventFake; use Hypervel\Tests\Auth\Fixtures\AbilitiesEnum; +use Hypervel\Tests\Auth\Fixtures\ChildOfDummyWithUsePolicy; use Hypervel\Tests\Auth\Fixtures\DummyWithoutUsePolicy; use Hypervel\Tests\Auth\Fixtures\DummyWithUsePolicy; use Hypervel\Tests\Auth\Fixtures\DummyWithUsePolicyPolicy; @@ -1580,13 +1581,23 @@ public function testGateEvaluationDispatchesToAnEventFakeWithoutRealListeners(): public function testPolicyCacheReturnsSameResultOnSecondCall() { $gate = $this->getBasicGate(); - $gate->policy(AccessGateTestDummy::class, AccessGateTestPolicy::class); - $first = $gate->getPolicyFor(new AccessGateTestDummy); - $second = $gate->getPolicyFor(new AccessGateTestDummy); + $guesses = 0; + $gate->guessPolicyNamesUsing(function () use (&$guesses) { + ++$guesses; + + return [AccessGateTestPolicy::class]; + }); + + // Resolve through gates created by forUser() first, so the result must reach the shared cache. + $first = $gate->forUser((object) ['id' => 2])->getPolicyFor(new AccessGateTestDummy); + $second = $gate->forUser((object) ['id' => 3])->getPolicyFor(new AccessGateTestDummy); + $third = $gate->getPolicyFor(new AccessGateTestDummy); $this->assertInstanceOf(AccessGateTestPolicy::class, $first); $this->assertInstanceOf(AccessGateTestPolicy::class, $second); + $this->assertInstanceOf(AccessGateTestPolicy::class, $third); + $this->assertSame(1, $guesses); } public function testPolicyCacheDoesNotInterfereWithExplicitPolicies() @@ -1603,26 +1614,53 @@ public function testPolicyCacheDoesNotInterfereWithExplicitPolicies() $this->assertInstanceOf(AccessGateTestPolicy::class, $result); } - public function testPolicyCacheIsClearedByFlushState() + public function testPolicyCacheIsClearedWhenGuessCallbackChanges() { $gate = $this->getBasicGate(); $counter = 0; - $gate->guessPolicyNamesUsing(function () use (&$counter) { + $guesser = function () use (&$counter) { ++$counter; return []; - }); + }; + $gate->guessPolicyNamesUsing($guesser); + $gate->getPolicyFor('SomeClassA'); $gate->getPolicyFor('SomeClassA'); $this->assertSame(1, $counter); - // After flush, the guesser should be called again for the same class - Gate::flushState(); + // After the guesser changes, the same class should be guessed again + $gate->guessPolicyNamesUsing($guesser); $gate->getPolicyFor('SomeClassA'); $this->assertSame(2, $counter); } + public function testPolicyCacheIsClearedWhenAPolicyIsRegistered() + { + $gate = $this->getBasicGate(); + $userGate = $gate->forUser((object) ['id' => 2]); + + $this->assertInstanceOf(DummyWithUsePolicyPolicy::class, $gate->getPolicyFor(ChildOfDummyWithUsePolicy::class)); + + $gate->policy(DummyWithUsePolicy::class, AccessGateTestPolicy::class); + + // The registered parent policy precedes the inherited attribute, while the + // earlier gate keeps its previous policies and cached resolution. + $this->assertInstanceOf(AccessGateTestPolicy::class, $gate->getPolicyFor(ChildOfDummyWithUsePolicy::class)); + $this->assertInstanceOf(DummyWithUsePolicyPolicy::class, $userGate->getPolicyFor(ChildOfDummyWithUsePolicy::class)); + } + + public function testPolicyCacheIsNotSharedBetweenGatesWithDifferentPolicies() + { + $registered = $this->getBasicGate(); + $registered->policy(DummyWithUsePolicy::class, AccessGateTestPolicy::class); + $unregistered = $this->getBasicGate(); + + $this->assertInstanceOf(AccessGateTestPolicy::class, $registered->getPolicyFor(ChildOfDummyWithUsePolicy::class)); + $this->assertInstanceOf(DummyWithUsePolicyPolicy::class, $unregistered->getPolicyFor(ChildOfDummyWithUsePolicy::class)); + } + public function testPolicyCacheStoresNullForClassWithNoPolicy() { $gate = $this->getBasicGate(); @@ -1675,10 +1713,7 @@ public function testFlushStateRestoresLazyGuestCallbackCacheSentinel(): void public function testFlushStateClearsAllCaches() { $gate = $this->getBasicGate(); - - // Populate policy cache $gate->policy(AccessGateTestDummy::class, AccessGateTestPolicy::class); - $gate->getPolicyFor(new AccessGateTestDummy); // Populate ability method cache $gate->check('update-dash', new AccessGateTestDummy); @@ -1687,7 +1722,6 @@ public function testFlushStateClearsAllCaches() Gate::flushState(); // Verify everything still works after flush - $gate->policy(AccessGateTestDummy::class, AccessGateTestPolicy::class); $result = $gate->getPolicyFor(new AccessGateTestDummy); $this->assertInstanceOf(AccessGateTestPolicy::class, $result); } diff --git a/tests/Auth/Fixtures/ChildOfDummyWithUsePolicy.php b/tests/Auth/Fixtures/ChildOfDummyWithUsePolicy.php new file mode 100644 index 0000000000..4a566c5b74 --- /dev/null +++ b/tests/Auth/Fixtures/ChildOfDummyWithUsePolicy.php @@ -0,0 +1,12 @@ +assertInstanceOf( + AuthTestUserPolicy::class, + Gate::getPolicyFor(AuthTestUser::class) + ); + + $this->assertInstanceOf( + AuthTestUserPolicy::class, + Gate::getPolicyFor(ModelAuthTestUser::class) + ); + + $this->assertNull( + Gate::getPolicyFor(static::class) + ); + } + + public function testPolicyCanBeGuessedForParallelClassHierarchies(): void + { + $this->assertInstanceOf( + TopTestUserPolicy::class, + Gate::getPolicyFor(TopTestUser::class) + ); + + $this->assertInstanceOf( + SubTestUserPolicy::class, + Gate::getPolicyFor(SubTestUser::class) + ); + } + + public function testPolicyCanBeGuessedUsingCallback(): void + { + Gate::guessPolicyNamesUsing(function () { + return AuthTestUserPolicy::class; + }); + + $this->assertInstanceOf( + AuthTestUserPolicy::class, + Gate::getPolicyFor(AuthTestUser::class) + ); + } + + public function testPolicyCanBeGuessedMultipleTimes(): void + { + Gate::guessPolicyNamesUsing(function () { + return [ + 'App\Policies\TestUserPolicy', + AuthTestUserPolicy::class, + ]; + }); + + $this->assertInstanceOf( + AuthTestUserPolicy::class, + Gate::getPolicyFor(AuthTestUser::class) + ); + } + + public function testPolicyCanBeGivenByAttribute(): void + { + Gate::guessPolicyNamesUsing(fn () => [AuthTestUserPolicy::class]); + + $this->assertInstanceOf(PostPolicy::class, Gate::getPolicyFor(Post::class)); + } + + public function testPolicyGivenByAttributeIsInheritedByChildClasses(): void + { + $this->assertInstanceOf(PostPolicy::class, Gate::getPolicyFor(ChildPost::class)); + $this->assertInstanceOf(PostPolicy::class, Gate::getPolicyFor(GrandchildPost::class)); + } + + public function testPolicyGivenByAttributeOnChildClassOverridesParentAttribute(): void + { + $this->assertInstanceOf(AudioPostPolicy::class, Gate::getPolicyFor(AudioPost::class)); + } + + public function testRegisteredPolicyTakesPrecedenceOverInheritedAttribute(): void + { + Gate::policy(ChildPost::class, AudioPostPolicy::class); + + $this->assertInstanceOf(AudioPostPolicy::class, Gate::getPolicyFor(ChildPost::class)); + } + + public function testGuessedPolicyTakesPrecedenceOverInheritedAttribute(): void + { + Gate::guessPolicyNamesUsing(fn () => [AuthTestUserPolicy::class]); + + $this->assertInstanceOf(AuthTestUserPolicy::class, Gate::getPolicyFor(ChildPost::class)); + } +} + +#[UsePolicy(PostPolicy::class)] +class Post extends Model +{ +} + +class ChildPost extends Post +{ +} + +class GrandchildPost extends ChildPost +{ +} + +#[UsePolicy(AudioPostPolicy::class)] +class AudioPost extends Post +{ +} + +class PostPolicy +{ +} + +class AudioPostPolicy +{ +} diff --git a/tests/Integration/Auth/Fixtures/Models/AuthTestUser.php b/tests/Integration/Auth/Fixtures/Models/AuthTestUser.php new file mode 100644 index 0000000000..7065e465d2 --- /dev/null +++ b/tests/Integration/Auth/Fixtures/Models/AuthTestUser.php @@ -0,0 +1,30 @@ + Date: Thu, 24 Sep 2026 06:15:09 +0000 Subject: [PATCH 10/12] Restore the element type of Number::summarize()'s units While reconciling laravel/framework #49681 (https://github.com/laravel/framework/pull/49681), which Hypervel's native types already cover, summarize() was found without Laravel's array description of its units, which the native array type does not carry. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/support/src/Number.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/support/src/Number.php b/src/support/src/Number.php index fd075e4249..e0b7be8085 100644 --- a/src/support/src/Number.php +++ b/src/support/src/Number.php @@ -223,6 +223,8 @@ public static function forHumans(float|int $number, int $precision = 0, ?int $ma /** * Convert the number to its human-readable equivalent. * + * @param array $units + * * @phpstan-return non-empty-string|false */ protected static function summarize(float|int $number, int $precision = 0, ?int $maxPrecision = null, array $units = []): false|string From aa60e8254fa31090a4ca1c4b5f00c65db21e7e11 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 06:25:29 +0000 Subject: [PATCH 11/12] Remove the deprecated InvokableRule contract Laravel deprecated Contracts\Validation\InvokableRule in 10.0 in favor of ValidationRule, which receives the same attribute, value and failure callback through validate(). No Laravel or Hypervel rule implements it; the validator only adapted it into the same wrapper. With owner approval, Hypervel no longer ships it. The rule parser, Rule::when() and unless(), ConditionalRules, the InvokableValidationRule wrapper, the default-rule cloning and the Data Rule attribute now accept ValidationRule only, and the wrapper calls validate() directly. Rule and ImplicitRule remain the validator's execution contracts. The validation README and the Laravel porting guide describe the migration: implement ValidationRule and rename __invoke to validate. ConditionalRules also rejected the callables Rule::when() and unless() accept, because its condition property only allowed a Closure, so a condition such as [$this, 'shouldRequire'] threw a TypeError. The constructor now accepts any callable and stores it as a Closure. Validation: the Validation, Data and Integration validation suites, FacadeDocblocksTest, composer lint:fix and composer analyse, with a regression test for array-callable conditions. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- .../src/Validation/InvokableRule.php | 21 ------------------- src/data/src/Attributes/Validation/Rule.php | 5 ++--- src/docs/porting-from-laravel.md | 2 ++ src/validation/README.md | 1 + src/validation/src/ConditionalRules.php | 19 ++++++++++------- .../src/InvokableValidationRule.php | 15 +++++-------- src/validation/src/Rule.php | 9 ++++---- .../src/Rules/Concerns/ClonesCustomRules.php | 5 +---- src/validation/src/ValidationRuleParser.php | 6 +++--- .../ValidationDefaultRuleIsolationTest.php | 15 ++----------- tests/Validation/ValidationRuleParserTest.php | 21 +++++++++++++++++++ 11 files changed, 53 insertions(+), 66 deletions(-) delete mode 100644 src/contracts/src/Validation/InvokableRule.php diff --git a/src/contracts/src/Validation/InvokableRule.php b/src/contracts/src/Validation/InvokableRule.php deleted file mode 100644 index cff723468f..0000000000 --- a/src/contracts/src/Validation/InvokableRule.php +++ /dev/null @@ -1,21 +0,0 @@ - */ + /** @var array */ protected array $rules = []; /** * Create a custom rule attribute. */ - public function __construct(string|array|ValidationRule|RuleContract|InvokableRuleContract|ValidationRuleContract ...$rules) + public function __construct(string|array|ValidationRule|RuleContract|ValidationRuleContract ...$rules) { $this->rules = $rules; } diff --git a/src/docs/porting-from-laravel.md b/src/docs/porting-from-laravel.md index c4b00ab354..4508bfa50e 100644 --- a/src/docs/porting-from-laravel.md +++ b/src/docs/porting-from-laravel.md @@ -530,6 +530,8 @@ Handwritten validation parameters use standard CSV quoting. Replace backslash-es `FailOnUnknownFields` accepts the contents of `array` fields without child rules. Add child rules or allowed keys (`array:name,email`) when those contents must be restricted. See [unknown fields](/docs/{{version}}/validation#request-failing-on-unknown-fields). +Laravel's deprecated `InvokableRule` contract is not available. Change rules that implement it to implement `Hypervel\Contracts\Validation\ValidationRule` and rename their `__invoke` method to `validate`; code that calls such a rule object directly must call `validate` or keep its own `__invoke` method. See [rule objects](/docs/{{version}}/validation#using-rule-objects). + ### Request and Input Data diff --git a/src/validation/README.md b/src/validation/README.md index b24e7c4a66..648448759a 100644 --- a/src/validation/README.md +++ b/src/validation/README.md @@ -11,5 +11,6 @@ Documentation: https://hypervel.org/docs/validation - Scalar `in` and `not_in` rules compare the submitted value with the rule's literal values as strings. Numeric strings are not loosely coerced. - Date comparison rules allow a referenced field to be missing or `null` unless it is also required. Unparseable date strings and invalid referenced values fail validation instead of being compared with `null`. - Rule keys may escape a literal asterisk as `\*`, matching the existing `\.` literal-dot syntax. +- Laravel's deprecated `InvokableRule` contract is not provided. Implement `ValidationRule` and its `validate()` method instead. Ported from: https://github.com/laravel/framework diff --git a/src/validation/src/ConditionalRules.php b/src/validation/src/ConditionalRules.php index a028e81f30..fe3cdb4724 100644 --- a/src/validation/src/ConditionalRules.php +++ b/src/validation/src/ConditionalRules.php @@ -5,25 +5,30 @@ namespace Hypervel\Validation; use Closure; -use Hypervel\Contracts\Validation\InvokableRule; use Hypervel\Contracts\Validation\Rule; use Hypervel\Contracts\Validation\ValidationRule; use Hypervel\Support\Fluent; class ConditionalRules { + /** + * The boolean condition indicating if the rules should be added to the attribute. + */ + protected bool|Closure $condition; + /** * Create a new conditional rules instance. * - * @param bool|Closure $condition the boolean condition indicating if the rules should be added to the attribute - * @param array|Closure|InvokableRule|Rule|string|ValidationRule $rules the rules to be added to the attribute - * @param array|Closure|InvokableRule|Rule|string|ValidationRule $defaultRules the rules to be added to the attribute if the condition fails + * @param bool|callable $condition the boolean condition indicating if the rules should be added to the attribute + * @param array|Closure|Rule|string|ValidationRule $rules the rules to be added to the attribute + * @param array|Closure|Rule|string|ValidationRule $defaultRules the rules to be added to the attribute if the condition fails */ public function __construct( - protected bool|Closure $condition, - protected array|Closure|InvokableRule|Rule|string|ValidationRule $rules, - protected array|Closure|InvokableRule|Rule|string|ValidationRule $defaultRules = [] + bool|callable $condition, + protected array|Closure|Rule|string|ValidationRule $rules, + protected array|Closure|Rule|string|ValidationRule $defaultRules = [] ) { + $this->condition = is_bool($condition) ? $condition : $condition(...); } /** diff --git a/src/validation/src/InvokableValidationRule.php b/src/validation/src/InvokableValidationRule.php index d9fcb13b0a..af574b6a65 100644 --- a/src/validation/src/InvokableValidationRule.php +++ b/src/validation/src/InvokableValidationRule.php @@ -6,7 +6,6 @@ use Hypervel\Contracts\Validation\DataAwareRule; use Hypervel\Contracts\Validation\ImplicitRule; -use Hypervel\Contracts\Validation\InvokableRule; use Hypervel\Contracts\Validation\Rule; use Hypervel\Contracts\Validation\ValidationRule; use Hypervel\Contracts\Validation\ValidatorAwareRule; @@ -39,17 +38,17 @@ class InvokableValidationRule implements Rule, ValidatorAwareRule /** * Create a new explicit Invokable validation rule. * - * @param InvokableRule|ValidationRule $invokable the invokable that validates the attribute + * @param ValidationRule $invokable the invokable that validates the attribute */ protected function __construct( - protected InvokableRule|ValidationRule $invokable + protected ValidationRule $invokable ) { } /** * Create a new implicit or explicit Invokable validation rule. */ - public static function make(InvokableRule|ValidationRule $invokable): InvokableValidationRule + public static function make(ValidationRule $invokable): InvokableValidationRule { if ($invokable->implicit ?? false) { return new class($invokable) extends InvokableValidationRule implements ImplicitRule {}; @@ -73,11 +72,7 @@ public function passes(string $attribute, mixed $value): bool $this->invokable->setValidator($this->validator); } - $method = $this->invokable instanceof ValidationRule - ? 'validate' - : '__invoke'; - - $this->invokable->{$method}($attribute, $value, function ($attribute, $message = null) { + $this->invokable->validate($attribute, $value, function ($attribute, $message = null) { $this->failed = true; return $this->pendingPotentiallyTranslatedString($attribute, $message); @@ -89,7 +84,7 @@ public function passes(string $attribute, mixed $value): bool /** * Get the underlying invokable rule. */ - public function invokable(): InvokableRule|ValidationRule + public function invokable(): ValidationRule { return $this->invokable; } diff --git a/src/validation/src/Rule.php b/src/validation/src/Rule.php index 299303b5fa..5b0bf6de0d 100644 --- a/src/validation/src/Rule.php +++ b/src/validation/src/Rule.php @@ -7,7 +7,6 @@ use Closure; use Hypervel\Context\CoroutineContext; use Hypervel\Contracts\Support\Arrayable; -use Hypervel\Contracts\Validation\InvokableRule; use Hypervel\Contracts\Validation\Rule as RuleContract; use Hypervel\Contracts\Validation\ValidationRule; use Hypervel\Support\Arr; @@ -64,8 +63,8 @@ public static function can(string $ability, mixed ...$arguments): Can */ public static function when( bool|callable $condition, - array|Closure|InvokableRule|RuleContract|string|ValidationRule $rules, - array|Closure|InvokableRule|RuleContract|string|ValidationRule $defaultRules = [] + array|Closure|RuleContract|string|ValidationRule $rules, + array|Closure|RuleContract|string|ValidationRule $defaultRules = [] ): ConditionalRules { return new ConditionalRules($condition, $rules, $defaultRules); } @@ -75,8 +74,8 @@ public static function when( */ public static function unless( bool|callable $condition, - array|Closure|InvokableRule|RuleContract|string|ValidationRule $rules, - array|Closure|InvokableRule|RuleContract|string|ValidationRule $defaultRules = [] + array|Closure|RuleContract|string|ValidationRule $rules, + array|Closure|RuleContract|string|ValidationRule $defaultRules = [] ): ConditionalRules { return new ConditionalRules($condition, $defaultRules, $rules); } diff --git a/src/validation/src/Rules/Concerns/ClonesCustomRules.php b/src/validation/src/Rules/Concerns/ClonesCustomRules.php index 84f50cf4c5..f31a090841 100644 --- a/src/validation/src/Rules/Concerns/ClonesCustomRules.php +++ b/src/validation/src/Rules/Concerns/ClonesCustomRules.php @@ -4,7 +4,6 @@ namespace Hypervel\Validation\Rules\Concerns; -use Hypervel\Contracts\Validation\InvokableRule; use Hypervel\Contracts\Validation\Rule; use Hypervel\Contracts\Validation\ValidationRule; @@ -16,9 +15,7 @@ trait ClonesCustomRules public function __clone(): void { foreach ($this->customRules as $key => $rule) { - if ($rule instanceof Rule - || $rule instanceof InvokableRule - || $rule instanceof ValidationRule) { + if ($rule instanceof Rule || $rule instanceof ValidationRule) { $this->customRules[$key] = clone $rule; } } diff --git a/src/validation/src/ValidationRuleParser.php b/src/validation/src/ValidationRuleParser.php index 670c7c55b2..11b8e39340 100644 --- a/src/validation/src/ValidationRuleParser.php +++ b/src/validation/src/ValidationRuleParser.php @@ -7,7 +7,6 @@ use Closure; use Hypervel\Context\CoroutineContext; use Hypervel\Contracts\Validation\CompilableRules; -use Hypervel\Contracts\Validation\InvokableRule; use Hypervel\Contracts\Validation\Rule as RuleContract; use Hypervel\Contracts\Validation\ValidationRule; use Hypervel\Support\Arr; @@ -118,7 +117,9 @@ protected function prepareRule(mixed $rule, string $attribute): mixed $rule = new ClosureValidationRule($rule); } - if ($rule instanceof InvokableRule || $rule instanceof ValidationRule) { + // REMOVED: Laravel's deprecated InvokableRule contract is omitted; + // implement ValidationRule::validate() instead. + if ($rule instanceof ValidationRule) { $rule = InvokableValidationRule::make($rule); } @@ -145,7 +146,6 @@ public static function ruleReducesToString(mixed $rule): bool { return is_object($rule) && ! $rule instanceof Closure - && ! $rule instanceof InvokableRule && ! $rule instanceof ValidationRule && ! $rule instanceof RuleContract && ! $rule instanceof CompilableRules diff --git a/tests/Validation/ValidationDefaultRuleIsolationTest.php b/tests/Validation/ValidationDefaultRuleIsolationTest.php index f593b2e7e8..f969a7ea91 100644 --- a/tests/Validation/ValidationDefaultRuleIsolationTest.php +++ b/tests/Validation/ValidationDefaultRuleIsolationTest.php @@ -5,7 +5,6 @@ namespace Hypervel\Tests\Validation; use Closure; -use Hypervel\Contracts\Validation\InvokableRule; use Hypervel\Contracts\Validation\Rule; use Hypervel\Contracts\Validation\ValidationRule; use Hypervel\Engine\Channel; @@ -46,20 +45,17 @@ public function testCallableResultsAreClonedBeforeMutation(): void public function testNestedExecutableRulesAreClonedWithThePrototype(): void { $legacy = new DefaultLegacyRule; - $invokable = new DefaultInvokableRule; $validation = new DefaultValidationRule; - Password::defaults(Password::min(8)->rules([$legacy, $invokable, $validation])); + Password::defaults(Password::min(8)->rules([$legacy, $validation])); $first = Password::default()->appliedRules()['customRules']; $second = Password::default()->appliedRules()['customRules']; $this->assertNotSame($legacy, $first[0]); - $this->assertNotSame($invokable, $first[1]); - $this->assertNotSame($validation, $first[2]); + $this->assertNotSame($validation, $first[1]); $this->assertNotSame($first[0], $second[0]); $this->assertNotSame($first[1], $second[1]); - $this->assertNotSame($first[2], $second[2]); } public function testConfiguredDefaultsAreIsolatedBetweenCoroutines(): void @@ -125,13 +121,6 @@ public function message(): string } } -class DefaultInvokableRule implements InvokableRule -{ - public function __invoke(string $attribute, mixed $value, Closure $fail): void - { - } -} - class DefaultValidationRule implements ValidationRule { public function validate(string $attribute, mixed $value, Closure $fail): void diff --git a/tests/Validation/ValidationRuleParserTest.php b/tests/Validation/ValidationRuleParserTest.php index 5976186e7e..e9953c1e42 100644 --- a/tests/Validation/ValidationRuleParserTest.php +++ b/tests/Validation/ValidationRuleParserTest.php @@ -102,6 +102,27 @@ public function testConditionalRulesWithDefault(): void ], $rules); } + public function testConditionalRulesAcceptNonClosureCallableConditions(): void + { + $rules = ValidationRuleParser::filterConditionalRules([ + 'name' => Rule::when([$this, 'conditionIsMet'], ['required'], ['nullable']), + 'email' => Rule::unless([$this, 'conditionIsMet'], ['required'], ['nullable']), + ]); + + $this->assertSame([ + 'name' => ['required'], + 'email' => ['nullable'], + ], $rules); + } + + /** + * Determine if the conditional test rules apply. + */ + public function conditionIsMet(Fluent $input): bool + { + return true; + } + public function testEmptyConditionalRulesArePreserved(): void { $isAdmin = true; From 03dc1c12df5f9e400c25a7d0669fe2fe72b8fb79 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Thu, 24 Sep 2026 07:07:09 +0000 Subject: [PATCH 12/12] Describe policy and exception handler precedence accurately The UsePolicy inheritance sentence named registered and discovered policies for the child model, but a policy registered for one of its parent classes also takes precedence over the inherited attribute. The authorization docs now list it. Several boot-only warnings on the exception handler and its withExceptions() configuration said a callback is considered for every later reported or rendered exception. Some exceptions never reach those callbacks, such as an exception that renders its own response, one that reports itself, or a retry decision settled by an earlier match. The warnings now describe what the setting affects and for how long, the way the dontReport() warning already does. Claude-Session: https://claude.ai/code/session_01WveaB7yyFo2T6yo7cnEpW9 --- src/docs/authorization.md | 2 +- .../src/Configuration/Exceptions.php | 40 +++++++++---------- src/foundation/src/Exceptions/Handler.php | 32 +++++++-------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/docs/authorization.md b/src/docs/authorization.md index 845993f098..66d9d9c1f0 100644 --- a/src/docs/authorization.md +++ b/src/docs/authorization.md @@ -410,7 +410,7 @@ class Order extends Model } ``` -Models that extend a class with the `UsePolicy` attribute use its policy as well, unless a policy is registered or discovered for the child model or the child model declares its own `UsePolicy` attribute. +Models that extend a class with the `UsePolicy` attribute use its policy as well, unless the child model declares its own `UsePolicy` attribute, a policy is discovered for it, or a policy is registered for it or one of its parent classes. ## Writing Policies diff --git a/src/foundation/src/Configuration/Exceptions.php b/src/foundation/src/Configuration/Exceptions.php index 242c81a3db..52b59540c5 100644 --- a/src/foundation/src/Configuration/Exceptions.php +++ b/src/foundation/src/Configuration/Exceptions.php @@ -25,8 +25,8 @@ public function __construct(public Handler $handler) /** * Register a reportable callback. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. */ public function report(callable $using): ReportableHandler { @@ -36,8 +36,8 @@ public function report(callable $using): ReportableHandler /** * Register a reportable callback. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. */ public function reportable(callable $reportUsing): ReportableHandler { @@ -47,8 +47,8 @@ public function reportable(callable $reportUsing): ReportableHandler /** * Register a renderable callback. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently rendered exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * rendering for every subsequent request in the worker. */ public function render(callable $using): static { @@ -60,8 +60,8 @@ public function render(callable $using): static /** * Register a renderable callback. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently rendered exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * rendering for every subsequent request in the worker. */ public function renderable(callable $renderUsing): static { @@ -74,7 +74,7 @@ public function renderable(callable $renderUsing): static * Register a callback to prepare the final, rendered exception response. * * Boot-only. The callback replaces the shared handler's response callback for - * every subsequently rendered exception in the worker. + * every subsequent request in the worker. */ public function respond(callable $using): static { @@ -86,8 +86,8 @@ public function respond(callable $using): static /** * Specify the callback that should be used to throttle reportable exceptions. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. */ public function throttle(callable $throttleUsing): static { @@ -99,8 +99,8 @@ public function throttle(callable $throttleUsing): static /** * Register a new exception mapping. * - * Boot-only. The mapping persists on the shared handler and is considered for - * every subsequently reported or rendered exception in the worker. + * Boot-only. The mapping persists on the shared handler and affects exception + * reporting and rendering for every subsequent request and job in the worker. * * @throws InvalidArgumentException */ @@ -114,8 +114,8 @@ public function map(Closure|string $from, Closure|string|null $to = null): stati /** * Set the log level for the given exception type. * - * Boot-only. The level persists on the shared handler and applies to every - * subsequently reported exception of that type in the worker. + * Boot-only. The level persists on the shared handler and affects how later + * exceptions of that type are logged in the worker. * * @param class-string $type * @param LogLevel::* $level @@ -173,8 +173,8 @@ public function dontRetry(array|string $class): static /** * Register a callback to determine if an exception should not be reported. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. * * @param (Closure(Throwable): bool) $dontReportWhen */ @@ -188,8 +188,8 @@ public function dontReportWhen(Closure $dontReportWhen): static /** * Register a callback to determine if an exception should stop job retries. * - * Boot-only. The callback persists on the shared handler and is considered for - * every subsequently failed job in the worker. + * Boot-only. The callback persists on the shared handler and affects job retry + * decisions for every subsequent job in the worker. * * @template TException of Throwable * @@ -232,7 +232,7 @@ public function dontFlash(array|string $attributes): static * Register the callable that determines if the exception handler response should be JSON. * * Boot-only. The callable replaces the shared handler's JSON check for every - * subsequently rendered exception in the worker. + * subsequent request in the worker. */ public function shouldRenderJsonWhen(callable $callback): static { diff --git a/src/foundation/src/Exceptions/Handler.php b/src/foundation/src/Exceptions/Handler.php index 67a890ab48..dc955a59cb 100644 --- a/src/foundation/src/Exceptions/Handler.php +++ b/src/foundation/src/Exceptions/Handler.php @@ -223,8 +223,8 @@ public function register(): void /** * Register a reportable callback. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. */ public function reportable(callable $reportUsing): ReportableHandler { @@ -240,8 +240,8 @@ public function reportable(callable $reportUsing): ReportableHandler /** * Register a renderable callback. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently rendered exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * rendering for every subsequent request in the worker. */ public function renderable(callable $renderUsing): static { @@ -257,8 +257,8 @@ public function renderable(callable $renderUsing): static /** * Register a new exception mapping. * - * Boot-only. The mapping persists on the shared handler and is considered for - * every subsequently reported or rendered exception in the worker. + * Boot-only. The mapping persists on the shared handler and affects exception + * reporting and rendering for every subsequent request and job in the worker. * * @throws InvalidArgumentException */ @@ -297,8 +297,8 @@ public function dontReport(array|string $exceptions): static /** * Register a callback to determine if an exception should not be reported. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. * * @param (callable(Throwable): bool) $dontReportWhen */ @@ -346,8 +346,8 @@ public function dontRetry(array|string $exceptions): static /** * Register a callback to determine if jobs should stop retrying for an exception. * - * Boot-only. The callback persists on the shared handler and is considered for - * every subsequently failed job in the worker. + * Boot-only. The callback persists on the shared handler and affects job retry + * decisions for every subsequent job in the worker. * * @template TException of Throwable * @@ -439,8 +439,8 @@ public function dontFlash(array|string $attributes): static /** * Set the log level for the given exception type. * - * Boot-only. The level persists on the shared handler and applies to every - * subsequently reported exception of that type in the worker. + * Boot-only. The level persists on the shared handler and affects how later + * exceptions of that type are logged in the worker. * * @param class-string $type * @param LogLevel::* $level @@ -650,8 +650,8 @@ protected function throttle(Throwable $e): Lottery|AdmissionPolicy|null /** * Specify the callback that should be used to throttle reportable exceptions. * - * Boot-only. The callback persists on the shared handler and is considered - * for every subsequently reported exception in the worker. + * Boot-only. The callback persists on the shared handler and affects exception + * reporting for every subsequent request and job in the worker. */ public function throttleUsing(callable $throttleUsing): static { @@ -838,7 +838,7 @@ protected function afterResponseCallbacks(): array * Prepare the final, rendered response for an exception using the given callback. * * Boot-only. The callback replaces the shared handler's response callback for - * every subsequently rendered exception in the worker. + * every subsequent request in the worker. */ public function respondUsing(callable $callback): static { @@ -990,7 +990,7 @@ protected function shouldReturnJson(Request $request, Throwable $e): bool * Register the callable that determines if the exception handler response should be JSON. * * Boot-only. The callable replaces the shared handler's JSON check for every - * subsequently rendered exception in the worker. + * subsequent request in the worker. * * @param callable(Request $request, Throwable): bool $callback */