データの削除
エンティティの削除
deleteメソッドでエンティティを削除する。
<?php $entity = $this->Articles->get(2); $result = $this->Articles->delete($entity); ?>
連鎖削除
エンティティーを削除するとき関連データを削除することもできる。テーブルの初期化メソッドにおいてHasOne や HasMany が dependent として設定されている場合、削除処理はそれらのエンティティーにも連鎖適用される。
<?php // テーブル内の初期化メソッド $this->hasMany('Comments', [ 'dependent' => true, 'cascadeCallbacks' => true, ]); ?>
一括削除
deleteAll()で、引数の条件を満たすレコードを一括削除できる。
<?php // 全てのスパムを削除する function destroySpam() { return $this->deleteAll(['is_spam' => true]); } ?>
厳密な削除
deleteOrFail()は、次の条件で Cake\ORM\Exception\PersistenceFailedException を投げます。
<?php try { $table->deleteOrFail($entity); } catch (\Cake\ORM\Exception\PersistenceFailedException $e) { echo $e->getEntity(); // エラー内容を表示 } ?>
データの保存 ~リクエストデータからアソシエーションを保存する~
リクエストデータのエンティティへの変換
getData()で取得したリクエストデータをnewEntity()に渡し、エンティティに変換。
<?php $entity = $articles->newEntity($this->request->getData()); ?>
複数のエンティティを変換する場合、newEntities()を用いる。
<?php // リクエストデータ [ [ 'title' => '一番目の投稿', 'published' => 1 ], [ 'title' => '二番目の投稿', 'published' => 1 ], ]; $entity = $articles->newEntities($this->request->getData()); ?>
変換するアソシエーションを定義
リクエストデータに含まれているアソシエーションを保存する場合、associatedオプションでどのアソシエーションが変換されるべきか定義する必要がある。'associated' => ['モデル']で変換するアソシエーションを定義する。
<?php // Tags, Comments, Commentに紐付いたUsersも含めてエンティティに変換する $entity = $articles->newEntity($this->request->getData(), [ 'associated' => [ 'Tags', 'Comments' => ['associated' => ['Users']] // 入れ子になっている ] ]); // より簡潔な記法 $entity = $articles->newEntity($this->request->getData(), [ 'associated' => ['Tags', 'Comments.Users'] ]); ?>
リクエストデータの形式
belongsToMany
アソシエーションのデータを新規作成する場合
<?php $data = [ 'title' => '私のタイトル', 'body' => '本文', 'user_id' => 1, 'tags' => [ ['name' => 'CakePHP'], ['name' => 'インターネット'], ] ]; ?>
既存のアソシエーションと紐付ける場合
<?php $data = [ 'title' => '私のタイトル', 'body' => '本文', 'user_id' => 1, 'tags' => [ '_ids' => [1, 2, 3, 4] ] ]; ?>
新規と既存のレコードを両方関連付ける場合
<?php $data = [ 'title' => '私のタイトル', 'body' => '本文', 'user_id' => 1, 'tags' => [ ['name' => '新しいタグ'], ['name' => '別の新しいタグ'], ['id' => 5], ['id' => 21] ] ]; ?>
hasMany
アソシエーションを新規作成したり、それらのプロパティを更新する場合
<?php $data = [ 'title' => 'My Title', 'body' => 'The text', 'comments' => [ ['id' => 1, 'comment' => 'Update the first comment'], // 既に紐付いているアソシエーションのプロパティを更新 ['id' => 2, 'comment' => 'Update the second comment'], ['comment' => 'Create a new comment'], // アソシエーションを新規作成 ] ]; ?>
既存のレコードと紐付ける場合
<?php $data = [ 'title' => '私の新しい記事', 'body' => '本文', 'user_id' => 1, 'comments' => [ '_ids' => [1, 2, 3, 4] ] ]; ?>
データの保存
データの新規作成
データの新規作成は以下の流れで行われる。
- newEntity()でエンティティをビルド
- エンティティのプロパティに値をセット
- save()でエンティティを保存
<?php use Cake\ORM\TableRegistry; $articlesTable = TableRegistry::getTableLocator()->get('Articles'); $article = $articlesTable->newEntity(); $article->title = '新しい記事'; $article->body = 'これは記事の本文です'; $articlesTable->save($article); ?>
データの更新
データの更新も新規作成と同様にsave()で行われる。
<?php $article = $articlesTable->get(12); // id 12 の記事を返します $article->title = 'CakePHP は最高のフレームワークです!'; $articlesTable->save($article) ?>
アソシエーションの保存
デフォルトではsaveでアソシエーションの一階層目を保存できる。
<?php $author = $articlesTable->Authors->findByUserName('mark')->first(); // 関連のあるテーブルからレコードを取得 $article = $articlesTable->newEntity(); // エンティティを生成 $article->title = 'mark の記事'; $article->author = $author; // 関連付け $articlesTable->save($article); // 保存 ?>
save()でアソシエーションのレコードを保存することもできる。
<?php $Comment = $articlesTable->Comments->newEntity(); // アソシエーションのエンティティを生成 $Comment->body = 'CakePHP の機能は傑出しています'; $article = $articlesTable->get(12); $article->comments = [$Comment]; // 関連付け $articlesTable->save($article); // 保存 ?>
関連付けにlink()を用いても可。
<?php $articlesTable->Tags->link($article, [$tag1, $tag2]); // 関連付け $articlesTable->save($article); $articlesTable->Tags->unlink($article, $tags); // 関連付け解除 ?>
リクエストデータをもとにエンティティを保存
getData()で取得したリクエストデータをnewEntity()に渡し、エンティティに変換。saveで保存する。
<?php $entity = $articles->newEntity($this->request->getData()); $articlesTable->save($entity); ?>
エンティティ構築前のリクエストデータ変更
beforeMarshalイベントの中でエンティティ構築前のリクエストデータ変更処理を行う。
<?php // ファイルの先頭に use ステートメントを入れること。 use Cake\Event\Event; use ArrayObject; // テーブルまたはビヘイビアークラスの中で public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options) { if (isset($data['username'])) { $data['username'] = mb_strtolower($data['username']); } } ?>
マスアサインメント攻撃の回避
マスアサインメント攻撃を回避するには、エンティティの一括代入機能を使う。編集可能なプロパティの配列を$_accessibleとして定義する。
<?php namespace App\Model\Entity; use Cake\ORM\Entity; class Article extends Entity { protected $_accessible = [ 'title' => true, 'body' => true, ]; } ?>
または、newEntity()あるいはpatchEntity()でfieldListオプションを使用する。
<?php $data = $this->request->getData(); // タイトルのみ変更することを許します $entity = $this->patchEntity($entity, $data, [ 'fieldList' => ['title'] ]); $this->save($entity); ?>
厳密な保存
saveOrFail()を使うと、次の条件でCake\ORM\Exception\PersistenceFailedExceptionを投げる。
- アプリケーションルールのチェックに失敗した場合
- エンティティーにエラーが含まれている場合
- 保存がコールバックによって中断された場合
<?php try { $table->saveOrFail($entity); } catch (\Cake\ORM\Exception\PersistenceFailedException $e) { echo $e->getEntity(); // エラーの内容を表示 } ?>
バリデーションとルール
バリデーション
リクエストデータがエンティティにコンバートされる前に、データ型や書式のルールが適用される。
<?php $article = $articles->newEntity($this->request->getData()); if ($article->errors()) { // エンティティー検証失敗。 } ?>
エンティティ構築時のバリデーションの流れ
- バリデータオブジェクトが作成される
- table および default バリデーションプロバイダーが追加される
- 命名に沿ったバリデーションメソッドが呼び出される。たとえば validationDefault 。
- Model.buildValidator イベントが発動する
- リクエストデータが検証される
- リクエストデータがそのカラム型に対応する型に変換される
- エラーがエンティティーにセットされる
- 正しいデータはエンティティーに設定されるが、 検証を通らなかったフィールドは除外される
バリデータオブジェクトの作成
デフォルトのバリデータオブジェクト
デフォルトのバリデータオブジェクトはテーブル中でvalidationDefault()で作成される。
<?php use Cake\ORM\Table; use Cake\Validation\Validator; class ArticlesTable extends Table { public function validationDefault(Validator $validator) { $validator ->requirePresence('title', 'create') ->notEmpty('title'); $validator ->allowEmpty('link') ->add('link', 'valid-url', ['rule' => 'url']); ... return $validator; } } ?>
カスタムバリデータオブジェクト
カスタムバリデータ'update'の使用例。validationUpdate()メソッドで作成する。
<?php class ArticlesTable extends Table { public function validationUpdate($validator) { $validator ->add('title', 'notEmpty', [ 'rule' => 'notEmpty', 'message' => __('タイトルを設定してください'), ]) ->add('body', 'notEmpty', [ 'rule' => 'notEmpty', 'message' => __('本文は必須です') ]); return $validator; } } ?>
エンティティ作成時のオプションで'update'を指定。これでデフォルトのバリデータではなくupdateバリデータが適用される。
<?php $article = $articles->newEntity( $this->request->getData(), ['validate' => 'update'] // バリデーションのオプション ); ?>
バリデータの組み合わせ
バリデータ作成メソッドの中で異なるバリデータを作成することで、バリデーションを組み合わせることができる。
<?php public function validationHardened(Validator $validator) { $validator = $this->validationDefault($validator); // デフォルトバリデータオブジェクトの作成 $validator->add('password', 'length', ['rule' => ['lengthBetween', 8, 100]]); return $validator; } ?>
ルール
データが保存される前に、ドメインルールまたはアプリケーションルールが適用される。
バリデーションはデータの構文や形式が正しいことを保証するのに対し、ルールはアプリケーションやネットワークの既存の状態に対してデータを比較することに焦点を当て、データの一貫性を保証する。
ルールチェッカーの作成
ルールチェッカーはテーブルクラスのbuildRules()で定義される。
<?php use Cake\ORM\RulesChecker; // テーブルクラスの中で public function buildRules(RulesChecker $rules) { // 作成および更新操作に提供されるルールを追加 $rules->add(function ($entity, $options) { // 失敗/成功を示す真偽値を返す }, 'ruleName'); // 作成のルールを追加 $rules->addCreate(function ($entity, $options) { // 失敗/成功を示す真偽値を返す }, 'ruleName'); // 更新のルールを追加 $rules->addUpdate(function ($entity, $options) { // 失敗/成功を示す真偽値を返す }, 'ruleName'); // 削除のルールを追加 $rules->addDelete(function ($entity, $options) { // 失敗/成功を示す真偽値を返す }, 'ruleName'); return $rules; } ?>
一意性ルール
isUnique(['検証するフィールド'])でデータの一意性を検証する。
<?php use Cake\ORM\Rule\IsUnique; // 一つのフィールド $rules->add($rules->isUnique(['email'])); // フィールドのリスト $rules->add($rules->isUnique( ['username', 'account_id'], )); ?>
外部キールール
existIn('検証するフィールド', '参照先テーブル')でほかのリソースと関連づいているかどうか(外部キーに参照先テーブルのキーが入っているか)を検証する。
<?php // 一つのフィールド $rules->add($rules->existsIn('article_id', 'Articles')); // 複数キー。複合主キーに役立ちます。 $rules->add($rules->existsIn(['site_id', 'article_id'], 'Articles')); ?>
アソシエーションカウントルール
一対多、多対多の関連があるとき、validCount('関連データ名', '件数', '比較演算子', 'エラーメッセージ')で関連データの件数を検証する。
<?php // ArticlesTable.php ファイルの中で // 記事にタグは5つ以内。 $rules->add($rules->validCount('tags', 5, '<=', 'タグは 5 つまで持てます')); ?>
ルールの無効化
エンティティ保存時にオプションでルールを無効化できる。
<?php $articles->save($article, ['checkRules' => false]); ?>
キャッシュ
FileCache
ローカルファイルを使用するキャッシュ。tmp/cache配下にキャッシュファイルを作成し、そこにデータを保存する。
クエリ結果をキャッシュする
cacheメソッドでクエリ結果をキャッシュする。第一引数にキャッシュキーを渡す。
<?php $query->cache('recent_articles'); $query->cache('recent_articles', 'dbResults'); // 第二引数にキャッシュ設定を渡す ?>
この場合、デフォルトではキャッシュファイルのパスはtmp/cache/cake_recent_articlesになる。
キャッシュへの書き込み
Cache::write(key, value)でキャッシュに値を書き込む。またCache::writeManyでは複数のキーを書き込める。
<?php Cache::write('recent_articles', $articles); Cache::writeMany([ // 連想配列を渡す 'recent_articles' => $article, 'recent_comments' => $comments ]); ?>
キャッシュへの書き込み
Cache::read(key)でキャッシュから値を読み込む。またCache::readManyでは複数のキーを読み込める。
<?php $results = Cache::read('recent_articles'); $results = Cache::readMany([ // 配列を渡す 'recent_articles' , 'recent_comments' ]); // [ // 'recent_articles' => '...', // 'recent_comments' => '...' // ] ?>
キャッシュからの削除
Cache::delete(key, value)でキャッシュから値を削除する。またCache::deleteManyでは複数のキーを削除できる。
<?php Cache::delete('recent_articles'); Cache::deleteMany([ // 連想配列を渡す 'recent_articles', 'recent_comments' ]); ?>
キャッシュデータのクリア
<?php // 有効期限切れのキーのみをクリアする。 Cache::clear(true); // すべてのキーをクリアする。 Cache::clear(false); ?>
【Table】関連データの取得
関連データの取得
containメソッド
containで関連データをeagar loadして取得する。
<?php $articles->find('all')->contain(['Authors', 'Comments']); ?>
ネストされた関連データを取得するときはドット記法を用いる。
<?php $articles->find()->contain([ 'Authors.Addresses', 'Comments.Authors' ]); ?>
関連データを条件によってフィルターすることができる。条件を指定するには、第二引数としてクエリーオブジェクトを受け取る無名関数を渡す。
<?php $articles->find()->contain('Comments', function (Query $q) { return $q ->select(['body', 'author_id']) ->where(['Comments.approved' => true]); }); ?>
matchingメソッド
matchingメソッドで、関連データの条件にマッチするレコードを取得する。
<?php $articles->find()->matching('Tags', function ($q) { return $q->where(['Tags.name' => 'CakePHP']); }); ?>
このメソッドはINNER JOIN句を生成するので、レコードが重複する可能性がある。distinctメソッドでプライマリーキーなど(ここではid)で重複行をまとめる。
<?php $articles->find()->distinct('id')->matching(...); ?>
innerJoinWithメソッド
matchingと同様に関連データの条件でレコードを絞り込むが、matchingと違い関連データを取得しない。
<?php $articles->find()->innerJoinWith('Tags', function ($q) { return $q->where(['Tags.name' => 'CakePHP']); }); ?>
notMatchingメソッド
matchingメソッドの逆。関連データの条件にマッチしないレコードを取得する。
<?php $articles->find()->notMatching('Tags', function ($q) { return $q->where(['Tags.name' => 'CakePHP']); }); ?>
このメソッドはLEFT JOIN句を生成するので、レコードが重複する可能性がある。distinctメソッドでプライマリーキーなど(ここではid)で重複行をまとめる。
<?php $articles->find()->distinct('id')->notMatching(...); ?>
leftJoinWithメソッド
すべての関連データをロードしたくはないが、関連データに基いて結果を計算したいときはleftJoinWithを使う。以下は記事 (Article) の全データと一緒に、記事ごとのコメント (Comment) 数をロードするときの例。
<?php $articlesTable->find()->select(['total_comments' => $query->func()->count('Comments.id')]) ->leftJoinWith('Comments') ->group(['Articles.id']) ->enableAutoFields(true); ?>
【Table】データの取り出し
クエリの生成
findメソッド
findメソッドでクエリを生成。
<?php $query = $articles->find('all'); // この時点ではクエリーは走らない。 $results = $query->all(); // all() の呼び出しはクエリーを実行し、結果セットを返す foreach ($query as $row) {...} // イテレーションはクエリーを実行する ?>
第一引数
<?php // 全件取得 $query = $articles->find('all'); // データを連想配列として取得 $query = $articles->find('list', [ 'keyField' => 'slug', // キーを指定 デフォルトではid 'valueField' => 'title' // 値を指定 デフォルトではdisplayFieldで指定された列 ]); // 取得結果 // $query->toArray() = [ // 'first-post' => '最初の投稿', // 'second-article-i-wrote' => '私が書いた2つ目の記事', // ]; // スレッド状のデータを取得 $query = $articles->find('thread'); ?>
カスタムFinder
findFooBarメソッドによって、fooBarという名前のカスタムfinderが作れる。
<?php public function findOwnedBy(Query $query, array $options) { $user = $options['user']; return $query->where(['author_id' => $user->id]); } $query = $articles->find('ownedBy', ['user' => $userEntity]); // 呼び出し ?>
クエリの実行
クエリ実行パターン① findの第二引数にオプションを指定する
<?php $query = $articles->find('all', [ 'conditions' => ['Articles.created >' => new DateTime('-10 days')], 'contain' => ['Authors', 'Comments'], 'limit' => 10 ]); ?>
findで使えるオプション
- conditions WHERE
- limit LIMIT
- offset 指定した整数番目からデータを取得する
- contain 関連をイーガーロード (eager load) する
- fields 列を指定する
- group GROUP BY
- having HAVING
- join JOIN
- order ORDER BY
クエリ実行パターン② クエリオブジェクトにメソッドを呼び出す
<?php $articles->find('all')->first(); // 最初のレコードを取得 $articles->find('all')->count(); // レコードの件数を取得 $articles->find('list')->toArray(); // レコードを連想配列として取得 ?>