Validatorを使いたくなった
日時のバリデーションが思うように動いていなかったので、カスタムルールを追加
This commit is contained in:
		@@ -4,18 +4,24 @@ namespace App\Io;
 | 
			
		||||
 | 
			
		||||
use App\Ejaculation;
 | 
			
		||||
use App\Exceptions\CsvImportException;
 | 
			
		||||
use App\Rules\CsvDateTime;
 | 
			
		||||
use App\Tag;
 | 
			
		||||
use App\User;
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use Illuminate\Support\Facades\DB;
 | 
			
		||||
use Illuminate\Support\Facades\Validator;
 | 
			
		||||
use League\Csv\Reader;
 | 
			
		||||
 | 
			
		||||
class CheckinCsvImporter
 | 
			
		||||
{
 | 
			
		||||
    /** @var User Target user */
 | 
			
		||||
    private $user;
 | 
			
		||||
    /** @var string CSV filename */
 | 
			
		||||
    private $filename;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $filename)
 | 
			
		||||
    public function __construct(User $user, string $filename)
 | 
			
		||||
    {
 | 
			
		||||
        $this->user = $user;
 | 
			
		||||
        $this->filename = $filename;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -45,48 +51,24 @@ class CheckinCsvImporter
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach ($csv->getRecords() as $offset => $record) {
 | 
			
		||||
                $ejaculation = new Ejaculation();
 | 
			
		||||
                $ejaculation = new Ejaculation(['user_id' => $this->user->id]);
 | 
			
		||||
 | 
			
		||||
                $checkinAt = $record['日時'] ?? null;
 | 
			
		||||
                if (empty($checkinAt)) {
 | 
			
		||||
                    $errors[] = "{$offset} 行 : 日時列は必須です。";
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                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;
 | 
			
		||||
                }
 | 
			
		||||
                $validator = Validator::make($record, [
 | 
			
		||||
                    '日時' => ['required', new CsvDateTime()],
 | 
			
		||||
                    'ノート' => 'nullable|string|max:500',
 | 
			
		||||
                    'オカズリンク' => 'nullable|url|max:2000',
 | 
			
		||||
                ]);
 | 
			
		||||
 | 
			
		||||
                if (!empty($record['ノート'])) {
 | 
			
		||||
                    $note = $record['ノート'];
 | 
			
		||||
 | 
			
		||||
                    if (mb_strlen($note) > 500) {
 | 
			
		||||
                        $errors[] = "{$offset} 行 : ノート列は500文字以内にしてください。";
 | 
			
		||||
                        continue;
 | 
			
		||||
                if ($validator->fails()) {
 | 
			
		||||
                    foreach ($validator->errors()->all() as $message) {
 | 
			
		||||
                        $errors[] = "{$offset} 行 : {$message}";
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    $ejaculation->note = $note;
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!empty($record['オカズリンク'])) {
 | 
			
		||||
                    $link = $record['オカズリンク'];
 | 
			
		||||
 | 
			
		||||
                    if (mb_strlen($link) > 2000) {
 | 
			
		||||
                        $errors[] = "{$offset} 行 : オカズリンク列は500文字以内にしてください。";
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // TODO: URL Validation
 | 
			
		||||
 | 
			
		||||
                    $ejaculation->link = $link;
 | 
			
		||||
                }
 | 
			
		||||
                $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']);
 | 
			
		||||
                $ejaculation->note = $record['ノート'] ?? '';
 | 
			
		||||
                $ejaculation->link = $record['オカズリンク'] ?? '';
 | 
			
		||||
 | 
			
		||||
                $tagIds = [];
 | 
			
		||||
                for ($i = 1; $i <= 32; $i++) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										65
									
								
								app/Rules/CsvDateTime.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								app/Rules/CsvDateTime.php
									
									
									
									
									
										Normal 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 の形式は "年/月/日 時:分" にしてください。';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,34 +4,90 @@ namespace Tests\Unit\Io;
 | 
			
		||||
 | 
			
		||||
use App\Exceptions\CsvImportException;
 | 
			
		||||
use App\Io\CheckinCsvImporter;
 | 
			
		||||
use App\User;
 | 
			
		||||
use Illuminate\Foundation\Testing\RefreshDatabase;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
 | 
			
		||||
class CheckinCsvImporterTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use RefreshDatabase;
 | 
			
		||||
 | 
			
		||||
    public function testIncompatibleCharsetEUCJP()
 | 
			
		||||
    {
 | 
			
		||||
        $user = factory(User::class)->create();
 | 
			
		||||
        $this->expectException(CsvImportException::class);
 | 
			
		||||
        $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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testMissingTimeUTF8()
 | 
			
		||||
    {
 | 
			
		||||
        $user = factory(User::class)->create();
 | 
			
		||||
        $this->expectException(CsvImportException::class);
 | 
			
		||||
        $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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testMissingTimeSJIS()
 | 
			
		||||
    {
 | 
			
		||||
        $user = factory(User::class)->create();
 | 
			
		||||
        $this->expectException(CsvImportException::class);
 | 
			
		||||
        $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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								tests/fixture/Csv/date-nosecond.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/fixture/Csv/date-nosecond.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
日時
 | 
			
		||||
2020/01/23 06:01
 | 
			
		||||
		
		
			
  | 
							
								
								
									
										2
									
								
								tests/fixture/Csv/date-nozero-nosecond.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/fixture/Csv/date-nozero-nosecond.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
日時
 | 
			
		||||
2020/1/23 6:01
 | 
			
		||||
		
		
			
  | 
							
								
								
									
										2
									
								
								tests/fixture/Csv/date-nozero.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/fixture/Csv/date-nozero.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
日時
 | 
			
		||||
2020/1/23 6:01:02
 | 
			
		||||
		
		
			
  | 
							
								
								
									
										2
									
								
								tests/fixture/Csv/date.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/fixture/Csv/date.utf8.csv
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
日時
 | 
			
		||||
2020/01/23 06:01:02
 | 
			
		||||
		
		
			
  | 
@@ -1,2 +1,2 @@
 | 
			
		||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<EFBFBD>Ρ<EFBFBD><EFBFBD><EFBFBD>,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
 | 
			
		||||
2019-01-01 00:01:02,<EFBFBD>ƥ<EFBFBD><EFBFBD>ȥƥ<EFBFBD><EFBFBD>Ȥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,https://example.com/
 | 
			
		||||
2019/1/01 0:01,<EFBFBD>ƥ<EFBFBD><EFBFBD>ȥƥ<EFBFBD><EFBFBD>Ȥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,https://example.com/
 | 
			
		||||
 
 | 
			
		||||
		
		
			
  | 
		Reference in New Issue
	
	Block a user