Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/api-client/src/PendingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 = [])
Expand All @@ -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)
Expand All @@ -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)
Expand Down
69 changes: 40 additions & 29 deletions src/auth/src/Access/Gate.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Hypervel\Auth\Access;

use ArrayObject;
use Closure;
use Exception;
use Hypervel\Auth\Access\Events\GateEvaluated;
Expand Down Expand Up @@ -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<class-string, class-string|false>
* @var null|ArrayObject<string, class-string|false>
*/
protected static array $policyClassCache = [];
protected ?ArrayObject $policyClassCache = null;

/**
* Cached guest-access results for class methods.
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)
Expand All @@ -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
*/
Expand All @@ -647,7 +648,7 @@ private function resolvePolicyClass(string $class): string|false
}
}

return false;
return $this->getPolicyFromAttribute($class, includeParents: true) ?? false;
}

/**
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -1124,7 +1136,6 @@ protected function buildScopeSelection(
*/
public static function flushState(): void
{
static::$policyClassCache = [];
static::$guestMethodCache = [];
static::$guestCallbackCache = null;
static::$abilityMethodCache = [];
Expand Down
3 changes: 2 additions & 1 deletion src/broadcasting/src/BroadcastManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,7 +49,7 @@
use function Hypervel\Support\enum_value;

/**
* @mixin \Hypervel\Broadcasting\Broadcasters\Broadcaster
* @mixin BaseBroadcaster
*/
class BroadcastManager implements BroadcastingFactoryContract
{
Expand Down
8 changes: 4 additions & 4 deletions src/collections/src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
* @template-covariant TValue
*
* @implements ArrayAccess<TKey, TValue>
* @implements \Hypervel\Support\Enumerable<TKey, TValue>
* @implements Enumerable<TKey, TValue>
*/
class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable, Transient
{
/**
* @use \Hypervel\Support\Traits\EnumeratesValues<TKey, TValue>
* @use EnumeratesValues<TKey, TValue>
*/
use EnumeratesValues;

Expand Down Expand Up @@ -93,7 +93,7 @@ public function all(): array
/**
* Get a lazy collection for the items in this collection.
*
* @return \Hypervel\Support\LazyCollection<TKey, TValue>
* @return LazyCollection<TKey, TValue>
*/
public function lazy(): LazyCollection
{
Expand Down Expand Up @@ -856,7 +856,7 @@ public function merge(mixed $items): static
* @template TMergeRecursiveValue
*
* @param Arrayable<TKey, TMergeRecursiveValue>|iterable<TKey, TMergeRecursiveValue> $items
* @return static<TKey, TMergeRecursiveValue|TValue>
* @return static<TKey, (TKey is int ? TMergeRecursiveValue|TValue : array<array-key, mixed>|TMergeRecursiveValue|TValue)>
*/
public function mergeRecursive(mixed $items): static
{
Expand Down
2 changes: 1 addition & 1 deletion src/collections/src/Enumerable.php
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ public function merge(mixed $items): static;
* @template TMergeRecursiveValue
*
* @param Arrayable<TKey, TMergeRecursiveValue>|iterable<TKey, TMergeRecursiveValue> $items
* @return static<TKey, TMergeRecursiveValue|TValue>
* @return static<TKey, (TKey is int ? TMergeRecursiveValue|TValue : array<array-key, mixed>|TMergeRecursiveValue|TValue)>
*/
public function mergeRecursive(mixed $items): static;

Expand Down
13 changes: 7 additions & 6 deletions src/collections/src/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Hypervel\Contracts\Support\Arrayable;
use Hypervel\Support\Arr;
use Hypervel\Support\Collection;

Expand All @@ -12,8 +13,8 @@
* @template TKey of array-key
* @template TValue
*
* @param null|\Hypervel\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $value
* @return \Hypervel\Support\Collection<TKey, TValue>
* @param null|Arrayable<TKey, TValue>|iterable<TKey, TValue> $value
* @return Collection<TKey, TValue>
*/
function collect($value = []): Collection
{
Expand Down Expand Up @@ -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
*/
Expand All @@ -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)
{
Expand Down
5 changes: 3 additions & 2 deletions src/container/src/Attributes/BindWhen.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Attribute;
use Closure;
use Hypervel\Contracts\Container\Container;

/**
* Define a binding selected by a boot-stable condition.
Expand All @@ -25,15 +26,15 @@ 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;

/**
* 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)
{
Expand Down
21 changes: 0 additions & 21 deletions src/contracts/src/Validation/InvokableRule.php

This file was deleted.

5 changes: 2 additions & 3 deletions src/data/src/Attributes/Validation/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@
namespace Hypervel\Data\Attributes\Validation;

use Attribute;
use Hypervel\Contracts\Validation\InvokableRule as InvokableRuleContract;
use Hypervel\Contracts\Validation\Rule as RuleContract;
use Hypervel\Contracts\Validation\ValidationRule as ValidationRuleContract;
use Hypervel\Data\Support\Validation\ValidationRule;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class Rule extends ValidationRule
{
/** @var array<array|InvokableRuleContract|RuleContract|string|ValidationRule|ValidationRuleContract> */
/** @var array<array|RuleContract|string|ValidationRule|ValidationRuleContract> */
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;
}
Expand Down
Loading
Loading