Validatorを使いたくなった

日時のバリデーションが思うように動いていなかったので、カスタムルールを追加
This commit is contained in:
shibafu 2020-02-13 01:03:54 +09:00
parent 64065ce9e6
commit 67b697a600
8 changed files with 153 additions and 42 deletions

View File

@ -4,18 +4,24 @@ namespace App\Io;
use App\Ejaculation; use App\Ejaculation;
use App\Exceptions\CsvImportException; use App\Exceptions\CsvImportException;
use App\Rules\CsvDateTime;
use App\Tag; use App\Tag;
use App\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use League\Csv\Reader; use League\Csv\Reader;
class CheckinCsvImporter class CheckinCsvImporter
{ {
/** @var User Target user */
private $user;
/** @var string CSV filename */ /** @var string CSV filename */
private $filename; private $filename;
public function __construct(string $filename) public function __construct(User $user, string $filename)
{ {
$this->user = $user;
$this->filename = $filename; $this->filename = $filename;
} }
@ -45,48 +51,24 @@ class CheckinCsvImporter
} }
foreach ($csv->getRecords() as $offset => $record) { foreach ($csv->getRecords() as $offset => $record) {
$ejaculation = new Ejaculation(); $ejaculation = new Ejaculation(['user_id' => $this->user->id]);
$checkinAt = $record['日時'] ?? null; $validator = Validator::make($record, [
if (empty($checkinAt)) { '日時' => ['required', new CsvDateTime()],
$errors[] = "{$offset} 行 : 日時列は必須です。"; 'ノート' => 'nullable|string|max:500',
continue; 'オカズリンク' => 'nullable|url|max:2000',
} ]);
if (preg_match('/\A20\d{2}[-/](1[0-2]|0?\d)[-/](0?\d|[1-2]\d|3[01]) (0?\d|1\d|2[0-4]):(0?\d|[1-5]\d)(:(0?\d|[1-5]\d))?\z/', $checkinAt) !== 1) {
$errors[] = "{$offset} 行 : 日時列の書式が正しくありません。";
continue;
}
$checkinAt = str_replace('/', '-', $checkinAt);
try {
$ejaculation->ejaculated_date = Carbon::createFromFormat('!Y-m-d H:i+', $checkinAt);
} catch (\InvalidArgumentException $e) {
$errors[] = "{$offset} 行 : 日時列に不正な値が入力されています。";
continue;
}
if (!empty($record['ノート'])) { if ($validator->fails()) {
$note = $record['ノート']; foreach ($validator->errors()->all() as $message) {
$errors[] = "{$offset} 行 : {$message}";
if (mb_strlen($note) > 500) {
$errors[] = "{$offset} 行 : ート列は500文字以内にしてください。";
continue;
} }
continue;
$ejaculation->note = $note;
} }
if (!empty($record['オカズリンク'])) { $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']);
$link = $record['オカズリンク']; $ejaculation->note = $record['ノート'] ?? '';
$ejaculation->link = $record['オカズリンク'] ?? '';
if (mb_strlen($link) > 2000) {
$errors[] = "{$offset} 行 : オカズリンク列は500文字以内にしてください。";
continue;
}
// TODO: URL Validation
$ejaculation->link = $link;
}
$tagIds = []; $tagIds = [];
for ($i = 1; $i <= 32; $i++) { for ($i = 1; $i <= 32; $i++) {

65
app/Rules/CsvDateTime.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
/**
* CSVインポート機能の日時バリデーションルール
* @package App\Rules
*/
class CsvDateTime implements Rule
{
const VALID_FORMATS = [
'Y/m/d H:i:s',
'Y/n/j G:i:s',
'Y/m/d H:i',
'Y/n/j G:i',
];
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
// この辺の実装の元ネタは、LaravelのValidatesAttributes#validateDateFormat()
if (!is_string($value)) {
return false;
}
foreach (self::VALID_FORMATS as $format) {
$date = \DateTime::createFromFormat('!' . $format, $value);
if ($date && $date->format($format) === $value) {
return true;
}
}
return false;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return ':attribute の形式は "年/月/日 時:分" にしてください。';
}
}

View File

@ -4,34 +4,90 @@ namespace Tests\Unit\Io;
use App\Exceptions\CsvImportException; use App\Exceptions\CsvImportException;
use App\Io\CheckinCsvImporter; use App\Io\CheckinCsvImporter;
use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Tests\TestCase; use Tests\TestCase;
class CheckinCsvImporterTest extends TestCase class CheckinCsvImporterTest extends TestCase
{ {
use RefreshDatabase;
public function testIncompatibleCharsetEUCJP() public function testIncompatibleCharsetEUCJP()
{ {
$user = factory(User::class)->create();
$this->expectException(CsvImportException::class); $this->expectException(CsvImportException::class);
$this->expectExceptionMessage('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。'); $this->expectExceptionMessage('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。');
$importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv'); $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv');
$importer->execute(); $importer->execute();
} }
public function testMissingTimeUTF8() public function testMissingTimeUTF8()
{ {
$user = factory(User::class)->create();
$this->expectException(CsvImportException::class); $this->expectException(CsvImportException::class);
$this->expectExceptionMessage('日時列は必須です。'); $this->expectExceptionMessage('日時列は必須です。');
$importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.utf8.csv');
$importer->execute(); $importer->execute();
} }
public function testMissingTimeSJIS() public function testMissingTimeSJIS()
{ {
$user = factory(User::class)->create();
$this->expectException(CsvImportException::class); $this->expectException(CsvImportException::class);
$this->expectExceptionMessage('日時列は必須です。'); $this->expectExceptionMessage('日時列は必須です。');
$importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.sjis.csv');
$importer->execute(); $importer->execute();
} }
public function testDateNoSecondUTF8()
{
$user = factory(User::class)->create();
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nosecond.utf8.csv');
$importer->execute();
$ejaculation = $user->ejaculations()->first();
$this->assertSame(1, $user->ejaculations()->count());
$this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date);
}
public function testDateNoZeroNoSecondUTF8()
{
$user = factory(User::class)->create();
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv');
$importer->execute();
$ejaculation = $user->ejaculations()->first();
$this->assertSame(1, $user->ejaculations()->count());
$this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date);
}
public function testDateUTF8()
{
$user = factory(User::class)->create();
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date.utf8.csv');
$importer->execute();
$ejaculation = $user->ejaculations()->first();
$this->assertSame(1, $user->ejaculations()->count());
$this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date);
}
public function testDateNoZeroUTF8()
{
$user = factory(User::class)->create();
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero.utf8.csv');
$importer->execute();
$ejaculation = $user->ejaculations()->first();
$this->assertSame(1, $user->ejaculations()->count());
$this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date);
}
} }

View File

@ -0,0 +1,2 @@
日時
2020/01/23 06:01
1 日時
2 2020/01/23 06:01

View File

@ -0,0 +1,2 @@
日時
2020/1/23 6:01
1 日時
2 2020/1/23 6:01

View File

@ -0,0 +1,2 @@
日時
2020/1/23 6:01:02
1 日時
2 2020/1/23 6:01:02

2
tests/fixture/Csv/date.utf8.csv vendored Normal file
View File

@ -0,0 +1,2 @@
日時
2020/01/23 06:01:02
1 日時
2 2020/01/23 06:01:02

View File

@ -1,2 +1,2 @@
日時,ノート,オカズリンク 日時,ノート,オカズリンク
2019-01-01 00:01:02,テストテストあああああ,https://example.com/ 2019/1/01 0:01,テストテストあああああ,https://example.com/

1 日時 ノート オカズリンク
2 2019-01-01 00:01:02 2019/1/01 0:01 テストテストあああああ https://example.com/