CakePHP3のアソシエーションでJOINのタイプのLEFT、INNERを切り替えながら使う方法
2023/12/28
Tableファイルで INNERが指定してあるアソシエーションのデータを LEFTでの取得を Controller側で指定する方法
この記事は、CakePHP3 向けに書いた記事ですが、CakePHP4でも同じ記述方法をそのまま利用することができます。
想定している環境について
まず、想定しているテーブルは以下のものです。
ユーザ管理、ログイン認証のプラグインである「CakeDC/Users」を使う際に作成するテーブルが「Users」です。
また、ユーザ情報として、ユーザ名や住所、電話番号と言った情報を追加で登録、管理する想定ですが、プラグインで生成される「Users」テーブルには手を付けず、1対1でアソシエーションする「UserDetails」テーブルを作成する仕様を想定しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
CREATE TABLE `users` ( `id` char(36) NOT NULL, `username` varchar(255) NOT NULL, `email` varchar(255) DEFAULT NULL, `password` varchar(255) NOT NULL, `first_name` varchar(50) DEFAULT NULL, `last_name` varchar(50) DEFAULT NULL, `token` varchar(255) DEFAULT NULL, `token_expires` datetime DEFAULT NULL, `api_token` varchar(255) DEFAULT NULL, `activation_date` datetime DEFAULT NULL, `secret` varchar(32) DEFAULT NULL, `secret_verified` tinyint(1) DEFAULT NULL, `tos_date` datetime DEFAULT NULL, `active` tinyint(1) NOT NULL DEFAULT '0', `is_superuser` tinyint(1) NOT NULL DEFAULT '0', `role` varchar(255) DEFAULT 'user', `created` datetime NOT NULL, `modified` datetime NOT NULL, `additional_data` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 CREATE TABLE `user_details` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` char(36) NOT NULL UNIQUE, `name` varchar(80) NOT NULL, `zip_code` char(8) DEFAULT NULL, `tel` varchar(13) DEFAULT NULL, `address_pref` char(2) DEFAULT NULL, `address_city` char(4) DEFAULT NULL, `address_detail` varchar(80) DEFAULT NULL, `comment` text DEFAULT NULL, `publish_flag` tinyint(4) NOT NULL DEFAULT '2', `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
また、この記事の説明は、テーブルのアソシエーションに関連する説明になっています。
そのため、実際に「CakeDC/Users」を利用して動作検証をしようとする場合は、下記の「CakeDC/Users」に関連する記事を確認してください。
CakePHP3のユーザ管理・ログイン認証プラグインCakeDC/Usersのインストール解説・3.6以降対応
CakePHP3のCakeDC/Usersのログイン後のリダイレクトとユーザ権限管理の設定解説
CakePHP3のCakeDC/Usersのバリデーションのカスタマイズ方法解説
特に、デフォルトだと、UsersTableファイルは「/vendor/cakedc/users/src/Model/Table/UsersTable.php」にあります。
ですが、コアファイルは触らずに、「/src/Model/Table/UsersTabble.php」にファイルを設置し、コアファイルの処理をオーバーライドさせています。
UsersTabble.phpにアソシエーションの設定をする
UserDetailsテーブルを Bakeすると、「/src/Model/Table/UserDetailsTabble.php」の「initialize」には下記のようになっていると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public function initialize(array $config) { parent::initialize($config); $this->setTable('users_details'); $this->setDisplayField('id'); $this->setPrimaryKey('id'); $this->addBehavior('Timestamp'); $this->belongsTo('Users', [ 'foreignKey' => 'user_id', 'joinType' => 'INNER' ]); } |
これだけだと、UserDetails側からアソシエーションの設定を利用して Usersのデータを取得することはできますが、Users側からは UserDetailsのデータを取得することはできません。
そのため、Usersの tableファイル「/src/Model/Table/UsersTabble.php」に下記を追記します。
1 2 3 4 5 6 7 8 9 |
public function initialize(array $config) { parent::initialize($config); $this->hasOne('UsersDetails', [ 'foreignKey' => 'user_id', 'joinType' => 'INNER' ]); } |
「joinType」に「INNER」を指定していますが、指定がない場合はデフォルトとして「LEFT」になります。
「LEFT」と「INNER」の違いは、Usersに連結させる UserDetailsのテーブルがないときの対応の違いです。
「LEFT」の場合は、UserDetailsの部分を NULLが入っているものとして取得します。
「INNER」の場合は、UserDetailsにレコードがある Usesのみ取得します。
Usersから contain句を利用してレコードを取得
上記のように Tableファイルの設定をしている場合、Usersテーブルから値を取得する場合は、コントローラーには下記のように記述します。
1 |
$usersList = $this->Users->find()->contain(["UserDetails"])->all(); |
Tableファイルには INNERでアソシエーションしていますので、UserDetailsにレコードがない場合は、Usersのレコードも取得されません。
ユーザ情報を一覧表示するようなシステムの場合、必要となるのは、UserDetailsテーブルに情報が登録されているレコードですので、INNERでアソシエーションされている方がいいでしょう。
ですが、管理画面でユーザ情報を管理する場合は、UsersDetailsにレコードがない Usersのレコードも取得する必要があります。
そのため、管理画面では INNERでアソシエーションするのではなく、LEFTでアソシエーションした情報を取得する必要があります。
どうやって INNERと LEFTを切り替えればいいのでしょうか?
前振りが長くなりましたが、ここが今回の記事のポイントです。
Controllerで「leftJoinWith()」「innerJoinWith()」を指定する
結論は、下記のように「leftJoinWith()」句を使います。
1 |
$usersLeftList = $this->Users->find()->leftJoinWith("UserDetails")->contain(["UserDetails"])->all(); |
上記の通り「leftJoinWith()」句を使うことで、UsersTableでは「INNER」が指定してあっても「LEFT」でデータを取得してくることができるようになります。
ここで紹介した方法と逆に、UsersTableの方で「LEFT」を指定し、レコードを取得する際に「innerJoinWith()」を使う、という方法もあります。
1 |
$usersList = $this->Users->find()->leftJoinWith("UserDetails")->where(["user_id is not null"])->all(); |
また、そもそも、UsersTableの方で「LEFT」を指定し、「where()」句で「user_id is not null」で「UserDetails」のレコードがないものを省く方法もあります。
「leftJoinWith()」「innerJoinWith()」や「is not null」でレコードを取得する場合は、いずれの場合においても、UsersTabble.php に hasOneなどのアソシエーションの設定がないとエラーになります。
Tableファイルではなく Controllerファイルでアソシエーションの設定を行う方法
基本的には Tableファイルでアソシエーションの設定を行うものですが、Tableファイルにアソシエーションの設定がなくても、Controllerファイルでテーブルの連携の設定を行うこともできます。
1 2 3 4 |
$usersLeftJoinList = $this->Users->find() ->join(["table"=>"user_details", "type"=>"LEFT", "conditions"=>"users.id = users_details.user_id"])->all(); |
ここでの注意点は、テーブル名は Modelで管理しているテーブル名(UserDetails)ではなく、実テーブル名である「user_details」を使う点です。
詳細は、下記の Cookbookを参照してください。
CakePHP3 Cookbook クリエービルダー
https://book.cakephp.org/3.0/ja/orm/query-builder.html#join
CakePHP3では Tableでアソシエーションを指定するべきもの?
ちなみに、この記述方法は、CakePHP3では望ましい書き方ではないのではないか、という感じがします。
CakePHP2の頃までは「Recursive」「ContainableBehavior」を使ってアソシエーションのテーブルのデータをコントロールしていたようですが、CakePHP3では「Recursive」「ContainableBehavior」は削除されています。
https://book.cakephp.org/3.0/ja/appendices/orm-migration.html#recursive-containablebehavior
CakePHP3では、アソシエーションしたどのテーブルのデータを取得するかは、「contain」で指定するようになりました、というお話ですが、Tableファイルにアソシエーションを設定し、contain句を使って必要なデータを指定すれば、余計なデータを取得して処理が重くなるということも防げるのではないか、と思います。
「join()」でアソシエーションした先のテーブルの情報を取得する方法 2023.12.28追記
また、「join()」でアソシエーションした先のテーブルの情報を取得する場合は、下記のように記述をします。
1 2 3 4 5 6 7 8 9 10 |
$usersLeftJoinList = $this->Users->find() ->select($this->Users) ->select(["users_details.id", "users_details.name", "users_details.zip_code", "users_details.tel"]) ->join(["table"=>"user_details", "type"=>"LEFT", "conditions"=>"users.id = users_details.user_id"]) ->all(); |
「Users」のテーブルのカラムは「->select($this->Users)
」の 1行でまとめて取得することが可能なのですが、アソシエーション先のカラムは「->select(["users_details.id",....
」のように取得するカラムを一つずつ指定する必要があるようです。
※いろいろ試してみましたが、「->select($this->Users)
」のようにテーブルのカラムをまとめて取得する方法を見つけることができませんでした。分かる方、教えてください!
ただ、ここまでして controller内でアソシエーションの設定をする必要性があるのか、疑問に感じます。
素直に Model内で設定する方が簡単だと思います。
CakePHP4 Cookbook クリエービルダー
https://book.cakephp.org/4/ja/orm/query-builder.html#join
CakePHP3の関連記事
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にOGPをfetch、asignを利用してテンプレートごとに指定する方法を解説
その他の「CakePHP3」に関する記事一覧
GoogleAdwords
GoogleAdwords
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
-
CakePHP3にデイトピッカー jQuery UI DatePickerを実装する手順の解説
CakePHP3にjQuery UIのDatePickerを実装する手順を説明。併せて、デイトピッカーを設置に関連するCakePHP3の解説と、テーマを変更したり、表記を変更するカスタマイズする方法なども紹介。
-
-
CakePHP 2.3 Search Pluginで検索処理 その5入力項目に複数項目入力した場合の AND検索、OR検索
CakePHPの検索プラグイン Search Pluginの検索処理の中で複数項目を入力した場合の AND検索、OR検索についての解説です。
-
-
CakePHP 2.3 テーブルの項目を演算した結果を条件として抽出する方法
アソシエーション(連携)している先のテーブルの項目で演算をする場合の考え方と注意点をサンプルソースを用いて説明しています。分かってしまえば簡単です。
-
-
CakePHP3で保存前にバリデーション結果を取得する2つの方法
CakePHP3でデータベースに値を保存する前にバリデーションを行い、その結果によって処理を振り分ける方法について解説。「$topic->errors()」と「$topic->hasErrors()」の2つの方法がある。
-
-
CakePHP4の数値項目は「like %10%」の部分一致検索(find select)はできない
CakePHP4でテーブルの数値項目に対してlike句を使用した部分一致検索を実行するとエラーが発生する。クリエービルダーの不具合だと思われ対処方法が分からない。
-
-
CakePHP3のアソシエーション機能を使い関連レコードをまとめて削除
CakePHP3でレコードを削除する際に関連するレコードをまとめて削除する機能の解説。フレームワークのメリットを存分に発揮し、コマンドを1行追加するだけで実装可能。
-
-
CakePHP3で /Layout/defult.ctpにある titleタグ、h1タグを編集する方法
CakePHP3でtitleタグ、h1タグのテキストをデフォルトから変更する方法を解説。テンプレートファイルに「$this->assign()」でテキストを指定して「/Layout/defult.ctp」で受け取る。
-
-
VirtualBoxにCakePHP3を設置。必要なCentOS、Apache、PHP、MySQL、Composerをインストールし設定する
VirtualBoxにCentOS、Apache、MySQL、PHPをインストールするところから初めてCakePHP3の開発環境を構築する手順を詳細解説。この記事1つで全ての設定が完了する。
-
-
CakePHP4のCakeDC/Usersの Usersへの接続、バリデーションのカスタマイズ方法解説
CakeDC謹製Usersプラグインの紹介。CakePHP4で使う場合のUsersのカスタマイズとして入力項目のバリデーションの変更を、プラグインのファイルは触らずオーバーライドにより実装する方法を解説する。
-
-
CakePHP3のタイムゾーンを協定世界時UTCから日本標準時間JSTにずれを変更する方法
CakePHP3の標準設定のタイムゾーンは「UTC(協定世界時)」に設定されている。これを日本標準時に変更する方法(app.php、bootstrap.phpの変更方法)の解説。