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

标签 php mysql laravel datetime

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

迁移.php

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

我使用过dateTimetimezone。我陷入了这样的陷阱:“我不需要日期,只需时间。我将日期保存在其他地方。”然后,我会找到一个建议保存为时间戳的线程,并比较那里的日期。

我有一个预订工厂来生成(以及其他详细信息)datetime_starttime_end:

Factory.php

'date' => date('Y-m-d'),
'time_start' => '15:00:00',
'time_end' => '17:00:00',

我读过的大多数帖子都建议使用 strtotime 进行比较。像这样的东西:

'time_start' => strtotime('15:00:00'),  // 1582210800

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

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

Controller.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
}
...

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

->whereTime('created_at', '=', '11:20:45') 

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

测试.php

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

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

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

如果您能提供建议以更好地理解如何:

  • 最好保存时间时间时间戳日期时间
  • 使用(或不使用)strtotime。如果是这样,理想的数据类型是什么? 时间戳日期时间
  • 使用适当的错误响应; 400 是理想的吗?

非常感谢您的任何想法。

更新

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

我现在将 time_starttime_end 保存为迁移中的 dateTime 字段。

Controller.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);

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

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

最佳答案

您最好将此信息存储为两个 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();

您还可以将 time_starttime_end 添加到模型的 $dates 数组中,以利用 automatic casting .

说到模型,如果您的关系设置正确,则此查询可能如下所示,而不是使用 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();

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


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

The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. This code is used in situations where the user might be able to resolve the conflict and resubmit the request. The server SHOULD generate a payload that includes enough information for a user to recognize the source of the conflict.

关于php - Laravel 是开始和结束之间的时间(防止重复预订),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60320244/

相关文章:

php 不向收件人发送邮件

PHP MySQL并发,数据库中的最大项目数有限

php - Mysql LIKE 显示未知字符

php - Laravel - 请解释命名 Controller 路由 'uses' , 'as'

laravel - 如何重写 Laravel Facade 方法?

php - 为 PHP 设置 Apache 时遇到问题

php - 无法从 Html5 CSS 和 php 表单获取信息

php - 我收到 laravel mysql [2002] 连接错误

MySQL 加入前一条出现的记录

php - Laravel 迁移在现有数据库表中添加列问题