はじめに

Laravelで「開始日時」と「終了日時」を登録する際に、日付と時刻の入力欄がそれぞれに用意されているようなケースで、「開始日時 < 終了日時」であることをバリデーションする方法。

追記: こっちのやり方使った方が良いかも。
Laravel でコンマ区切りのフォームデータを良い感じにバリデーションする

TL;DR

  • クロージャ内で日時データを組み立てて比較することで解決(暫定)
  • 他にも色々と試したけどダメだった
  • Laravel 歴が浅いので鵜呑みにしないこと

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. やりたいこと
    2. 解決策(暫定)
      1. イケてない点
    3. その他 試したこと
      1. rules にパラメータを追加
      2. withValidator でゴニョゴニョ
  5. まとめ
  6. その他・メモ
  7. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.1
BuildVersion: 19B88

$ php -v
PHP 7.3.9 (cli) (built: Sep 10 2019 17:45:01) ( NTS )

$ composer -V
Composer version 1.9.0 2019-08-02 20:55:32

$ composer info laravel/framework
name : laravel/framework
versions : * v5.7.28

詳細

やりたいこと

「開始日時」と「終了日時」を登録する際、日付と時刻の入力欄がそれぞれに用意されているようなケースにおいて、「開始日時 < 終了日時」であることをバリデーションしたい。

some/form.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<form action="/hoge" method="POST">
@csrf

{{-- 開催日時 --}}
<input name="start_date" type="date" value="{{ old('start_date') }}" required>
@if ($errors->has("start_date"))
<strong>{{ $errors->first('start_date') }}</strong>
@endif

<input name="start_time" type="time" value="{{ old('start_time') }}" required>
@if ($errors->has("start_time"))
<strong>{{ $errors->first('start_time') }}</strong>
@endif

{{-- 終了日時 --}}
<input name="end_date" type="date" value="{{ old('end_date') }}" required>
@if ($errors->has("end_date"))
<strong>{{ $errors->first('end_date') }}</strong>
@endif

<input name="end_time" type="time" value="{{ old('end_time') }}" required>
@if ($errors->has("end_time"))
<strong>{{ $errors->first('end_time') }}</strong>
@endif
{{-- 略 --}}
</form>

解決策(暫定)

end_time のバリデーション最後でクロージャを使って、中で日時データを組み立てて比較するようにした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function rules()
{
return [
'start_date' => 'required|date_format:Y-m-d',
'start_time' => 'required|date_format:H:i',
'end_date' => 'required|date_format:Y-m-d',
'end_time' => [
'required',
'date_format:H:i',
function($attribute, $value, $fail) {
$start_datetime = Carbon::parse($this->start_date.' '.$this->start_time);
$end_datetime = Carbon::parse($this->end_date.' '.$this->end_time);
if ($end_datetime <= $start_datetime) {
$fail('終了日時は開始日時より後にしてください。');
}
},
],
];
}

イケてない点

'end_time' のエラーとなるので、エラーとなった入力欄を強調表示(Bootstrap の class="is-invalid" 付与など)する際には、start_date, start_time, end_date でも $errors->has('end_time') を見ないといけない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 <form action="/hoge" method="POST">
@csrf

{{-- 開催日時 --}}
<input name="start_date" type="date" value="{{ old('start_date') }}" required>
- @if ($errors->has("start_date"))
+ @if ($errors->has("start_date") || $errors->has("end_time"))
<strong>{{ $errors->first('start_date') }}</strong>
@endif

<input name="start_time" type="time" value="{{ old('start_time') }}" required>
- @if ($errors->has("start_time"))
+ @if ($errors->has("start_time") || $errors->has("end_time"))
<strong>{{ $errors->first('start_time') }}</strong>
@endif

{{-- 終了日時 --}}
<input name="end_date" type="date" value="{{ old('end_date') }}" required>
- @if ($errors->has("end_date"))
+ @if ($errors->has("end_date") || $errors->has("end_time"))
<strong>{{ $errors->first('end_date') }}</strong>
@endif

<input name="end_time" type="time" value="{{ old('end_time') }}" required>
@if ($errors->has("end_time"))
<strong>{{ $errors->first('end_time') }}</strong>
@endif
{{-- 略 --}}
</form>

※上記例では特にメッセージ表示やフォームの強調表示は行っておらず、どのように影響があるかのみを示している。

その他 試したこと

備忘のためにも残しておく。

rules にパラメータを追加

$this->request->parameters にフォームパラメータが詰まっているので、rules の中で $xxxx_datetime として組み上げて $this->request->parameters に追加すれば良きに計らってくれることを期待したがダメだった。

$this->request->parametersprotected なフィールドだが、add メソッドがあるので $this->request->add() でフィールドを追加できる。

あとは after_of_equalbefore_or_equal で比較。

という予定だったが、$this->request->add() でフィールドを追加しても、追加する場所やタイミングの問題なのか、"start_datetime" は必須です。 とエラーになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function rules()
{
$this->request->add([
'start_datetime' => $this->start_date.' '.$this->start_time,
'end_datetime' => $this->end_date.' '.$this->end_time,
]);
return [
'start_date' => 'required|date_format:Y-m-d',
'start_time' => 'required|date_format:H:i',
'start_datetime' => [
'required',
'date_format:Y-m-d H:i',
'before_or_equal:end_datetime'
],
'end_date' => 'required|date_format:Y-m-d',
'end_time' => 'required|date_format:H:i',
'end_datetime' => [
'required',
'date_format:Y-m-d H:i',
'after_or_equal:start_datetime'
],
];
}

上手くいけば、これが一番キレイっぽかっただけに残念…

withValidator でゴニョゴニョ

withValidator$validator->after() とか使ってどうにかできないかな、と色々と調べたり試したりしたけど何も思いつかなかった。

まとめ

  • クロージャ内で日時データを組み立てて比較することで解決(暫定)
  • 他にも色々と試したけどダメだった
  • Laravel 歴が浅いので鵜呑みにしないこと

その他・メモ

他のやり方としては JS を使って、start_datetime, end_datetime フィールドを hidden で埋め込んでおくとかだと思う。

参考文献

関連記事