[13.x] Fix touch() query generation on HasOneOrManyThrough relations

Merged
HA
25d ago
+107-0

overrides touch() and adds allRelatedIds() on HasOneOrManyThrough, matching the same pattern already used in BelongsToMany::touch().

HasOneOrManyThrough currently inherits Relation::touch() that calls rawUpdate() on the relation query:

public function rawUpdate(array $attributes = [])
{
    return $this->query->withoutGlobalScopes()->update($attributes);
}

Because HasManyThrough and HasOneThrough queries use an INNER JOIN against the intermediate table, running ->touch() causes two bugs:

  1. SQL crashes:
    • On MySQL and MariaDB, it crashes with: Column 'updated_at' in field list is ambiguous (both tables have an updated_at column).
    • On SQLite and PostgreSQL, UPDATE ... INNER JOIN queries throw syntax errors.
  2. Intermediate soft-deletes leakage:
    • Calling withoutGlobalScopes() strips the intermediate soft-delete scope. Because of that, records belonging to soft-deleted intermediate parents get touched by mistake.

Fix

Follow the same approach as BelongsToMany:

  • Added allRelatedIds() to pluck the IDs of the related models.
  • Overrode touch() on HasOneOrManyThrough to pluck those IDs and run a clean whereIn update without any joins.

Tests

Added integration tests to both DatabaseEloquentHasManyThroughIntegrationTest and DatabaseEloquentHasOneThroughIntegrationTest covering:

  • Updating timestamps via touch().
  • Ensuring soft-deleted intermediate models are respected and not touched.
  • Respecting Model::withoutTouching().
  • Verifying allRelatedIds() returns the expected collection of IDs.