Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d7f9286
Include soft-deleted rows in mass pruning
binaryfire Sep 28, 2026
798b55d
Forward validation rule arguments to their constructors
binaryfire Sep 28, 2026
563a92c
Describe query-log callback results precisely
binaryfire Sep 28, 2026
5b8c9c4
Match wildcard paths in missing JSON assertions
binaryfire Sep 28, 2026
4c934a3
Inherit custom model builders and expose violation exceptions
binaryfire Sep 28, 2026
29cbe97
Expose the Vite development server URL
binaryfire Sep 28, 2026
62eefbc
Invalidate Redis connections after transport warnings
binaryfire Sep 28, 2026
33c8e77
Qualify soft-delete columns with query table aliases
binaryfire Sep 28, 2026
3730ca2
Complete native session ID handler methods
binaryfire Sep 28, 2026
8fe79b1
Support explicitly encoded route parameters
binaryfire Sep 28, 2026
61b0013
Retain Resend message IDs on the sent message
binaryfire Sep 28, 2026
283ee53
Accept arrays in cache existence and removal methods
binaryfire Sep 28, 2026
0bb82b6
Type console completion results as lists
binaryfire Sep 28, 2026
7aa51ef
Compare schema index names without case differences
binaryfire Sep 28, 2026
7ae3a3c
Handle null collection matches and expose higher-order sole
binaryfire Sep 28, 2026
dccfea4
Cover truncated UTF-8 in binary detection
binaryfire Sep 28, 2026
538e5c9
Return no factory attributes for a zero count
binaryfire Sep 28, 2026
bf3e365
Add pool-safe cross-disk file transfers
binaryfire Sep 28, 2026
6b110b1
Reject equivalent paths before cross-disk file transfers
binaryfire Sep 28, 2026
4a398ba
Ignore read defaults when checking multiple cache keys
binaryfire Sep 28, 2026
e0edd9f
Match wildcard JSON assertion paths against their structure
binaryfire Sep 28, 2026
df836e0
Use recorded query source aliases for Eloquent qualification
binaryfire Sep 28, 2026
fcfbcfa
Reject a vanished Vite hot file during asset generation
binaryfire Sep 28, 2026
c03b895
Reuse the original message when recording the Resend ID
binaryfire Sep 28, 2026
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
4 changes: 2 additions & 2 deletions src/bus/src/Batch.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public function add(array|object $jobs): ?Batch

$this->queue->connection($this->options['connection'] ?? null)->bulk(
$jobs->all(),
$data = '',
$this->options['queue'] ?? null
data: '',
queue: $this->options['queue'] ?? null
Comment on lines +93 to +94

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Named arguments data:/queue: resolve against the concrete method's parameter names at runtime, and PHP allows a class implementing the Queue contract to legally rename its parameters (e.g. $payload) without breaking interface conformance. A custom queue driver with renamed parameters would then fatal with Error: Unknown named parameter $data. The removed $data = '' assignment was unused after the call, so the cleanup can keep positional arguments to avoid the interop risk. Note upstream Laravel (Illuminate\Bus\Batch::add) still calls bulk($jobs->all(), $data = '', $this->options['queue'] ?? null) positionally.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/bus/src/Batch.php, line 93:

<comment>Named arguments `data:`/`queue:` resolve against the concrete method's parameter names at runtime, and PHP allows a class implementing the `Queue` contract to legally rename its parameters (e.g. `$payload`) without breaking interface conformance. A custom queue driver with renamed parameters would then fatal with `Error: Unknown named parameter $data`. The removed `$data = ''` assignment was unused after the call, so the cleanup can keep positional arguments to avoid the interop risk. Note upstream Laravel (`Illuminate\Bus\Batch::add`) still calls `bulk($jobs->all(), $data = '', $this->options['queue'] ?? null)` positionally.</comment>

<file context>
@@ -90,8 +90,8 @@ public function add(array|object $jobs): ?Batch
                 $jobs->all(),
-                $data = '',
-                $this->options['queue'] ?? null
+                data: '',
+                queue: $this->options['queue'] ?? null
             );
</file context>
Suggested change
data: '',
queue: $this->options['queue'] ?? null
'',
$this->options['queue'] ?? null

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current upstream Batch::add() already uses data: and queue: here. Those parameter names are part of the supported queue API. I am retaining the upstream call rather than supporting overrides that rename its named arguments.

);
});

Expand Down
2 changes: 1 addition & 1 deletion src/cache/src/AnyModeTaggedCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public function pull(UnitEnum|string $key, mixed $default = null): mixed
*
* @throws BadMethodCallException always - tags are for writing and flushing only
*/
public function forget(UnitEnum|string $key): bool
public function forget(array|UnitEnum|string $key): bool
{
throw new BadMethodCallException(
'Cannot forget items via tags in any mode. Tags are for writing and flushing only. '
Expand Down
16 changes: 15 additions & 1 deletion src/cache/src/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ public function __construct(Store $store, array $config = [])
*/
public function has(array|UnitEnum|string $key): bool
{
if (is_array($key)) {
$keys = [];

foreach ($key as $name => $value) {
$keys[] = is_string($name) ? $name : $value;
}

return ! in_array(null, $this->many($keys), true);
}
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

return ! is_null($this->get($key));
}

Expand Down Expand Up @@ -807,8 +817,12 @@ public function funnel(UnitEnum|string $name): ConcurrencyLimiterBuilder
/**
* Remove an item from the cache.
*/
public function forget(UnitEnum|string $key): bool
public function forget(array|UnitEnum|string $key): bool
{
if (is_array($key)) {
return $this->deleteMultiple($key);
}

$key = $key instanceof UnitEnum ? (string) enum_value($key) : $key;

if ($this->events?->hasListeners(ForgettingKey::class)) {
Expand Down
2 changes: 1 addition & 1 deletion src/collections/src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public function containsStrict(mixed $key, mixed $value = null): bool
}

if ($this->useAsCallable($key)) {
return ! is_null($this->first($key));
return array_any($this->items, $key);
}

return in_array($key, $this->items, true);
Expand Down
4 changes: 3 additions & 1 deletion src/collections/src/LazyCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,9 @@ public function containsStrict(mixed $key, mixed $value = null): bool
}

if ($this->useAsCallable($key)) {
return ! is_null($this->first($key));
$placeholder = new stdClass;

return $this->first($key, $placeholder) !== $placeholder;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new containsStrict() callable branch passes $key to first() without the /** @var callable $key */ annotation that the identical contains() branch directly above requires. PHPStan cannot narrow $key to a callable after useAsCallable() (no @phpstan-assert on EnumeratesValues::useAsCallable()), so the parameter's array-key|(callable(TValue): bool)|TValue union can be reported as an argument.type error against first(?callable $callback). Mirror the annotation from the contains() branch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/collections/src/LazyCollection.php, line 323:

<comment>The new `containsStrict()` callable branch passes `$key` to `first()` without the `/** @var callable $key */` annotation that the identical `contains()` branch directly above requires. PHPStan cannot narrow `$key` to a callable after `useAsCallable()` (no `@phpstan-assert` on `EnumeratesValues::useAsCallable()`), so the parameter's `array-key|(callable(TValue): bool)|TValue` union can be reported as an `argument.type` error against `first(?callable $callback)`. Mirror the annotation from the `contains()` branch.</comment>

<file context>
@@ -318,7 +318,9 @@ public function containsStrict(mixed $key, mixed $value = null): bool
-            return ! is_null($this->first($key));
+            $placeholder = new stdClass;
+
+            return $this->first($key, $placeholder) !== $placeholder;
         }
 
</file context>
Suggested change
return $this->first($key, $placeholder) !== $placeholder;
if ($this->useAsCallable($key)) {
$placeholder = new stdClass;
/** @var callable $key */
return $this->first($key, $placeholder) !== $placeholder;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complete source and type-fixture PHPStan checks pass with this branch as written. There is no reported type error to fix, so I am not adding a redundant callable annotation.

}

foreach ($this as $item) {
Expand Down
2 changes: 2 additions & 0 deletions src/collections/src/Traits/EnumeratesValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
* @property-read HigherOrderCollectionProxy<'reject', TValue, static> $reject
* @property-read HigherOrderCollectionProxy<'skipUntil', TValue, static> $skipUntil
* @property-read HigherOrderCollectionProxy<'skipWhile', TValue, static> $skipWhile
* @property-read HigherOrderCollectionProxy<'sole', TValue, static> $sole
* @property-read HigherOrderCollectionProxy<'some', TValue, static> $some
* @property-read HigherOrderCollectionProxy<'sortBy', TValue, static> $sortBy
* @property-read HigherOrderCollectionProxy<'sortByDesc', TValue, static> $sortByDesc
Expand Down Expand Up @@ -96,6 +97,7 @@ trait EnumeratesValues
'reject',
'skipUntil',
'skipWhile',
'sole',
'some',
'sortBy',
'sortByDesc',
Expand Down
4 changes: 2 additions & 2 deletions src/console/src/Concerns/InteractsWithIO.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public function ask(string $question, ?string $default = null): mixed
/**
* Prompt the user for input with auto completion.
*
* @param (callable(string): string[])|iterable $choices
* @param (callable(string): list<string>)|iterable $choices

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: list<string> narrows the autocompleter callback's documented return type further than Symfony's contract. Symfony's Question::setAutocompleterCallback (^8.1) accepts any array of suggestions — keys need not be sequential integers, and values may be strings or ints — so a callback returning a keyed or sparse array (valid at runtime) would be reported as a static-analysis error against (callable(string): list<string>). Keep string[] or document Symfony's actual shape.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/console/src/Concerns/InteractsWithIO.php, line 149:

<comment>`list<string>` narrows the autocompleter callback's documented return type further than Symfony's contract. Symfony's `Question::setAutocompleterCallback` (^8.1) accepts any array of suggestions — keys need not be sequential integers, and values may be strings or ints — so a callback returning a keyed or sparse array (valid at runtime) would be reported as a static-analysis error against `(callable(string): list<string>)`. Keep `string[]` or document Symfony's actual shape.</comment>

<file context>
@@ -146,7 +146,7 @@ public function ask(string $question, ?string $default = null): mixed
      * Prompt the user for input with auto completion.
      *
-     * @param (callable(string): string[])|iterable $choices
+     * @param (callable(string): list<string>)|iterable $choices
      */
     public function anticipate(string $question, iterable|callable $choices, ?string $default = null): mixed
</file context>
Suggested change
* @param (callable(string): list<string>)|iterable $choices
* @param (callable(string): array<int|string, string|int>)|iterable $choices

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symfony QuestionHelper uses the callback result directly and indexes it with zero-based offsets during arrow-key selection. Sparse or string-keyed results do not satisfy that behavior. list accurately describes the supported callback result and matches upstream.

*/
public function anticipate(string $question, iterable|callable $choices, ?string $default = null): mixed
{
Expand All @@ -156,7 +156,7 @@ public function anticipate(string $question, iterable|callable $choices, ?string
/**
* Prompt the user for input with auto completion.
*
* @param (callable(string): string[])|iterable $choices
* @param (callable(string): list<string>)|iterable $choices
*/
public function askWithCompletion(string $question, iterable|callable $choices, ?string $default = null): mixed
{
Expand Down
2 changes: 1 addition & 1 deletion src/contracts/src/Cache/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public function touch(UnitEnum|string $key, DateInterval|DateTimeInterface|int $
/**
* Remove an item from the cache.
*/
public function forget(UnitEnum|string $key): bool;
public function forget(array|UnitEnum|string $key): bool;

/**
* Get the cache store implementation.
Expand Down
1 change: 1 addition & 0 deletions src/database/src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ public function withoutPretending(Closure $callback): mixed
/**
* Execute the given callback in "dry run" mode.
*
* @param (Closure(): (array{query: string, bindings: array, time: null|float}[])) $callback
* @return array{query: string, bindings: array, time: null|float}[]
*/
protected function withFreshQueryLog(Closure $callback): array
Expand Down
26 changes: 25 additions & 1 deletion src/database/src/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,16 @@ public function qualifyColumn(Expression|string $column): string
{
$column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column;

if (! is_null($alias = $this->getTableAlias())) {
if (! str_contains($column, '.')) {
return $alias . '.' . $column;
}

if (str_starts_with($column, $table = $this->model->getTable() . '.')) {
return $alias . '.' . substr($column, strlen($table));
}
}

return $this->model->qualifyColumn($column);
}

Expand All @@ -2023,7 +2033,21 @@ public function qualifyColumn(Expression|string $column): string
*/
public function qualifyColumns(array $columns): array
{
return $this->model->qualifyColumns($columns);
$qualified = [];

foreach ($columns as $key => $column) {
$qualified[$key] = $this->qualifyColumn($column);
}

return $qualified;
}

/**
* Get the alias or table name that qualifies the query's source columns.
*/
protected function getTableAlias(): ?string
{
return $this->query->getFromAlias();
}

/**
Expand Down
19 changes: 14 additions & 5 deletions src/database/src/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -522,11 +522,13 @@ protected function throwMissingAttributeExceptionIfApplicable(string $key): mixe
&& ! $this->wasRecentlyCreated
&& static::preventsAccessingMissingAttributes()
&& ! CoroutineContext::get(self::MISSING_ATTRIBUTE_ACCESS_SUPPRESSED_CONTEXT_KEY, false)) {
$exception = new MissingAttributeException($this, $key);

if (isset(static::$missingAttributeViolationCallback)) {
return call_user_func(static::$missingAttributeViolationCallback, $this, $key);
return call_user_func(static::$missingAttributeViolationCallback, $this, $key, $exception);
}

throw new MissingAttributeException($this, $key);
throw $exception;
}

return null;
Expand Down Expand Up @@ -600,15 +602,22 @@ public function isRelation(string $key): bool
*/
protected function handleLazyLoadingViolation(string $key): mixed
{
$exception = new LazyLoadingViolationException($this, $key);

if (isset(static::$lazyLoadingViolationCallback)) {
return call_user_func(static::$lazyLoadingViolationCallback, $this, $key);
return call_user_func(
static::$lazyLoadingViolationCallback,
$this,
$key,
$exception
);
}

if (! $this->exists || $this->wasRecentlyCreated) {
return null;
}

throw new LazyLoadingViolationException($this, $key);
throw $exception;
}

/**
Expand Down Expand Up @@ -1981,7 +1990,7 @@ public function getOriginal(?string $key = null, mixed $default = null): mixed
{
return (new static)->setRawAttributes(
$this->original,
$sync = true
sync: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Keep this call positional because setRawAttributes is a public overridable method. A model override with a compatible but differently named second parameter will make getOriginal() fail with an unknown named parameter; pass true positionally instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/database/src/Eloquent/Concerns/HasAttributes.php, line 1993:

<comment>Keep this call positional because `setRawAttributes` is a public overridable method. A model override with a compatible but differently named second parameter will make `getOriginal()` fail with an unknown named parameter; pass `true` positionally instead.</comment>

<file context>
@@ -1981,7 +1990,7 @@ public function getOriginal(?string $key = null, mixed $default = null): mixed
         return (new static)->setRawAttributes(
             $this->original,
-            $sync = true
+            sync: true
         )->getOriginalWithoutRewindingModel($key, $default);
     }
</file context>
Suggested change
sync: true
true

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call and parameter name match current upstream. The named sync argument is part of the supported method API; changing that parameter name in an override also breaks callers using that API. No extra compatibility path is needed here.

)->getOriginalWithoutRewindingModel($key, $default);
}

Expand Down
4 changes: 4 additions & 0 deletions src/database/src/Eloquent/Factories/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ public function raw(callable|array $attributes = [], ?Model $parent = null): arr
return $this->state($attributes)->getExpandedAttributes($parent);
}

if ($this->count < 1) {
return [];
}

return array_map(function () use ($attributes, $parent) {
return $this->state($attributes)->getExpandedAttributes($parent);
}, range(1, $this->count));
Expand Down
11 changes: 5 additions & 6 deletions src/database/src/Eloquent/MassPrunable.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@ trait MassPrunable
*/
public function pruneAll(int $chunkSize = 1000): int
{
$query = tap($this->prunable(), function ($query) use ($chunkSize) {
$query->when(! $query->getQuery()->limit, function ($query) use ($chunkSize) {
$query->limit($chunkSize);
});
$softDeletable = static::isSoftDeletable();

$query = tap($this->prunable(), function (Builder $query) use ($chunkSize, $softDeletable): void {
$query->when($softDeletable, fn (Builder $query): Builder => $query->withTrashed())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: withTrashed() here can never change the executed SQL: for soft-deletable models pruneAll always calls forceDelete(), and Builder::forceDelete() runs $this->query->delete() directly without applyScopes() (unlike delete(), which goes through toBase()). The soft-delete filter was already absent on this path, so the new testPrunesActiveAndSoftDeletedRecords passes regardless of this line and does not exercise it. Drop the inert call, or if the goal is scope-aware pruning, make the mechanism explicit.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/database/src/Eloquent/MassPrunable.php, line 21:

<comment>`withTrashed()` here can never change the executed SQL: for soft-deletable models pruneAll always calls `forceDelete()`, and `Builder::forceDelete()` runs `$this->query->delete()` directly without `applyScopes()` (unlike `delete()`, which goes through `toBase()`). The soft-delete filter was already absent on this path, so the new `testPrunesActiveAndSoftDeletedRecords` passes regardless of this line and does not exercise it. Drop the inert call, or if the goal is scope-aware pruning, make the mechanism explicit.</comment>

<file context>
@@ -15,17 +15,16 @@ trait MassPrunable
+        $softDeletable = static::isSoftDeletable();
+
+        $query = tap($this->prunable(), function (Builder $query) use ($chunkSize, $softDeletable): void {
+            $query->when($softDeletable, fn (Builder $query): Builder => $query->withTrashed())
+                ->when(! $query->getQuery()->limit, fn (Builder $query): Builder => $query->limit($chunkSize));
         });
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default forceDelete() bypasses scopes, but custom Eloquent builders may apply them. withTrashed() expresses the intended pruning query for that extension point and matches upstream. The regression test still verifies that both active and soft-deleted rows are pruned.

->when(! $query->getQuery()->limit, fn (Builder $query): Builder => $query->limit($chunkSize));
});

$total = 0;
$events = null;

$softDeletable = static::isSoftDeletable();

do {
$total += $count = $softDeletable
? $query->forceDelete()
Expand Down
54 changes: 27 additions & 27 deletions src/database/src/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use Hypervel\Database\Eloquent\Relations\HasManyThrough;
use Hypervel\Database\Eloquent\Relations\Pivot;
use Hypervel\Database\Eloquent\Relations\Relation;
use Hypervel\Database\LazyLoadingViolationException;
use Hypervel\Database\Query\Builder as QueryBuilder;
use Hypervel\Engine\Coroutine;
use Hypervel\Support\Arr;
Expand Down Expand Up @@ -228,9 +229,9 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
/**
* The callback that is responsible for handling lazy loading violations.
*
* @var null|(callable(self, string): mixed)
* @var null|(callable(self, string, LazyLoadingViolationException): mixed)
*/
protected static $lazyLoadingViolationCallback;
protected static mixed $lazyLoadingViolationCallback = null;

/**
* Indicates if an exception should be thrown instead of silently discarding non-fillable attributes.
Expand All @@ -240,9 +241,9 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
/**
* The callback that is responsible for handling discarded attribute violations.
*
* @var null|(callable(self, array): mixed)
* @var null|(callable(self, array, MassAssignmentException): mixed)
*/
protected static $discardedAttributeViolationCallback;
protected static mixed $discardedAttributeViolationCallback = null;

/**
* Indicates if an exception should be thrown when trying to access a missing attribute on a retrieved model.
Expand All @@ -252,9 +253,9 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
/**
* The callback that is responsible for handling missing attribute violations.
*
* @var null|(callable(self, string): mixed)
* @var null|(callable(self, string, MissingAttributeException): mixed)
*/
protected static $missingAttributeViolationCallback;
protected static mixed $missingAttributeViolationCallback = null;

/**
* Indicates if invalid value exceptions during implicit route model binding should be reported.
Expand Down Expand Up @@ -679,7 +680,7 @@ public static function automaticallyEagerLoadRelationships(bool $value = true):
* Boot-only. The callback persists in a static property for the worker
* lifetime and runs on every lazy-loading violation across all coroutines.
*
* @param null|(callable(self, string): mixed) $callback
* @param null|(callable(self, string, LazyLoadingViolationException): mixed) $callback
*/
public static function handleLazyLoadingViolationUsing(?callable $callback): void
{
Expand All @@ -704,7 +705,7 @@ public static function preventSilentlyDiscardingAttributes(bool $value = true):
* lifetime and runs on every discarded-attribute violation across all
* coroutines.
*
* @param null|(callable(self, array): mixed) $callback
* @param null|(callable(self, array, MassAssignmentException): mixed) $callback
*/
public static function handleDiscardedAttributeViolationUsing(?callable $callback): void
{
Expand All @@ -729,7 +730,7 @@ public static function preventAccessingMissingAttributes(bool $value = true): vo
* lifetime and runs on every missing-attribute violation across all
* coroutines.
*
* @param null|(callable(self, string): mixed) $callback
* @param null|(callable(self, string, MissingAttributeException): mixed) $callback
*/
public static function handleMissingAttributeViolationUsing(?callable $callback): void
{
Expand Down Expand Up @@ -796,14 +797,16 @@ public function fill(array $attributes): static
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded || static::preventsSilentlyDiscardingAttributes()) {
$exception = new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key,
get_class($this)
));

if (isset(static::$discardedAttributeViolationCallback)) {
call_user_func(static::$discardedAttributeViolationCallback, $this, [$key]);
call_user_func(static::$discardedAttributeViolationCallback, $this, [$key], $exception);
} else {
throw new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key,
get_class($this)
));
throw $exception;
}
}
}
Expand All @@ -812,14 +815,16 @@ public function fill(array $attributes): static
&& static::preventsSilentlyDiscardingAttributes()) {
$keys = array_diff(array_keys($attributes), array_keys($fillable));

$exception = new MassAssignmentException(sprintf(
'Add fillable property [%s] to allow mass assignment on [%s].',
implode(', ', $keys),
get_class($this)
));

if (isset(static::$discardedAttributeViolationCallback)) {
call_user_func(static::$discardedAttributeViolationCallback, $this, $keys);
call_user_func(static::$discardedAttributeViolationCallback, $this, $keys, $exception);
} else {
throw new MassAssignmentException(sprintf(
'Add fillable property [%s] to allow mass assignment on [%s].',
implode(', ', $keys),
get_class($this)
));
throw $exception;
}
}

Expand Down Expand Up @@ -2016,12 +2021,7 @@ public function newEloquentBuilder(QueryBuilder $query): Builder
*/
protected function resolveCustomBuilderClass(): string|false
{
$attributes = (new ReflectionClass($this))
->getAttributes(UseEloquentBuilder::class);

return ! empty($attributes)
? $attributes[0]->newInstance()->builderClass
: false;
return static::resolveClassAttribute(UseEloquentBuilder::class, 'builderClass') ?? false;
}

/**
Expand Down
Loading
Loading