エス技研

WordPress、CakePHP、PHP、baserCMSなどの Web系システムを中心に情報を提供します!


CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説

      2020/08/24

CakePHP3で CSVアップロードする処理作成について

 
CakePHP3を利用して、モデルとは直接関係がないフォームを作成します。
そこから CSVファイルをアップロードし、テーブルの情報を新規登録する、更新する、と言う処理について解説をします。
 
 

この記事の参考サイト

 
なお、この記事は、下記の記事を元にして書いています。
https://qiita.com/groggy-chan/items/ba37220abeb23a2c9562
 
上記の記事では、不足している処理や、間違っている箇所がありそのままでは動作しませんでしたので、この記事ではその修正ポイントや、追加の解説をする目的で書き始めました。
 
 

記事で利用するサンプルテーブルの構造

 
また、この記事で利用するテーブルは、下記の構造となっています。
 

CREATE TABLE bookmarks2 (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(50),
    description TEXT,
    url TEXT,
    flag_code INT,
    flag INT,
    created DATETIME,
    modified DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

 
このテーブルは、下記の CookBookの「ブックマークチュートリアル」で利用したテーブルを流用したテーブルになります。
(記事の「bookmarks」テーブルを元に「flag_code」「flag」のカラムを追加した「bookmarks2」テーブルを作成しています。)
https://book.cakephp.org/3/ja/tutorials-and-examples/bookmarks/intro.html
 
また、「ブックマークチュートリアル」に関連した記事としては以下の記事もあります。
CakePHP3のfriendsofcake/searchでブックマークチュートリアルのタグ検索を実装
 
 

CakePHP3で CSVアップロードする処理の作成手順解説

 

プラグイン「josegonzalez/cakephp-upload」のインストール

 
まず始めに、今回紹介する CSVアップロード処理では、プラグイン「josegonzalez/cakephp-upload」を必要としますので、「josegonzalez/cakephp-upload」をインストールしていない環境ではまず始めにインストールをします。
 
 
「josegonzalez/cakephp-upload」の解説や詳細なインストール方法などは下記に記事を書いていますのでこちらを参考にしてください。
CakePHP3の画像、ファイルアップロードプラグインUpload Plugin 3.0の設置解説・その1
 
 

1.「josegonzalez/cakephp-upload」のインストール

 
まず始めに、プラグイン「josegonzalez/cakephp-upload」を Composerを使ってインストールします。
 
コンソールから下記のコマンドを実行します。
 

$ composer require josegonzalez/cakephp-upload

 
いくつか確認のメッセージが表示されますが、指定のバージョンなどがある場合を除き、デフォルトのままエンターキーのみを入力して進めてください。
 
 

2.ロードする設定を「config/bootstrap.php」に記述

 
インストールしたプラグインを CakePHP3のアプリ内で使用できるようにロードする設定を記述します。
 
記述方法は、2つの方法があり、どちらか好きな方を選択して実行してください。
 
 

2-1.コマンドを実行する

 
下記のコマンドを実行します。
 

$ bin/cake plugin load Josegonzalez/Upload

 
 

2-2.手作業で「config/bootstrap.php」に記述する方法

 
「/config/bootstrap.php」ファイルに下記の記述を追記します。
追加する場所はファイルの一番下などで OKです。
 

\App\Application::addPlugin('Josegonzalez/Upload');

 
 

CSVファイルをアップロードするプログラムの作成

 

CSVファイルをアップロード処理の作成

 
「/src/Form/HogeCsvForm.php」ファイルを作成し、下記の内容を記述します。
 

<?php
namespace App\Form;

use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;

class HogeCsvForm extends Form
{
    protected function _buildSchema(Schema $schema)
    {
        return $schema->addField('csv', ['type' => 'file']);
    }

    protected function _buildValidator(Validator $validator)
    {
        $validator->setProvider('upload', \Josegonzalez\Upload\Validation\DefaultValidation::class);
        $validator->add('csv', 'fileFileUpload', [
            'rule' => ['isFileUpload'],
            'message' => 'アップロードに失敗しました。',
            'provider' => 'upload'
        ])->add('csv', 'fileBelowMaxSize', [
            'rule' => ['isBelowMaxSize', 10240000],
            'message' => 'ファイルサイズが大きすぎます。',
            'provider' => 'upload'
        ])->add('csv', 'fileType', [
            'rule' => ['mimeType', ['text/plain']],
            'message' => 'ファイル形式が違います。'
        ]);
    }

    protected function _execute(array $data)
    {
        return true;
    }

}

 
この処理でプラグイン「josegonzalez/cakephp-upload」を必要とします。
また、この「josegonzalez/cakephp-upload」を使用して、アップロードする CSVファイルのサイズや形式などのバリデーションを行います。
 
ファイルのアップロード、バリデーションの方法などは下記にも記事を書いていますので参考にしてください。
CakePHP3の画像、ファイルアップロードプラグインUpload Plugin 3.0の設置解説・その1
CakePHP3のUpload Plugin 3.0をバリデーションなど実運用向けのカスタマイズ方法解説・その2
CakePHP3の更新画面でUpload Plugin 3.0を使う方法、viewで使う方法解説・その3
 
 
また、参考にした元記事のソースコードから下記の点を修正しています。
 
※17行目

// 修正前
        $validator->provider('upload', \Josegonzalez\Upload\Validation\DefaultValidation::class);
// 修正後             ↓非推奨のため置き換え
        $validator->setProvider('upload', \Josegonzalez\Upload\Validation\DefaultValidation::class);

 
 

Controllerの作成

 
「/src/Controller/HogesController.php」ファイルを作成し、下記の内容を記述します。
 

<?php
namespace App\Controller;

use App\Controller\AppController;
use App\Form\HogeCsvForm; // モデルなしフォームロード
use Cake\Filesystem\Folder;  // Folderユーティリティロード

class HogesController extends AppController
{
    public function initialize()
    {
        parent::initialize();
        $this->loadComponent("Csv"); // CSVコンポートロード
    }
   public function csv()
    {
        $hogesCsv = new HogeCsvForm();
        if ($this->request->is(["patch", "post", "put"])) {
            if ($hogesCsv ->execute($this->request->getData())) {
                // ファイルコピー
                $path = ROOT . DS . "uploadfiles" . DS . "tmp";
                $folder = new Folder();
                $folder->create($path);
                $file = $path . DS . sha1(uniqid(mt_rand(), true)) . ".csv";
                if (move_uploaded_file($this->request->getData("csv.tmp_name"), $file)) {
                    chmod($file, 0644);

                    // CSVデータ取込
                    $csvData = $this->Csv->getCsv($file,"utf8",true);

                    // データ整形
                    $data = [];
                    foreach ($csvData as $vol) {
                        $data[] = [
                            "user_id"   => 1,
                            "title"     => $vol[1],
                            "flag"      => $vol[0],
                            "flag_code" => $vol[0],
                        ];
                    }

                    // データ登録
                    $this->loadModel("Bookmarks2");
                    $entities = $this->Bookmarks2->newEntities($data);
                    $result = $this->Bookmarks2->saveMany($entities);

                    unlink($file);
                    $this->Flash->success("登録しました。");
                }else{
                    $this->Flash->error("登録に失敗しました。");
                }
            } else {
                $this->Flash->error("登録に失敗しました。");
            }
        }
        $this->set("hogesCsv", $hogesCsv);
    }
}

 
20~23行目に書いてありますが、アップロードした CSVファイルは「/uploadfiles/tmp/***」に一時的に保存する処理になっています。
処理が終われば削除されますが、処理が途中で止まった場合などはファイルが残り続けますので、実運用の際は定期的に削除する処理などが必要であろうと思います。
 
 
また、Controllerの処理としては、参考にした元記事のソースコードから下記の点を修正しています。
 
 
※2行目

// 修正前
namespace App\Controller\Manage;
// 修正後                ↑ココを削除
namespace App\Controller;

 
 
※15行目

// 先頭に全角スペースが入っている
    public function csv()

 
 
※17行目

// 修正前
        $hogesCsv = new HogesCsvForm();
// 修正後                  ↓「s」が不要
        $hogesCsv = new HogeCsvForm();

 
 
※29行目

// 修正前
        $csvData = $this->Csv->getCsv($file);
// 修正後                                    ↓文字コードを指定する
        $csvData = $this->Csv->getCsv($file,"utf8");

 
ここは間違いではありませんが、CSVファイルの文字コードは、デフォルトでは「sjis」として処理する仕様になっています(「CsvComponent.php」に記述されています)。
 
ですが、最近は「utf8」が多いとおもいますので、ここでそれを指定しておく必要があります。
文字コードが間違っているとエラーとなることが多々あります。
 
 
また、元の処理では CSVファイルの 1行目はタイトル行があるものとして自動的に読み込まれない仕様になっていました。
そのため、これを機動的に対処できるよう 3つ目のパラメータ(true/false)を追加しています。
 

// 修正前
        $csvData = $this->Csv->getCsv($file,"utf8");
// 修正後                                          ↓ヘッダー処理の追加
        $csvData = $this->Csv->getCsv($file,"utf8",true);

 
「true」のときは 1行目がタイトル行として取り込まない処理が実行されます。
「false」のときはタイトル行がないものとして 1行目から取込処理を行います。
 
「CsvComponent.php」の処理も合わせて確認してください。
 
 
※43~45行目

        $this->loadModel("Bookmarks2");
        $entities = $this->Bookmarks2->newEntities($data);
        $result = $this->Bookmarks2->saveMany($entities);

 
43行目で読み込んだモデル名と 44、45行目のモデル名が異なっていましたので、ここは一致させる必要があります。
この記事では「Bookmarks2」というテーブル(Moldel)を使っていますので、上記のようになります。
 
 
このサンプルプログラムでは、取得した CSVファイルの内容を新規レコードとして追加する処理(Insert)として記述しています。
そのため、「ID」は編集せず自動的に付与される想定です。
 
また、新規追加(Insert)のほかの記述方法や、既存レコードに対して一部の項目だけを変更する処理(Update)の方法などについては、下記に記事を書きましたのであわせて参考にしてください。
CakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介
 
 

Viewの作成

 
「src/Template/Hoges/csv.ctp」ファイルを作成し、下記の内容を記述します。
 

<div>
<?= $this->Form->create($hogesCsv, ['type' => 'file']) ?>
    <fieldset>
        <legend><?= __('CSV登録') ?></legend>
        <?php
        echo $this->Form->control('csv', ['type' => 'file']);
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
</div>

 
2行目の「$hogesCsv」は、Controllerで生成している変数で「/hoges/csv」と言う値が入っています。
ここを下記のように直接パスを記述する方法でも問題ありません。
 

<?= $this->Form->create("/hoges/csv", ['type' => 'file']) ?>

 
 

CSVデータを読み込むコンポーネント作成

 
「Controller\Component\CsvComponent.php」ファイルを作成し、下記の内容を記述します。
 

<?php
namespace App\Controller\Component;

use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use \SplFileObject;

/**
 * Csv component
 */
class CsvComponent extends Component
{

    /**
     * Default configuration.
     *
     * @var array
     */
    protected $_defaultConfig = [];

    public function getCsv($csvfile, $mode="sjis", $titleSkip = true)
    {
        // ファイル存在確認
        if (!file_exists($csvfile)) {
            return false;
        }

        // 文字コードを変換しながら読み込めるようにPHPフィルタを定義
        if ($mode === "sjis") {
            $filter = "php://filter/read=convert.iconv.cp932%2Futf-8/resource=".$csvfile;
        } elseif ($mode === "utf16") {
            $filter = "php://filter/read=convert.iconv.utf-16%2Futf-8/resource=".$csvfile;
        } elseif ($mode === "utf8") {
            $filter = $csvfile;
        }

        // SplFileObject()を使用してCSVロード
        $file = new SplFileObject($filter);
        if ($mode === 'utf16') {
            $file->setCsvControl("\t");
        }
        $file->setFlags(
            SplFileObject::READ_CSV |
            SplFileObject::SKIP_EMPTY |
            SplFileObject::READ_AHEAD
        );

        // 各行を処理
        $records = [];
        foreach ($file as $i => $row)
        {
            // 1行目はスキップ
            if ($titleSkip === true) {
                if ($i===0) {
                    continue;
                }
            }

            // 2行目以降はデータ行として取り込み
            $records[] = $row;
        }
        return $records;
    }
}

 
Controllerのところでも説明しましたが、参考にしたサイトの設定では、1行目はヘッダーがあるものとして必ず削除される処理になっていました。
 
そのため、21行目、および、53、57行目に処理を変更し、パラメータで 1行目を削除するかしないかを変更できるようにしています。
 
 
ちなみに、このコンポーネントの処理は、下記のサイトのソースを参考に作成したとのことです。
https://kantaro-cgi.com/blog/php/super-csv-loader.html
 
 
作業はここまでになります。
 
ここまでの作業を行うと、下記の URLで CSVファイルをアップロードするフォームが表示されると思います。
http://example.com/hoges/csv
 
 
記事の途中にも書きましたが、取得した CSVファイルを追加するのではなく、更新したい!と言う場合などは、下記に追加の解説記事を書いていますのであわせて参考にしてください。
CakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介
 
 

CakePHP3の関連記事

CakePHPのpostlinkで生成した削除リンクをクリックしても処理が実行されない対処法
CakePHP4のCSS、JavaScript、画像のブラウザへのキャッシュをコントロールする
CakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介
CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説
CakePHP3でPHP Simple HTML DOM Parserを使ってスクレイピングする方法
CakePHP3のInsert On Duplicate Key Update(upsert)構文を解説・バルク処理も
CakePHP3の1対多での連携を中間テーブルを使った多対多の連携に変更するときの手順
CakePHP3でデフォルトのソート条件を設定してユーザの選択肢たソート条件を有効にする方法
CakePHP3で Ajaxを使う方法の解説。3.6以降対応。Successとthenの両方を解説。
CakePHP3でパンくずの指定は HTMLヘルパーを使って指定する方法を解説
 
その他の「CakePHP3」に関する記事一覧
 
 

 - CakePHP 3.x 4.x 5.x

最後までお読みいただきましてありがとうございます。
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

下記の空欄を埋めてください。 * Time limit is exhausted. Please reload CAPTCHA.

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

※入力いただいたコメントは管理者の承認後に掲載されます。

  関連記事

CakePHPのFlashエラーは出るが入力項目ごとのメッセージが出ないエラーの原因
CakePHPのFlashエラーは出るが入力項目ごとのメッセージが出ないエラーの原因

Bakeして自動生成した入力フォーム処理を元に少し処理を追加したら、入力エラーがあってもエラーメッセージが表示されなくなった。原因はリダイレクトの処理にあった。

CakePHP3のプラグイン「CakeDC/Users」を日本語化・翻訳ファイルもダウンロード可
CakePHP3のプラグイン「CakeDC/Users」を日本語化・翻訳ファイルもダウンロード可

CakePHP3のユーザ管理、ログイン認証プラグインである「CakeDC/Users」のメッセージを日本語にする手順の解説とともに、日本語の翻訳ファイルを提供。ファイルを設置すれば日本語になる!

CakePHP3のfriendsofcake/searchでツリーカテゴリーの子階層も含めて検索する方法
CakePHP3のfriendsofcake/searchでツリーカテゴリーの子階層も含めて検索する方法

CakePHP3のツリービヘイビアを使ったツリーカテゴリーの子階層も含めての検索を検索プラグイン「friendsofcake/search」を使って実現する方法を解説しました。

MySQL+CakePHPのdate型、datetime型項目は「2999-12-31」までしか扱えない
MySQL+CakePHPのdate型、datetime型項目は「2999-12-31」までしか扱えない

CakePHPの日付、日時の型であるdate型、datetime型の最大値は2999-12-31となっている。MySQLは9999年までなので違いがあるので注意が必要だ。

CakePHP 2.3 連携先のテーブルの項目で条件抽出する場合

アソシエーション(連携)している先のテーブルの項目で条件抽出する際の考え方と注意点をサンプルソースを用いて説明しています。

CakePHP3でkeywords、DescriptionをHTMLヘルパーを使って設定する
CakePHP3でkeywords、DescriptionをHTMLヘルパーを使って設定する

CakePHP3のkeywordsとdescriptionを設定する方法の解説。CakePHP3にはmetaタグを編集するHTMLヘルパーが用意されているためそれを利用すればOK!ポイントはブロック化を有効にすること。

CakePHP4で定数の設定と呼び出し方法の解説(defineとConfigure)
CakePHP4で定数の設定と呼び出し方法の解説(defineとConfigure)

CakePHP4で定数を設定、使用する方法を解説。定数定義はdefineとConfigureを使用する方法を解説。また、bootstrap.phpに直接記述する方法と別のファイルにする方法を解説。

CakePHP 2.3 ID以外のカラムでアソシエーション(連携)をさせる場合

ID以外のカラムでアソシエーション(連携)させるための考え方とサンプルソースを用いての説明を行っています。

CakePHP 2.3でファイルをアップロード・その2 ファイル名を乱数で設定

CakePHPのアップロードするファイル名を乱数で変更しセキュリティを高める方法を解説。

CakePHP 2.3でファイルのアップロード処理を作る

CakePHPでプラグインを使わないファイルアップロード処理を解説します。簡単です。DBにファイルを格納する方法も。