定制 aaronfrancis/reservable 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

aaronfrancis/reservable

Composer 安装命令:

composer require aaronfrancis/reservable

包简介

Eloquent model reservation/locking through Laravel's cache lock system

README 文档

README

Latest Version on Packagist Tests Total Downloads PHP Version License

Eloquent model reservation/locking through Laravel's cache lock system.

This package allows you to temporarily "reserve" Eloquent models using Laravel's atomic cache locks. This is useful when you need to ensure exclusive access to a model for a period of time, such as during background processing.

Installation

composer require aaronfrancis/reservable

Publish the migration:

php artisan vendor:publish --tag=reservable-migrations
php artisan migrate

Optionally publish the config:

php artisan vendor:publish --tag=reservable-config

Requirements

This package requires Laravel's database cache driver. Make sure your cache_locks table exists (created by Laravel's default cache migration).

The published migration adds generated columns to parse reservation keys into queryable columns. This allows efficient querying of reserved/unreserved models.

Supported databases: PostgreSQL, MySQL/MariaDB, SQLite

Usage

Add the Reservable trait to your model:

use AaronFrancis\Reservable\Concerns\Reservable;

class Video extends Model
{
    use Reservable;
}

Reserve a model

// Reserve for 60 seconds (default)
$video->reserve('processing');

// Reserve for a specific duration in seconds
$video->reserve('processing', 300); // 5 minutes

// Reserve until a specific time
$video->reserve('processing', now()->addHour());

// Reserve with CarbonInterval
$video->reserve('processing', CarbonInterval::minutes(5));
$video->reserve('processing', CarbonInterval::hours(2));

The reserve() method returns true if the lock was acquired, or false if the model is already reserved.

Check if reserved

if ($video->isReserved('processing')) {
    // Model is currently reserved
}

Release a reservation

$video->releaseReservation('processing');

Blocking reserve

Wait for a lock to become available instead of failing immediately:

// Wait up to 10 seconds (default) for the lock
$video->blockingReserve('processing', duration: 60);

// Wait up to 30 seconds
$video->blockingReserve('processing', duration: 60, wait: 30);

// With CarbonInterval
$video->blockingReserve('processing', CarbonInterval::minutes(5), wait: 30);

Returns true if the lock was acquired, false if the wait time expired.

Reserve with callback

Automatically release the lock when your work is done:

$result = $video->reserveWhile('processing', 300, function ($video) {
    // Do work while holding the lock...
    return $video->transcode();
}); // Lock is automatically released

The lock is released even if the callback throws an exception. Returns false if the lock couldn't be acquired.

Extend a reservation

Add more time to an existing reservation without releasing it:

$video->reserve('processing', 60);

// Later, if you need more time:
$video->extendReservation('processing', 60); // Add 60 more seconds

Only reservations acquired by the current process can be extended.

Returns true if the reservation was extended, false if no active reservation exists or the current process does not own the lock.

Query scopes

Find unreserved models:

$available = Video::unreserved('processing')->get();

Find reserved models:

$reserved = Video::reserved('processing')->get();

Note: These scopes return point-in-time results. By the time you try to reserve a model from the results, another process may have already reserved it. Use reserveFor for atomic find-and-reserve operations.

Find and reserve in one query:

// Get unreserved models and reserve them atomically
$videos = Video::reserveFor('processing', 60)->limit(5)->get();

The reserveFor scope filters to unreserved models, then atomically reserves each one that's returned. Models that can't be reserved (race condition) are filtered out.

Note: Because of race conditions, reserveFor()->limit(5) may return fewer than 5 results. If another process reserves a model between the query and the lock attempt, that model is excluded from the results.

Key types

Reservation keys can be strings, enums, or objects:

// String key
$video->reserve('processing');

// Enum key
$video->reserve(JobType::Transcoding);

// Object key (uses class name)
$video->reserve($someService);

Multiple reservation types

A model can have multiple different reservation types simultaneously:

$video->reserve('transcoding');
$video->reserve('thumbnail-generation');

$video->isReserved('transcoding'); // true
$video->isReserved('thumbnail-generation'); // true
$video->isReserved('uploading'); // false

How it works

Reservable builds on Laravel's atomic cache locks, which use the cache_locks table to provide database-level mutual exclusion.

Lock key format

When you call $video->reserve('processing'), Reservable creates a cache lock with a specially formatted key:

reservation:{morph_class}:{model_id}:{reservation_key}

For example: reservation:App\Models\Video:42:processing

Generated columns

The challenge with cache locks is that they're not inherently queryable by model. You can't efficiently ask "give me all Videos that aren't locked" because the lock table doesn't know about your models.

Reservable solves this by adding generated columns to the cache_locks table that parse the key format:

Column Extracted From Example Value
is_reservation Key contains reservation: true
model_type Second segment App\Models\Video
model_id Third segment 42
type Fourth segment processing

These columns are computed automatically by the database whenever a row is inserted or updated. The exact SQL varies by database engine (PostgreSQL uses split_part(), MySQL uses SUBSTRING_INDEX(), SQLite uses substr()).

Efficient queries

With generated columns in place, the reserved() and unreserved() scopes become simple JOIN queries:

-- Find unreserved videos
SELECT * FROM videos
WHERE NOT EXISTS (
    SELECT 1 FROM cache_locks
    WHERE model_type = 'App\Models\Video'
    AND model_id = videos.id
    AND type = 'processing'
    AND expiration > UNIX_TIMESTAMP()
)

This is much more efficient than fetching all videos and checking each lock individually in PHP.

Atomic reservations

The reserve() method uses Laravel's Lock::get() which performs an atomic database operation—either the lock is acquired or it isn't. There's no window where two processes can both think they have the lock.

The reserveFor scope combines this with query filtering: it finds unreserved models, then attempts to reserve each one, filtering out any that fail due to race conditions.

The CacheLock model

Reservable uses an Eloquent model (AaronFrancis\Reservable\Models\CacheLock) to query the cache_locks table. This model provides the reservations() relationship on your reservable models, allowing you to access active locks:

$video->reservations; // Collection of active CacheLock models

You can swap this for a custom model in the config if you need to add functionality.

Configuration

// config/reservable.php

return [
    // The model representing cache locks
    'model' => AaronFrancis\Reservable\Models\CacheLock::class,
];

Testing

composer test

License

MIT

aaronfrancis/reservable 适用场景与选型建议

aaronfrancis/reservable 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 127 次下载、GitHub Stars 达 7, 最近一次更新时间为 2025 年 12 月 28 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「cache」 「laravel」 「lock」 「eloquent」 「reservation」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 aaronfrancis/reservable 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 aaronfrancis/reservable 我们能提供哪些服务?
定制开发 / 二次开发

基于 aaronfrancis/reservable 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 127
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 7
  • 点击次数: 24
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 7
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-12-28