エス技研

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


CakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介

      2020/08/24

CakePHP3でレコードを保存(追加(Insert)、更新(Update))する方法を紹介

 

レコードを追加、更新する方法の前提

 
CakePHP3でレコードを追加する方法、更新する方法について解説します。
 
レコードを追加(Insert)する方法も、更新(Update)する方法も 1つではありませんので、複数の方法を紹介しつつ、活用方法や特徴を解説します。
 
 
なお、この記事は、下記の記事である「CSVファイルを読み込んでテーブルを更新する」と言う処理の実験をベースに記事を書いています。
CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説

そのため、一般的な「画面から入力された情報を保存する」という処理とは「foreach」文などが若干異なります。
その辺りについては、解説を加えてはいますが。
 
 
今回のプログラムで更新対象としているテーブルは以下の内容です。
 

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)
);

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

CSVファイルを更新する Controllerの処理

 
CSVアップロードの処理全体は「CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説」を読んでいただくとして、そのうちの 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);
    }
}

 
 
この Controllerの処理の中で、
32~40行目で保存するレコードを整形し、
43~45行目で整形したレコードをテーブルに保存しています。
 
まず始めに、CSVとして取得したレコード全体で配列を作成し、「saveMany()」メソッドで一括して保存する処理となっています。
 
 
この記事では、このレコードを整形し、テーブルに保存する処理のバリエーションを解説します。
 
 
ちなみに、この処理では CSVファイルを一括して処理するため速い処理が期待できます。
 
ですが、カラム数にもよりますが、数百件程度であれば全く問題がなくても、万単位のレコードがある場合は、メモリオーバーや処理時間オーバーなどの不具合が発生する場合も考えられるため、CSVの件数が多くなる可能性がある場合は、件数をカウントして処理を分岐するなどの対応が必要だろうと思います。
 
 

テーブルの接続方法のバリエーション

 
まず始めに、テーブルに接続する方法のバリエーションです。
 
先の例では下記の記述で「Bookmarks2」のモデルをロードしています。
 

$this->loadModel("Bookmarks2");

 
ロードしたモデルを下記のように使用します。
 

$this->Bookmarks2->newEntities($data);

 
 
モデルを呼び出すほかの方法としては、下記の記述方法もあります。
まず、Controllerの use句を書いている箇所に下記を追記します。
 

use Cake\ORM\TableRegistry;

 
また、先の「$this->loadModel("Bookmarks2");」の記述を、下記の記述に変更します。
 

$this->Bookmarks2 = TableRegistry::getTableLocator()->get('Bookmarks2');

 
 

「save()」メソッドで 1件ずつ追加(Insert処理・その2)

 
最初のサンプルでは「saveMany()」メソッドを使用して、一括保存する処理でした。
 
その2では「save()」メソッドを使用して 1件ずつ保存する処理を紹介します。
 

      // レコードを新規登録するとき・その2
      $this->loadModel("Bookmarks2");

      foreach ($csvData as $vol) {
          $bookmarks2 = $this->Bookmarks2->newEntity();
          $bookmarks2->user_id   = 1;
          $bookmarks2->title     = $vol[1];
          $bookmarks2->flag      = $vol[0];
          $bookmarks2->flag_code = $vol[0];

          $this->Bookmarks2->save($bookmarks2);

//        if($this->Bookmarks2->save($bookmarks2)){
//            $id = $bookmarks2->id;
//        } else {
//            エラー処理
//        }
      }

 
この処理が、Bakeを実行して自動的に作成される処理に一番近い処理になります。
foreach文で配列から 1件ずつレコードを取り出して、saveメソッドで保存しています。
 
また、コメントにしていますが、11行目を 13~17行目と置き換えることで、正常に保存できた場合は保存したレコードの IDを取得することや、エラーが発生した場合はそのエラーの対処することなど、1件ずつ細かな管理をすることを容易にすることができます。
 
CSVの件数が少ない場合はこのような処理方法も使用できるでしょう。
 
 
基本的な処理が CSVファイルを取得することを想定していますので、配列「$csvData」を foreach文で処理しています。

ですが、入力フォームから取得した値 1件を受け取って保存する場合は以下の記述になります。
 

      $this->loadModel("Bookmarks2");
      $data = $this->request->getData();
      $bookmarks2 = $this->Bookmarks2->newEntity($data);
      $this->Bookmarks2->save($bookmarks2);

 
より Bakeされた際に作成される処理に近づけるならば下記の記述になります。
 

      $this->loadModel("Bookmarks2");
      $bookmarks2 = $this->Bookmarks2->newEntity();
      $data = $this->request->getData()
      $bookmarks2 = $this->Bookmarks2->patchEntity($bookmarks2,$data);
      $this->Bookmarks2->save($bookmarks2)

 
 

「save()」メソッドで 1件ずつ更新(Update処理・その1)

 
ここからは、レコードを更新する(Update)処理について解説します。
 
 

「get()」メソッドを使い IDからレコードを取得(その1-1)

 
CSVから取得する項目に IDが含まれ、IDを利用してレコードを更新する場合は下記のように記述することが出来ます。
 

    // レコードを更新するとき・その1-1
    $this->loadModel("Bookmarks2");

    foreach ($csvData as $vol) {
        if( $this->Bookmarks2->exists(["id"=>$vol[0]]) ){
            $bookmarks2 = $this->Bookmarks2->get($vol[0]);
            $bookmarks2->flag  = 4;
            $bookmarks2->title = $vol[1];
            $result = $this->Bookmarks2->save($bookmarks2);
        }
    }

 
getメソッドは、レコードの IDを指定してレコードを取得する処理ですが、指定した IDがない場合はエラーとなります。
 
テーブルに保存されていない IDが入力される可能性がある場合は、取得できない場合に例外処理(エラー処理)とする方法もありますが、ここではまず始めに existsメソッドでレコードの存在チェックをする方法を採用しています。
 
 

「find()」メソッドを使い条件に合致するレコードを取得する(その1-2)

 
IDがない可能性を考慮する場合や、取得する CSVに IDが含まれない、もしくは、プライマリーキーとなる ID以外をキーとしてレコードを更新したい場合は下記のように findメソッドを使用する方法が有効です。
 

    // レコードを更新するとき・その1-2
    $this->loadModel("Bookmarks2");

    foreach ($csvData as $vol) {
        $bookmarks2 = $this->Bookmarks2->find()->where(["flag_code"=>$vol[0]])->first();

        if($bookmarks2){
            $bookmarks2->flag = 1;
            $bookmarks2->title = $vol[1];
            $result = $this->Bookmarks2->save($bookmarks2);
        }
    }

 
 

「find()」メソッドを使い条件に合致するレコードを取得(その1-3)

 
基本的には「その1-2」と変わりませんが、5行目と 7行目を 1行にまとめて下記のように記述することも出来ます。
 

    // レコードを更新するとき・その1-3
    // 「その1-2」の処理を作るくらいならこちらのほうがマシ
    $this->loadModel("Bookmarks2");

    foreach ($csvData as $vol) {
        if($bookmarks2 = $this->Bookmarks2->find()->where(["flag_code"=>$vol[0]])->first()){
            $bookmarks2->flag = 1;
            $bookmarks2->title = $vol[1];
            $result = $this->Bookmarks2->save($bookmarks2);
        }
    }

 
 

「save()」メソッドで 1件ずつ更新(Update処理・その2)

 
あまり代わり映えはしませんが、保存するレコードを配列で整形する方法もあります。
 

    // レコードを更新するとき・その2
    $this->loadModel("Bookmarks2");

    foreach ($csvData as $vol) {
        $bookmarks2 = $this->Bookmarks2->find()->where(["flag_code"=>$vol[0]])->first();

        if($bookmarks2){
            $data = [
                "flag"  => 4,
                "title" => $vol[1],
            ];

            $entity = $this->Bookmarks2->patchEntity($bookmarks2, $data); 
            $this->Bookmarks2->save($entity);
        }
    }

 
 

「Query()」メソッドで複数件のレコードを更新(Update処理・その3)

 
続いて、複数のレコードをまとめて更新する方法です。
 
更新のその1、その2は、CSVの 1行でテーブルの 1レコードを更新する更新方法です。
 
「その1-1」は IDをキーに更新を行いますので 1レコードしかありませんが、「その1-2」「その1-3」「その2」に関しては、「["flag_code"=>$vol[0]]」の条件に該当するレコードが複数件ある場合も存在します。
処理上は、firstメソッドを用いていますので、そのうちの最初の 1件のみ更新する処理になっていますが。
 
 
このように、条件に該当するレコードが複数ある場合は、複数件まとめて更新する方法が以下の処理になります。
 
Queryメソッドを使って Update文を生成する方法です。
 

    // レコードを更新するとき・その3
    $this->loadModel("Bookmarks2");

    foreach ($csvData as $vol) {
        $data = [
            "flag"  => 4,
            "title" => $vol[1],
        ];

        $query = $this->Bookmarks2->query();
        $query->update()
              ->set($data)
              ->where(["flag_code" => $vol[0]])
              ->execute();
    }

 
 

「UpdateAll()」メソッドで複数件のレコードを更新(Update処理・その4)

 
前項と同じく、条件に該当するレコードが複数件ある場合は、下記のように UpdateAllメソッドを使う方法もあります。
 

    // レコードを更新するとき・その4
    $this->loadModel("Bookmarks2");

    foreach ($csvData as $vol) {
        $data = [
            "flag"  => 4,
            "title" => $vol[1],
        ];
        $condition = [ "flag_code" => $vol[0] ];

        $this->Bookmarks2->updateAll($data,$condition);
    }

 
 

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.

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

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

  関連記事

CakePHP3のHtmlHelperのLink設定のまとめ。mailto、URL、Root/Homeのリンクなども
CakePHP3のHtmlHelperのLink設定のまとめ。mailto、URL、Root/Homeのリンクなども

CakePHP3でHtmlHelperを使ってリンクの設定をする方法のまとめ。基本形からURLを指定、class、id、targetを指定、mailtoのリンク、画像をアンカーに、JavaScriptのダイアログなどの解説。

CakePHP3の検索プラグイン「friendsofcake/search」の様々な検索の仕方の実装方法
CakePHP3の検索プラグイン「friendsofcake/search」の様々な検索の仕方の実装方法

CakePHP3で検索をするプラグイン「friendsofcake/search」の検索条件のカスタマイズ方法の解説。検索項目を増やしたり、以上、以下での検索や、チェックボックスによる検索の方法などを解説。

CakePHP4、5のnewEmptyEntityで作成したオブジェクトは空なのか、確認してみた
CakePHP4、5のnewEmptyEntityで作成したオブジェクトは空なのか、確認してみた

CakePHPでレコードを新規登録をする際にnewEmptyEntity()を使って空のEntityを作成するが、これのempty、is_nullなどのでの判定結果がtrueかfalseか確認してみた。

CakePHP3で環境変数を設定して本番環境と開発環境を分けて処理をする場合
CakePHP3で環境変数を設定して本番環境と開発環境を分けて処理をする場合

CakePHP3で開発環境と本番環境とで違う設定ファイルを読み込ませて環境ごとに定数を切り替える方法を解説。Apacheのhttpd.confに環境変数を設定し、それを読み込み判別する。

CakePHP4で現在処理しているコントローラー名、アクション名を取得する方法
CakePHP4で現在処理しているコントローラー名、アクション名を取得する方法

CakePHP3で現在処理しているコントローラー名、アクション名を取得する方法を解説。複数の方法があるが、getParam()メソッドを使う方法が汎用性があって便利かも。

Windows環境の XAMPPを利用して CakePHPの開発する際の注意点
Windows環境の XAMPPを利用して CakePHPの開発する際の注意点

WindowsベースにXAMPPで環境を構築しCakePHP4を利用したWebシステムを構築する際は、大文字と小文字の違いを意識する必要がある。LinuxベースのWebサーバに移動させたときに不具合で動作しないこともある。

CakePHP 2.3 Search Pluginで検索処理 その7queryを使って 日付の範囲検索

CakePHPの検索プラグイン Search Pluginの検索処理の中で queryを使って日付の範囲検索の方法です。

CakePHP3チュートリアルで日付と時刻のDateTimeでエラーが出たときの対処方法
CakePHP3チュートリアルで日付と時刻のDateTimeでエラーが出たときの対処方法

CakePHP3のブックマークチュートリアルには記載ミスもあり、そのまま動かない個所もある。CakePHP3では namespaceを使うようになったので、classを呼び出すときに¥を追加する必要が!

CakePHP4、5のユーザ認証でID、PASS以外の削除フラグなども条件加える方法
CakePHP4、5のユーザ認証でID、PASS以外の削除フラグなども条件加える方法

CakePHP4系、5系ではAuthenticationを使用してログイン認証を行う。その認証でID、PASS以外の削除フラグなどの条件を加えたいときの対応方法について解説。

CakePHP4のFrozenDateで1ヵ月前、先月、今月1日、来月末の日付などを算出する方法
CakePHP4のFrozenDateで1ヵ月前、先月、今月1日、来月末の日付などを算出する方法

CakePHPには「FrozenDate」の日付を扱う関数が用意されている。これを利用して、1ヶ月後、月末日、月初日、5日後などを指定して日付を取得できる。それを解説。