Skip to content

Commit 68e47cd

Browse files
authored
Fix loaded relation index lookups in StoredWorkflow (#361)
1 parent 1785f13 commit 68e47cd

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

src/Models/StoredWorkflow.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public function findLogByIndex(int $index, bool $fresh = false): ?StoredWorkflow
146146
if ($this->relationLoaded('logs')) {
147147
/** @var Collection<int, StoredWorkflowLog> $logs */
148148
$logs = $this->getRelation('logs');
149-
return $logs->firstWhere('index', $index);
149+
return $logs->first(static fn (StoredWorkflowLog $log): bool => self::modelHasIndex($log, $index));
150150
}
151151

152152
return $this->logs()
@@ -197,7 +197,9 @@ public function findTimerByIndex(int $index): ?StoredWorkflowTimer
197197
if ($this->relationLoaded('timers')) {
198198
/** @var Collection<int, StoredWorkflowTimer> $timers */
199199
$timers = $this->getRelation('timers');
200-
return $timers->firstWhere('index', $index);
200+
return $timers->first(
201+
static fn (StoredWorkflowTimer $timer): bool => self::modelHasIndex($timer, $index)
202+
);
201203
}
202204

203205
return $this->timers()
@@ -334,4 +336,12 @@ protected function recursivePrune(self $workflow): void
334336
$workflow->delete();
335337
}
336338
}
339+
340+
private static function modelHasIndex(Model $model, int $index): bool
341+
{
342+
// Use raw attributes so loaded relations never fall back to Eloquent's magic relation lookup.
343+
$attributes = $model->getAttributes();
344+
345+
return array_key_exists('index', $attributes) && (int) $attributes['index'] === $index;
346+
}
337347
}

tests/Unit/Models/StoredWorkflowTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,33 @@ public function testFindLogByIndexUsesLoadedLogsRelation(): void
323323
$this->assertCount(0, DB::getQueryLog());
324324
}
325325

326+
public function testFindLogByIndexDoesNotUseFirstWhereForLoadedLogsRelation(): void
327+
{
328+
$workflow = StoredWorkflow::create([
329+
'class' => 'TestWorkflow',
330+
'status' => 'running',
331+
]);
332+
333+
$log = $workflow->logs()
334+
->create([
335+
'index' => 0,
336+
'now' => now(),
337+
'class' => 'test',
338+
]);
339+
340+
$workflow->setRelation('logs', new class([$log]) extends \Illuminate\Database\Eloquent\Collection {
341+
public function firstWhere($key, $operator = null, $value = null)
342+
{
343+
throw new \BadMethodCallException('Loaded log lookup should not rely on firstWhere.');
344+
}
345+
});
346+
347+
$foundLog = $workflow->findLogByIndex(0);
348+
349+
$this->assertNotNull($foundLog);
350+
$this->assertSame($log->id, $foundLog->id);
351+
}
352+
326353
public function testCreateLogSyncsLoadedLogsRelation(): void
327354
{
328355
$workflow = StoredWorkflow::create([
@@ -372,6 +399,33 @@ public function testFindTimerByIndexUsesLoadedTimersRelation(): void
372399
$this->assertCount(0, DB::getQueryLog());
373400
}
374401

402+
public function testFindTimerByIndexDoesNotUseFirstWhereForLoadedTimersRelation(): void
403+
{
404+
$workflow = StoredWorkflow::create([
405+
'class' => 'TestWorkflow',
406+
'status' => 'running',
407+
]);
408+
409+
$timer = $workflow->timers()
410+
->create([
411+
'index' => 3,
412+
'stop_at' => now()
413+
->addSecond(),
414+
]);
415+
416+
$workflow->setRelation('timers', new class([$timer]) extends \Illuminate\Database\Eloquent\Collection {
417+
public function firstWhere($key, $operator = null, $value = null)
418+
{
419+
throw new \BadMethodCallException('Loaded timer lookup should not rely on firstWhere.');
420+
}
421+
});
422+
423+
$foundTimer = $workflow->findTimerByIndex(3);
424+
425+
$this->assertNotNull($foundTimer);
426+
$this->assertSame($timer->id, $foundTimer->id);
427+
}
428+
375429
public function testFindTimerByIndexQueriesWhenTimersRelationIsNotLoaded(): void
376430
{
377431
$workflow = StoredWorkflow::create([

0 commit comments

Comments
 (0)