😸

Laravelでバイト数のバリデートができたファイルだけ読み込む方法のメモ

2021/07/08に公開

はじめに

CSVで外部連携を行う場合、読込側でCSVデータが不正になっていないかを事前にチェックする方法があります。
例えば、実データを対になる制御ファイルを持っておき、制御ファイルに実データのバイト数を保持しておく方法です。
これにより、CSVデータが作成途中ではないことが確認できる場合があります。

こういった方法を取る場合に、

  • 制御ファイルが存在するのか
  • 制御ファイルに記載のバイト数と実データのバイト数が一致するのか

といった場合分けをしつつ読み込みする処理が必要になります。

Laravelでこれが必要になったので、手を動かしながら調べてみました。

やってみたこと

Laravelプロジェクトを作成

$ composer create-project laravel/laravel load-file-csv-with-ctl

動作確認用のファイルを用意

実データ(.csvで統一する)と制御ファイル(.ctlで統一する)をペアで作成し、それを以下のパターンで用意します。

  • 正常
  • .ctlのバイト数がおかしい
  • .ctlが無い

作成後の状態はこんな感じです

$ cd storage/app/public/
$ head *
==> output_20210703072428.csv <==
1,apple,100
2,banana,150
3,cherry,200
==> output_20210703072428.ctl <==
38
==> output_20210703073006.csv <==
3,apple,100
2,banana,150
1,cherry,200
==> output_20210703073006.ctl <==
10
==> output_20210703073017.csv <==
3,apple,100
1,banana,150
2,cherry,200

処理を記述

routes/web.php に処理をすべて書いてしまいます。

routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\File;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/list', function () {
    $path = storage_path( 'app/public' );

    $fileNames = array();

    $files = File::files( $path );
    foreach( $files as $file ){
        $fileNames[] = pathinfo( $file->getFilename(), PATHINFO_FILENAME );
    }

    $set = array_unique( $fileNames );
    foreach( $set as $elm ){
        $ctlContent = [];
        $csvContent = [];
        $target = $path . '/' . $elm;

        // See: https://www.php.net/manual/ja/function.file-exists.php
        if ( file_exists( $target . '.ctl' ) ){
            $ctlContent = file_get_contents( $target . '.ctl' ) ?? '';
        } else {
            echo( $elm . '.ctl' . 'は存在しませんでした。');
        }

        if ( file_exists( $target . '.csv' ) ){
            $csvContent = new \SplFileObject( $target . '.csv' );
            $csvContent->setFlags(
                    \SplFileObject::READ_CSV |        // CSV 列として行を読み込む
                    \SplFileObject::READ_AHEAD |      // 先読み/巻き戻しで読み出す
                    \SplFileObject::SKIP_EMPTY |      // 空行は読み飛ばす
                    \SplFileObject::DROP_NEW_LINE     // 行末の改行を読み飛ばす
            );            
        } else {
            echo( $elm . '.csv' . 'は存在しませんでした。' . "\n");
        }

        if ( intval( $ctlContent ) !== $csvContent->getSize() ) {
            echo( $elm . '.csv' . 'のサイズが.ctlファイルの内容と一致しません' . "\n");
        } else {
            echo( $csvContent . "\n");
        }
    }
});

補足

        // See: https://www.php.net/manual/ja/function.file-exists.php
       if ( file_exists( $target . '.ctl' ) ){
           $ctlContent = file_get_contents( $target . '.ctl' ) ?? '';
       } else {
           echo( $elm . '.ctl' . 'は存在しませんでした。');
       }

file_existsを使って、 file_exists( <フルパス> ) でファイルの存在確認をすることができます。

        if ( intval( $ctlContent ) !== $csvContent->getSize() ) {
           echo( $elm . '.csv' . 'のサイズが.ctlファイルの内容と一致しません' . "\n");
       } else {

.ctlから読み込んだ内容をintvalで数値に変換しないと、ここでは文字で評価されてしまいます。なので変換しています。

動作確認

  • ローカルサーバを起動します
$ php artisan serve
  • 正しい内容だけ取得できることを確認
$ curl http://localhost:8000/list
1,apple,100
output_20210703073006.csvのサイズが.ctlファイルの内容と一致しません
output_20210703073017.ctlは存在しませんでした。output_20210703073017.csvのサイズが.ctlファイルの内容と一致しません

1, apple, 100 が出力され、他は失敗することを確認できました。

Discussion