Skip to content
Merged
18 changes: 11 additions & 7 deletions src/database/src/Concerns/BuildsQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ public function chunk(int $count, callable $callback): bool
throw new InvalidArgumentException('The chunk size should be at least 1');
}

$this->enforceOrderBy();
$clone = clone $this;

$skip = $this->getOffset();
$remaining = $this->getLimit();
$clone->enforceOrderBy();

$skip = $clone->getOffset();
$remaining = $clone->getLimit();

$page = 1;

Expand All @@ -61,7 +63,7 @@ public function chunk(int $count, callable $callback): bool
break;
}

$results = $this->offset($offset)->limit($limit)->get();
$results = $clone->offset($offset)->limit($limit)->get();

$countResults = $results->count();

Expand Down Expand Up @@ -246,12 +248,14 @@ public function lazy(int $chunkSize = 1000): LazyCollection
throw new InvalidArgumentException('The chunk size should be at least 1');
}

$this->enforceOrderBy();

$skip = $this->getOffset();
$remaining = $this->getLimit();

return new LazyCollection(function () use ($chunkSize, $skip, $remaining): Generator {
$clone = clone $this;

$clone->enforceOrderBy();

$page = 1;

while (true) {
Expand All @@ -263,7 +267,7 @@ public function lazy(int $chunkSize = 1000): LazyCollection
return;
}

$results = $this->offset($offset)->limit($limit)->get();
$results = $clone->offset($offset)->limit($limit)->get();

foreach ($results as $result) {
yield $result;
Expand Down
24 changes: 20 additions & 4 deletions src/database/src/Eloquent/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Hypervel\Database\Eloquent\Relations\Concerns\AsPivot;
use Hypervel\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
use Hypervel\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable;
use Hypervel\Database\Eloquent\Relations\Concerns\SupportsPivotInverseRelations;
use Hypervel\Database\MultipleRecordsFoundException;
use Hypervel\Database\Query\Builder as QueryBuilder;
use Hypervel\Database\Query\Grammars\MySqlGrammar;
Expand Down Expand Up @@ -44,6 +45,7 @@ class BelongsToMany extends Relation
{
use InteractsWithDictionary;
use InteractsWithPivotTable;
use SupportsPivotInverseRelations;

/**
* The intermediate table for the relation.
Expand Down Expand Up @@ -256,9 +258,21 @@ public function match(array $models, EloquentCollection $results, string $relati
$key = $this->getDictionaryKey($model->{$this->parentKey});

if ($key !== null && isset($dictionary[$key])) {
$items = $dictionary[$key];

// Correct $this->parent to the actual parent for each group of results...
if ($this->declaringInverseRelationship) {
foreach ($items as $item) {
$item->{$this->accessor}?->setRelation(

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: $items is shared across parents with the same key, so this loop repeatedly rewrites the same pivot inverse and leaves matched results pointing to the last parent. Give each parent its own related and pivot instances before setting the inverse.

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

<comment>`$items` is shared across parents with the same key, so this loop repeatedly rewrites the same pivot inverse and leaves matched results pointing to the last parent. Give each parent its own related and pivot instances before setting the inverse.</comment>

<file context>
@@ -256,9 +258,21 @@ public function match(array $models, EloquentCollection $results, string $relati
+                // Correct $this->parent to the actual parent for each group of results...
+                if ($this->declaringInverseRelationship) {
+                    foreach ($items as $item) {
+                        $item->{$this->accessor}?->setRelation(
+                            $this->declaringInverseRelationship,
+                            $model
</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.

Same-key parents already share their eager-loaded related and pivot objects. The inverse refers to the correct record; independent object graphs for differently mutated in-memory copies are not guaranteed. Adding cloning only here would change existing sharing behavior, so this remains unchanged.

$this->declaringInverseRelationship,
$model
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
);
}
}

$model->setRelation(
$relation,
$this->related->newCollection($dictionary[$key])
$this->related->newCollection($items)
);
}
}
Expand Down Expand Up @@ -1202,9 +1216,11 @@ protected function hydratePivotRelation(array $models): void
// and create a new Pivot model, which is basically a dynamic model that we
// will set the attributes, table, and connections on it so it will work.
foreach ($models as $model) {
$model->setRelation($this->accessor, $this->newExistingPivot(
$this->migratePivotAttributes($model)
));
$pivot = $this->newExistingPivot($this->migratePivotAttributes($model));

$this->applyChaperonesToPivot($pivot, $this->parent, $model);

$model->setRelation($this->accessor, $pivot);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

declare(strict_types=1);

namespace Hypervel\Database\Eloquent\Relations\Concerns;

use Hypervel\Database\Eloquent\Model;
use Hypervel\Database\Eloquent\RelationNotFoundException;
use Hypervel\Support\Arr;
use Hypervel\Support\Str;

trait SupportsPivotInverseRelations
{
/**
* The name of the declaring model's relationship on the pivot.
*/
protected ?string $declaringInverseRelationship = null;

/**
* The name of the related model's relationship on the pivot.
*/
protected ?string $relatedInverseRelationship = null;

/**
* Instruct Eloquent to link the declaring and related models back to the pivot after the relationship query has run.
*
* @return $this
*/
public function chaperone(?string $declaring = null, ?string $related = null): static
{
if (! $this->using) {

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: chaperone() returns without recording anything while $this->using is unset, so calling using() later silently leaves the pivot unchaperoned. Preserve the request until a custom pivot is configured, or document and enforce that using() must come first.

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

<comment>`chaperone()` returns without recording anything while `$this->using` is unset, so calling `using()` later silently leaves the pivot unchaperoned. Preserve the request until a custom pivot is configured, or document and enforce that `using()` must come first.</comment>

<file context>
@@ -0,0 +1,117 @@
+     */
+    public function chaperone(?string $declaring = null, ?string $related = null): static
+    {
+        if (! $this->using) {
+            return $this;
+        }
</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.

Documented in 7785b79: using must precede chaperone. This retains the upstream no-custom-pivot no-op without adding deferred configuration state.

return $this;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$pivotModel = new $this->using;

$this->declaringInverseRelationship = $this->resolvePivotInverseRelation(
$pivotModel,
$declaring,
$this->foreignPivotKey,
$this->parent
);

$this->relatedInverseRelationship = $this->resolvePivotInverseRelation(
$pivotModel,
$related,
$this->relatedPivotKey,
$this->related
);
Comment thread
greptile-apps[bot] marked this conversation as resolved.

// Both sides can guess the same model name, so keep it only on a side named
// explicitly or identified by its pivot key.
if ($this->declaringInverseRelationship !== null
&& $this->declaringInverseRelationship === $this->relatedInverseRelationship) {
$relation = $this->declaringInverseRelationship;

if ($declaring === null && ($related !== null || $this->relationNameFromPivotKey($this->foreignPivotKey, $this->parent) !== $relation)) {
$this->declaringInverseRelationship = null;
}

if ($related === null && ($declaring !== null || $this->relationNameFromPivotKey($this->relatedPivotKey, $this->related) !== $relation)) {
$this->relatedInverseRelationship = null;
}
}

return $this;
}

/**
* Remove the chaperone relationships for this query.
*
* @return $this
*/
public function withoutChaperone(): static
{
$this->declaringInverseRelationship = null;
$this->relatedInverseRelationship = null;

return $this;
}

/**
* Resolve the inverse relation name on the pivot for a given model.
*
* If an explicit name is provided and invalid, an exception is thrown.
* If guessing fails, null is returned.
*
* @throws RelationNotFoundException
*/
protected function resolvePivotInverseRelation(Model $pivotModel, ?string $relation, string $foreignKey, Model $model): ?string
{
if ($relation !== null) {
if (! $pivotModel->isRelation($relation)) {
throw RelationNotFoundException::make($pivotModel, $relation);
}

return $relation;
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
}

return $this->guessPivotInverseRelation($pivotModel, $foreignKey, $model);
}

/**
* Attempt to guess the inverse relation name on the pivot for a given model.
*/
protected function guessPivotInverseRelation(Model $pivotModel, string $foreignKey, Model $model): ?string
{
$candidates = array_filter(array_unique([
$this->relationNameFromPivotKey($foreignKey, $model),
Str::camel(class_basename($model)),
Comment on lines +109 to +110

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Wrong self-referencing pivot inverse

On a self-referencing User pivot with owner_id and friend_id, both owner() and user() can refer to owner_id while no friend() relation exists. The new fallback picks user for the friend side. Because the inferred names differ, the collision check does not run, and chaperone() stores the friend as pivot->user. Accessing that inverse returns the friend instead of the owner.

Knowledge Base Used: Database access and modeling

Fix in Claude Code Fix in Codex

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 same owner_id/friend_id keys and owner()/user() names can also describe a valid pivot where user() belongs to friend_id. Name-based inference cannot distinguish that from an alias on owner_id. Use chaperone(declaring: 'user') for the reported alias configuration; the explicit name wins and the conflicting related guess is cleared. Calling relationship methods on an empty pivot to inspect foreign keys would change the inference contract and can depend on missing attributes or cause side effects, so no additional runtime inspection is being added.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right. With the ambiguous owner_id/friend_id layout, relation names alone cannot distinguish an alias on owner_id from a legitimate user() relation on friend_id. The documented disambiguation is chaperone(declaring: 'user'); when both sides resolve to user, the collision logic preserves the explicit declaring side and clears the conflicting inferred related side.

Since inspecting relationship methods would alter the inference contract and introduce attribute/side-effect concerns, the original finding is not actionable. I’m withdrawing it.

]));

return Arr::first(
$candidates,
fn (string $relation): bool => $pivotModel->isRelation($relation)

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: isRelation() accepts any existing method, not only methods returning a relationship, so inference can select a helper such as user() and explicit names such as save also pass validation. Validate that each configured pivot method actually returns an Eloquent relation before hydrating it.

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

<comment>`isRelation()` accepts any existing method, not only methods returning a relationship, so inference can select a helper such as `user()` and explicit names such as `save` also pass validation. Validate that each configured pivot method actually returns an Eloquent relation before hydrating it.</comment>

<file context>
@@ -0,0 +1,117 @@
+
+        return Arr::first(
+            $candidates,
+            fn (string $relation): bool => $pivotModel->isRelation($relation)
+        );
+    }
</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.

This follows the established Eloquent isRelation convention, also used by existing inverse hydration. Passing a non-relationship method is invalid API use; invoking arbitrary methods for validation could itself cause side effects. The valid self-reference inference case is fixed separately.

);
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}

/**
* Derive the inverse relationship name from a pivot key.
*/
protected function relationNameFromPivotKey(string $pivotKey, Model $model): string
{
return Str::camel(Str::beforeLast($pivotKey, $model->getKeyName()));
}

/**
* Apply chaperone relationships to a pivot model instance.
*/
protected function applyChaperonesToPivot(Model $pivot, Model $declaring, Model $related): void
{
if ($this->declaringInverseRelationship) {
$pivot->setRelation($this->declaringInverseRelationship, $declaring);
}

if ($this->relatedInverseRelationship) {
$pivot->setRelation($this->relatedInverseRelationship, $related);
}
Comment on lines +132 to +138

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: When the declaring and related inverse relation names resolve to the same string, the second setRelation in applyChaperonesToPivot silently overwrites the first, leaving the pivot's declaring relation pointing at the related model. This happens with self-referential many-to-many relations where both pivot keys are the same name (e.g. belongsToMany(User::class, 'friendships', 'user_id', 'user_id')) and the pivot defines a single matching relation such as user(). chaperone() resolves both sides from the same foreignPivotKey/relatedPivotKey and only validates each with isRelation(), so there is no check preventing the collision, and the match() re-correction only rewrites the declaring side.

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

<comment>When the declaring and related inverse relation names resolve to the same string, the second `setRelation` in `applyChaperonesToPivot` silently overwrites the first, leaving the pivot's declaring relation pointing at the related model. This happens with self-referential many-to-many relations where both pivot keys are the same name (e.g. `belongsToMany(User::class, 'friendships', 'user_id', 'user_id')`) and the pivot defines a single matching relation such as `user()`. `chaperone()` resolves both sides from the same `foreignPivotKey`/`relatedPivotKey` and only validates each with `isRelation()`, so there is no check preventing the collision, and the `match()` re-correction only rewrites the declaring side.</comment>

<file context>
@@ -0,0 +1,117 @@
+     */
+    protected function applyChaperonesToPivot(Model $pivot, Model $declaring, Model $related): void
+    {
+        if ($this->declaringInverseRelationship) {
+            $pivot->setRelation($this->declaringInverseRelationship, $declaring);
+        }
</file context>
Suggested change
if ($this->declaringInverseRelationship) {
$pivot->setRelation($this->declaringInverseRelationship, $declaring);
}
if ($this->relatedInverseRelationship) {
$pivot->setRelation($this->relatedInverseRelationship, $related);
}
if ($this->declaringInverseRelationship && $this->declaringInverseRelationship !== $this->relatedInverseRelationship) {
$pivot->setRelation($this->declaringInverseRelationship, $declaring);
}
if ($this->relatedInverseRelationship) {
$pivot->setRelation($this->relatedInverseRelationship, $related);
}

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.

Fixed the valid distinct-column self-reference case in 7785b79. A shared model basename is no longer an automatic candidate, so key-derived guesses identify the correct side. Skipping the declaring assignment during hydration would still leave ambiguity and would not fix eager matching.

}
}
44 changes: 44 additions & 0 deletions src/docs/eloquent-relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Filtering Queries via Intermediate Table Columns](#filtering-queries-via-intermediate-table-columns)
- [Ordering Queries via Intermediate Table Columns](#ordering-queries-via-intermediate-table-columns)
- [Defining Custom Intermediate Table Models](#defining-custom-intermediate-table-models)
- [Automatically Hydrating Pivot Relationships](#automatically-hydrating-pivot-relationships)
- [Polymorphic Relationships](#polymorphic-relationships)
- [One to One](#one-to-one-polymorphic-relations)
- [One to Many](#one-to-many-polymorphic-relations)
Expand Down Expand Up @@ -1035,6 +1036,49 @@ class RoleUser extends Pivot
}
```

<a name="automatically-hydrating-pivot-relationships"></a>
#### Automatically Hydrating Pivot Relationships

When a custom pivot model defines `belongsTo` relationships for the declaring and related models, you may invoke `chaperone` after `using` to automatically hydrate those relationships on each pivot model. This avoids additional queries when accessing the models through the pivot:

```php
use Hypervel\Database\Eloquent\Model;
use Hypervel\Database\Eloquent\Relations\BelongsTo;
use Hypervel\Database\Eloquent\Relations\BelongsToMany;
use Hypervel\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

class Role extends Model
{
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)
->using(RoleUser::class)
->chaperone();
}
}
```

Eloquent infers the relationship names from the pivot keys and model names. You may also pass the declaring and related relationship names to `chaperone` explicitly:

```php
return $this->belongsToMany(User::class)
->using(RoleUser::class)
->chaperone(declaring: 'role', related: 'user');
```

<a name="polymorphic-relationships"></a>
## Polymorphic Relationships

Expand Down
2 changes: 2 additions & 0 deletions src/docs/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class InvalidOrderException extends Exception
}
```

You may use `Exceptions::contextForException($exception)` to get an exception's complete log context without reporting it. The returned array includes global context, the exception's context, and the exception itself.

<a name="the-report-helper"></a>
#### The `report` Helper

Expand Down
4 changes: 3 additions & 1 deletion src/docs/queues.md
Original file line number Diff line number Diff line change
Expand Up @@ -3047,7 +3047,7 @@ The `pause`, `pauseFor`, and `resume` methods also accept enums for queue and co

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.
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 `connectionName` 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`.

Expand Down Expand Up @@ -4038,3 +4038,5 @@ Event::listen(function (WorkerIdle $event) {
When an interrupting signal is delivered to running jobs, Hypervel dispatches a `Hypervel\Queue\Events\JobInterrupted` event once for each job that was notified. Its `connectionName`, `job`, and `signal` properties identify the interrupted work.

Queue workers also dispatch a `WorkerStopping` event before they stop. You may register a listener using `Queue::stopping` in the `boot` method of a service provider. Its `connectionName` and `queue` properties identify the worker, while `terminatesImmediately` is `true` when the process will be terminated as soon as the listeners return. In that case, listeners should not start cleanup that must finish after the listener returns.

When a job times out, the worker normally ends its own process with `SIGKILL`, so a process supervisor sees the signal rather than an exit status. If you need to control how the worker terminates, you may register a callback using `Hypervel\Queue\Worker::killUsing` in a service provider's `boot` method. The callback receives the worker's exit status after the `WorkerStopping` event is dispatched. If the callback returns, the worker is terminated as usual.
8 changes: 8 additions & 0 deletions src/foundation/src/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,14 @@ public function stopIgnoring(array|string $exceptions): static
return $this;
}

/**
* Create the context array for logging the given exception.
*/
public function contextForException(Throwable $e): array
{
return $this->buildExceptionContext($e);
}

/**
* Create the context array for logging the given exception.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function map(FlattenException $exception): FlattenException
/**
* Find the compiled view file for the given compiled path.
*/
protected function findCompiledView(string $compiledPath): ?string
public function findCompiledView(string $compiledPath): ?string
{
return once(fn () => $this->getKnownPaths())[$compiledPath] ?? null;
}
Expand Down Expand Up @@ -135,7 +135,7 @@ protected function filterViewData(array $data): array
/**
* Detect the line number in the original blade file.
*/
protected function detectLineNumber(string $filename, int $compiledLineNumber): int
public function detectLineNumber(string $filename, int $compiledLineNumber): int
{
$source = @file_get_contents($filename);

Expand Down
2 changes: 1 addition & 1 deletion src/queue/src/Events/QueuePaused.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class QueuePaused
* Create a new event instance.
*/
public function __construct(
public string $connection,
public string $connectionName,

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: This renames the public connection property on QueuePaused/QueueResumed to connectionName. It matches upstream laravel/framework#61388 (targeted at 14.x, not the 13.x line this package ports from), but it is a breaking change for app listeners that still read $event->connection — in PHP 8.4 that read returns null with only a deprecation notice, so affected pause/resume handling fails silently. Consider documenting the BC break (release/porting notes) or keeping a compatibility alias for the old property.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/queue/src/Events/QueuePaused.php, line 16:

<comment>This renames the public `connection` property on `QueuePaused`/`QueueResumed` to `connectionName`. It matches upstream laravel/framework#61388 (targeted at 14.x, not the 13.x line this package ports from), but it is a breaking change for app listeners that still read `$event->connection` — in PHP 8.4 that read returns `null` with only a deprecation notice, so affected pause/resume handling fails silently. Consider documenting the BC break (release/porting notes) or keeping a compatibility alias for the old property.</comment>

<file context>
@@ -13,7 +13,7 @@ class QueuePaused
      */
     public function __construct(
-        public string $connection,
+        public string $connectionName,
         public string $queue,
         public DateInterval|DateTimeInterface|int|null $ttl = null,
</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.

connectionName is the current upstream API. Hypervel 0.4 is unreleased and follows that API without an alias for its earlier property. Event consumers and documentation are aligned.

public string $queue,
public DateInterval|DateTimeInterface|int|null $ttl = null,
) {
Expand Down
2 changes: 1 addition & 1 deletion src/queue/src/Events/QueueResumed.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class QueueResumed
* Create a new event instance.
*/
public function __construct(
public string $connection,
public string $connectionName,
public string $queue,
) {
}
Expand Down
6 changes: 3 additions & 3 deletions src/queue/src/Events/WorkerStopping.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ class WorkerStopping
* Create a new event instance.
*
* @param null|float|int $memoryUsage the memory usage of the worker in megabytes
* @param bool $terminatesImmediately whether the process terminates as soon as listeners return; listeners must not start cleanup that must finish before returning when this is true
* @param bool $terminatesImmediately whether the process terminates as soon as listeners return; when true, listeners must not start cleanup that must finish after they return
*/
public function __construct(
public ?string $connectionName = null,

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: Reordering these promoted parameters breaks existing consumers that construct the public WorkerStopping event positionally; old status/options/reason arguments now bind to the wrong fields. Preserve the existing parameter order and append the new fields, or migrate construction to named arguments without changing the established positional contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/queue/src/Events/WorkerStopping.php, line 19:

<comment>Reordering these promoted parameters breaks existing consumers that construct the public `WorkerStopping` event positionally; old status/options/reason arguments now bind to the wrong fields. Preserve the existing parameter order and append the new fields, or migrate construction to named arguments without changing the established positional contract.</comment>

<file context>
@@ -13,17 +13,17 @@ class WorkerStopping
+     * @param bool $terminatesImmediately whether the process terminates as soon as listeners return; when true, listeners must not start cleanup that must finish after they return
      */
     public function __construct(
+        public ?string $connectionName = null,
+        public ?string $queue = null,
         public int $status = 0,
</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.

This follows the current upstream constructor order from laravel/framework#61714. Hypervel 0.4 is unreleased and does not retain compatibility with its earlier signatures. All framework callers use the current order or named arguments.

public ?string $queue = null,
public int $status = 0,
public ?WorkerOptions $workerOptions = null,
public ?WorkerStopReason $reason = null,
public ?int $jobsProcessed = null,
public float|int|null $lastJobProcessedAt = null,
public float|int|null $memoryUsage = null,
public ?string $connectionName = null,
public ?string $queue = null,
public bool $terminatesImmediately = false,
) {
}
Expand Down
Loading
Loading