Speeding Up Laravel Tests with tmpfs (MySQL in RAM)
If your Laravel test suite is painfully slow and does a lot of database work (multi-tenancy, migrations, seeding), the bottleneck is almost…
Laravel's $touches property automatically updates parent model timestamps when child models are modified.
class Comment extends Model
{
protected $touches = ['post'];
public function post()
{
return $this->belongsTo(Post::class, 'post_id');
}
}
When any Comment instance is created, updated, or deleted, Laravel automatically updates the updated_at timestamp of its parent Post model.
$touches property for relationship namestouch() on each specified relationshipupdated_at timestamp is updated with the current timeCache keys need to change when related data is modified to prevent serving stale data.
Include parent's updated_at timestamp in cache keys and use $touches on child models:
// Parent model
class Post extends Model
{
public function comments() {
return $this->hasMany(Comment::class);
}
}
// Child model
class Comment extends Model
{
protected $touches = ['post'];
public function post() {
return $this->belongsTo(Post::class);
}
}
// Cache service
class CacheService
{
protected function generateKey($post)
{
return sprintf('post:%d:v%s', $post->id, $post->updated_at->timestamp);
}
}
Result: Modifying any comment automatically changes the parent's timestamp, which changes the cache key, causing cache misses for stale data.
Child models don't require their own timestamps for $touches to function:
class PostTag extends Model
{
public $timestamps = false; // No created_at/updated_at columns
protected $touches = ['post']; // Still updates parent timestamp
}
Multiple relationships can be touched simultaneously:
class Attachment extends Model
{
protected $touches = ['post', 'author'];
public function post() {
return $this->belongsTo(Post::class);
}
public function author() {
return $this->belongsTo(User::class);
}
}
Both post and author models will have their timestamps updated when the attachment changes.
For bulk operations where touching is undesirable:
Model::withoutTouching(function () {
// Operations here won't trigger touches
Comment::where('post_id', 1)->update(['status' => 'approved']);
});
Or disable globally for a model:
Comment::withoutTouching(function () {
// All Comment operations won't touch parents
});
Each touch operation executes an additional UPDATE query:
UPDATE posts SET updated_at = '2024-01-15 10:30:00' WHERE id = 1
Performance impact:
withoutTouching() for bulk updatesupdated_at column$timestamps = true (default)Laravel's implementation uses the touchOwners() method in Illuminate\Database\Eloquent\Model:
public function touchOwners()
{
foreach ($this->touches as $relation) {
$this->$relation()->touch();
if ($this->$relation instanceof self) {
$this->$relation->fireModelEvent('saved', false);
$this->$relation->touchOwners();
}
}
}
Touching triggers the following events on the parent model:
savingupdatingsavedupdatedTouches cascade through multiple levels if intermediate models also have $touches defined:
// Comment -> Post -> Blog
class Comment extends Model
{
protected $touches = ['post'];
}
class Post extends Model
{
protected $touches = ['blog'];
}
Modifying Comment updates both Post and Blog timestamps.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com