Laravel 是开始和结束之间的时间(防止重复预订)

Dam*_*mon 2 php mysql datetime laravel

我正在编写测试以确保新的预订不会与另一个预订重复预订。我已经阅读了无数其他的 SO 线程,现在我更加困惑,不确定我做的事情是否正确。我在我的项目以及这个示例中专门使用了 Laravel。

迁移.php

...
$table->date('date');       // 2020-01-01
$table->time('time_start'); // 15:00:00
$table->time('time_end');   // 17:00:00
...
Run Code Online (Sandbox Code Playgroud)

我曾与dateTime或合作过timezone。我陷入了这样的陷阱:“我不需要日期,只需要时间。我把日期保存在其他地方。” 然后我会找到一个建议另存为的线程timestamp并比较那里的日期。

我有一个预订工厂要生成(以及其他详细信息)datetime_start并且time_end

工厂.php

'date' => date('Y-m-d'),
'time_start' => '15:00:00',
'time_end' => '17:00:00',
Run Code Online (Sandbox Code Playgroud)

我读过的大多数线程建议比较使用strtotime. 就像是:

'time_start' => strtotime('15:00:00'),  // 1582210800
Run Code Online (Sandbox Code Playgroud)

这是有道理的。但后来我读到,由于时区的原因,保存为dateTimetimezone更好。

在我的控制器中,我正在检查现有的预订,如下所示:

控制器.php

...
$existing = DB::table('reservations')
    ->where('asset_id', '=', $request->asset_id)
    ->whereDate('date', '=', $request->date)
    ->whereTime('time_start', '>=', $request->time_start)  // or use $request->strtotime('time_start')
    ->whereTime('time_end', '<=', $request->time_end)
    ->where(function ($query) {
        $query
            ->where('status', '=', 'created')
            ->orWhere('status', '=', 'pending')
            ->orWhere('status', '=', 'completed');
    })
    ->get();

if ($existing->count() > 0) {
    // Not allowed
} else {
    // OK to proceed
}
...
Run Code Online (Sandbox Code Playgroud)

使用whereTime看起来正是我所需要的:

->whereTime('created_at', '=', '11:20:45') 
Run Code Online (Sandbox Code Playgroud)

看起来会被保存为一time列。在我的测试中,我检查400如果无法创建它是否会得到返回。

测试.php

...
$http->assertStatus(400)
    ->assertJsonStructure([
        'type', 'data' => [
            'reason'
        ]])
        ->assertJson([
            'type' => 'reservations',
            'data' => [
                'reason' => 'Asset is no longer available.',
            ],
        ]);
Run Code Online (Sandbox Code Playgroud)

这很好用。15:00:00如果我创建相同17:00:00日期/资产等的预订。我的测试通过。我完全按照我的预期返回了 400 错误。但是,如果我通过了,15:01:00我的测试就会失败。并不感到惊讶,但这告诉我我没有正确处理比较。看起来我已经快到终点了,但随后两只鞋都解开了。

用户界面将只是一个带有人类可读时间的下拉菜单。我原本打算将这些值保存为 24 小时时间。例如,15:00:00。我不知道还能怎么做......

我将不胜感激您的建议,以更好地理解如何:

  • 最好节省时间time,,timestampdatetime
  • 使用strtotime(或不使用)。如果是这样,理想的数据类型是什么?timestamp, datetime?
  • 使用适当的错误响应;400是理想的吗?

非常感谢您的任何想法。

更新

遵循@miken32的建议 - 我确实已经以这种方式建立了关系,所以这是有道理的。

我现在正在储蓄time_starttime_end作为dateTime我迁移中的一个领域。

控制器.php

$asset = Asset::find($request->asset_id);

$existing = $asset->reservations()
    ->where(function ($query) use ($request) {
        $start_dt = new Carbon($request->time_start);
        $end_dt = new Carbon($request->time_end);

        $query->where('time_start', '>=', $start_dt)
            ->where('time_end', '<=', $end_dt);
        })
        ->whereIn('status', ['created', 'pending', 'completed'])
        ->get();

    if ($existing->count() > 0) {
        // Log::info('CANNOT MAKE RESERVATION FOR: ' . $request->first_name . ' ' . $request->last_name);
        return response()->json(['type' => 'reservations', 'data' => ['reason' => 'Asset is no longer available.']], 409);
    } else {
        $reservation = new Reservation();
        ...
        // Log::info('RESERVATION MADE FOR: ' . $reservation->first_name . ' ' . $reservation->last_name);
Run Code Online (Sandbox Code Playgroud)

用户只能选择预先确定的时间。一旦为任何给定资产保留了时隙,该块就不再可用。我确信我本质上是在确保某人无法(以某种方式)POST用不同的值覆盖请求。

希望这对其他人有帮助。如果我的实施失败,请告诉我,以便我可以为其他人纠正它。

mik*_*n32 5

您最好将此信息存储为两DATETIME列。优点包括能够利用 Laravel 内置的 Carbon 日期转换,并避免在午夜预订约会的麻烦。

然后,假设$request->time_start$request->time_end是完整的日期/时间,您的查询将变成这样:

$existing = DB::table('reservations')
    ->where('asset_id', $request->asset_id)
    ->where(
        fn ($q) => $q->whereBetween('time_start', [$request->time_start, $request->time_end])
            ->orWhereBetween('time_end', [$request->time_start, $request->time_end])
            ->orWhere(
                fn ($q) => $q->where('time_start', '<', $request->time_start)
                    ->where('time_end', '>', $request->time_end);
            )
    )
    ->whereIn('status', ['created', 'pending', 'completed'])
    ->get();
Run Code Online (Sandbox Code Playgroud)

您还可以将time_start和添加time_end到模型的$dates数组中以利用自动转换

说到模型,如果你的关系设置正确,这个查询可能像这样,而不是使用外观DB

$asset = Asset::find($request->asset_id);
$existing = $asset
    ->reservations()
    ->where(
        fn ($q) => $q->whereBetween('time_start', [$request->time_start, $request->time_end])
            ->orWhereBetween('time_end', [$request->time_start, $request->time_end])
            ->orWhere(
                fn ($q) => $q->where('time_start', '<', $request->time_start)
                    ->where('time_end', '>', $request->time_end)
            )
    )
    ->whereIn('status', ['created', 'pending', 'completed'])
    ->get();
Run Code Online (Sandbox Code Playgroud)

它并不短,但在我看来,它让人们更容易一目了然地看到正在搜索的内容。


至于 HTTP 响应,使用什么并不重要。这都是您的代码,因此您知道会发生什么。但如果你想学究气(我完全支持),也许409可能适合你的需要?

409(冲突)状态代码表示由于与目标资源的当前状态发生冲突,请求无法完成。此代码用于用户可能能够解决冲突并重新提交请求的情况。服务器应该生成一个有效负载,其中包含足够的信息,以便用户识别冲突的来源。