CakePHP データベースアクセス / ORM
・データベーステーブルが規約に準拠している場合、すぐにORMの利用を開始できる。 use Cake\ORM\TableRegistry; // articlesテーブルからデータ取得 $articles = TableRegistry::get('Articles'); $query = $articles->find(); foreach ($query as $row) { // 各$rowはArticleエンティティクラスのインスタンス echo $row->title; } ・テーブルクラスの作成 src/Model/Table に置く。 テーブルクラス名は、テーブル名にTableを加える。 namespace App\Model\Table; use Cake\ORM\Table; class ArticlesTable extends Table { } ・主キーの指定 id 以外の主キーの場合、initialize()で設定する。 $this->setPrimaryKey('my_id'); ・エンティティクラスの指定 命名規則に従わないエンティティクラスを使用する場合、initialize()で設定する。 $this->setEntityClass('App\Model\Entity\PO'); ・ビヘイビアの追加 ビヘイビアは、テーブルクラスの共通ロジック。 initialize()で設定する。 $this->addBehavior('Timestamp'); ・データ検証 // コントローラ側 $article = $this->Articles->newEntity($this->request->getData()); if ($article->errors()) { // 検証失敗 } ・バリデーションの作成 バリデーションは、ユーザ入力を検証する。 // デフォルトで呼ばれるバリデーション public function validationDefault(Validator $validator) { $validator->requirePresence('title', 'create') ->notEmpty('title'); $validator->allowEmpty('link') ->add('link', 'valid-url', ['rule' => 'url']); return $validator; } // バリデーションのカスタマイズ public function validationUpdate($validator) { ... } // コントローラ側 $article = $this->Articles->newEntity($this->request->getData(), ['validate' => 'update']); ・requirePresence() バリデーション対象の配列にフィールドが実在すること。 第2パラメータ create : create実行時のみ適用 update : update実行時のみ適用 $validator->requirePresence('author_id', 'create'); $validator->requirePresence(['author_id', 'title'], 'create'); $validator->requirePresence([ 'author_id' => [ 'mode' => 'create', 'message' => 'An author is required.', ], 'published' => [ 'mode' => 'update', 'message' => 'The published state is required.', ] ]); ・allowEmpty() フィールドが空欄でもよいこと。 第2パラメータ create : create実行時のみ適用 update : update実行時のみ適用 $validator->->allowEmpty('header_image', 'update'); ・notEmpty() フィールドが空欄でないこと。 第2パラメータ create : create実行時のみ適用 update : update実行時のみ適用 $validator->notEmpty('body', 'Body cannot be empty', 'create') ・add() 組み込みのバリデーションルール alphaNumeric:半角のアルファベットまたは数字であるか。 'rule' => 'alphaNumeric', between: データ長が指定範囲内であるか。 'rule' => array('between', 5, 15), blank: 空かスペースのみであるか。 スペースには、半角スペース・タブ・キャリッジリターン・改行文字を含む。 'rule' => 'blank', boolean: true/false または 0/1 または '0'/'1'であるか。 'rule' => array('boolean'), cc: クレジットカード番号として適切か。 'rule' => array('cc', array('visa', 'maestro'), false, null), comparison: 数字が指定範囲内であるか。 is greater, is less, greater or equal, less or equal, equal to, not equal 'rule' => array('comparison', '>=', 18), 'rule' => array('comparison', 'greater or equal', 18), date: 日付形式であるか。 'rule' => array('date', array('ymd')), decimal: データが小数であるか。小数点以下の桁数を指定。 'rule' => array('decimal', 2) email: email形式であるか。trueを指定すると、メールサーバのホストが存在するか確認する。 'rule' => array('email', true), equalTo: 指定値と等しいか。 'rule' => array('equalTo', 'cake'), extension: ファイル名の拡張子が指定値と等しいか。 'rule' => array('extension', array('gif', 'jpeg', 'png', 'jpg'), ip: IPv4形式であるか。 'rule' => 'ip', isUnique: 重複しない値であるか。 'rule' => 'isUnique', minLength: データ長が指定値以上であるか。 'rule' => array('minLength', '8'), maxLength: データ長が指定値以下であるか。 'rule' => array('maxLength', '15'), money: 金額として有効であるか。通貨記号の位置を'left'/'right'で指定。 'rule' => array('money', 'left'), Multiple: 複数選択で指定した値が含まれるか。指定数の最小値と最大値を指定。 'rule' => array('multiple', array('in' => array('foo', 'bar'), 'min' => 1, 'max' => 3)), inList: 指定リストに含まれるか。 'rule' => array('inList', array('Foo', 'Bar')), numeric: 数字または数値形式であるか。 'rule' => 'numeric', notEmpty: 空でないか。 'rule' => 'notEmpty', phone: アメリカの電話番号形式であるか。日本の場合は、電話番号形式の正規表現を第2パラメータに指定。 'rule' => array('phone', null, 'us'), postal: 郵便番号形式であるか。日本の場合は、郵便番号形式の正規表現を第2パラメータに指定。 'rule' => array('postal', null, 'us') range: 指定範囲内の値であるか。 'rule' => array('range', 0, 10), ssn: 社会保障番号であるか。 'rule' => array('ssn', null, 'us') url: URL形式であるか。 'rule' => 'url' ・ルールの作成 ルールは、コード中で変更されたデータを検証する。 ルールは save()及びdelete()メソッド実行時にチェックされる。 テーブルクラスのbuildRules()メソッドに定義する。 public function buildRules(RulesChecker $rules) { // add, update時のルール $rules->add(function ($entity, $options) { ... }, 'ruleName'); // add時のルール $rules->addCreate(function ($entity, $options) { ... }, 'ruleName'); // update時のルール $rules->addUpdate(function ($entity, $options) { ... }, 'ruleName'); // delete時のルール $rules->addDelete(function ($entity, $options) { ... }, 'ruleName'); return $rules; } ・エンティティクラスの作成 src/Model/Entity に置く。 エンティティクラス名は、テーブル名。 namespace App\Model\Entity; use Cakd\ORM\Entity; class Article extends Entity { } ・エンティティのデータへのアクセス $this->Articles->title; ・get get()をカスタマイズする際に使用する。 protected function _getTitle($title) { return ucwords($title); } // コントローラ側 echo $this->Articles->title; ・set set()をカスタマイズする際に使用する。 protected function _setTitle($title) { return Text::slug($title); } // コントローラ側 $this->Articles->title = 'foo'; ・仮想プロパティの生成 存在しないフィールドを作成し、アクセスを提供する。 protected function _getFullName() { return $this->_properties['first_name'] . ' ' . $this->_properties['last_name']; } // コントローラ側 echo $this->Users->full_name; ・エンティティの変更チェック ・フィールドの変更チェック $this->Articles->dirty('title'); ・変更を設定する $this->Articles->comments[] = $newComment; $this->Articles->dirty('comments', true); ・変更前の値を取得する $org = $this->Articles->getOriginal('title'); ・変更されたフィールド一覧を取得する $dirtyFields = $this->Articles->getDirty(); ・一括代入 リクエストからユーザデータをエンティティへ一括代入できるかを指定する。 protected $_accessible = [ 'title' => true, 'body' => true, '*' => false,]; // 指定されていないフィールド コントローラ側でアクセス可・不可を設定する。 $this->Articles->accessible('user_id', true); ・エンティティがDB上に存在するデータかチェックする if (!$this->Articles->isNew()) { echo 'already registered.'; } ・エンティティから配列への変換 $array = $this->Users->toArray(); ・エンティティからJSONへの変換 $json = json_encode($this->Users); ・接続設定 config/app.php に 'default' の接続設定をする。 ・データベースアクセスAPIで出来ること ・データベース接続 use Cake\Datasource\ConnectionManager; $dsn = 'mysql://root:password@localhost/my_database'; ConnectionManager::config('default', ['url' => $dsn]); $con = ConnectionManager::get('default'); ・select $results = $con->execute('select * from articles where id = :id', ['id' => 1]) ->fetchAll('assoc'); ・insert $con->insert('articles',['title' => 'A New Article', 'created' => new DateTime('now')], ['created' => 'datetime']); ・update // id=10を更新 $con->update('articles',['title' => 'New Title'], ['id' => 10]); ・delete $con->delete('articles', ['id' => 10]); ・query パラメータは使用できない。 $con->query('update articles set published = 1 where id = 2'); ・execute パラメータ、型指定を使用できる。 $con->execute('update articles set published_date = ? where id = ?', [new DateTime('now'), 2], ['date', 'integer']); ・トランザクション $con->begin(); $con->execute('update articles set published = ? where id = ?', [true, 2]); $con->execute('update articles set published = ? where id = ?', [false, 4]); $con->commit(); // bigen,commit,rollbackを自動でハンドリングする $con->transactional(function ($con) { $con->execute('update articles set published = ? where id = ?', [true, 2]); $con->execute('update articles set published = ? where id = ?', [false, 4]); }); ・結果行の取得 $stmt->execute('select ...'); // 1行読み込む $row = $stmt->fetch('assoc'); // 全行読み込む $rows = $stmt->fetchAll('assoc'); foreach ($rows as $row) { } ・行数の取得 $stmt->execute('select ...'); $rowCount = count($stmt); $rowCount = $stmt->rowCount(); ・クエリオブジェクト ・クエリオブジェクトの作成 $articles = TableRegistry::get('Articles'); $query = $articles->find(); // コントローラの場合 $query = $this->Articles->find(); ・メソッドをチェーンする $query = $this->Articles->find() ->select(['id', 'name']) ->where(['id !=' => 1]) ->order(['created' => 'DESC']); ・主キーで単一のエンティティを取得する $article = this->Articles->get($id); ・カラムから値リストを取得する $allTitles = $this->Articles->extract('title'); foreach ($allTitles as $title) { echo $title; } ・クエリはCollectionオブジェクト Collectionオブジェクトで使用できるメソッドは、クエリオブジェクトでも使用できる。 $keyValueList = $this->Articles->find()->combine('id', 'title'); ・SQL関数 $query=$this->Articles->find(); $query->select(['count' => $query->func()->count('*']); sum() avg() min() max() count() concat() : 2つの値を結合する。 coalesce() dateDiff() : 2つの日にち/時間の差を取得する。 now() extract() : SQL式から特定の日付部分(年など)をかえす。 dateAdd() dayOfWeek() ・Group と Having $query = $this->Articles->find(); $query->select(['count' => $query->func()->count('view_count'), 'published_date' => 'DATE(created)']) ->group('published_date') ->having(['count >' => 3]); ・Case select count(case when published = 'Y' then 1 end) as number_published from articles $query = $this->Articles->find(); $publishedCase = $query->newExpr() ->addCase($query->newExpr()->add(['published' => 'Y']), 1, 'integer'); $query->select(['number_published' => $query->func()->count($publishedCase)]); ・エンティティの代わりに配列を取得 $query = $this->Articles->find(); $query->hydrate(false); // エンティティの代わりに配列を返す $result = $query->toList() // クエリを実行し配列を返す ・高度な条件 $query = $this->Articles->find() ->where(['author_id' => 3, 'OR' => [['view_count' => 2],['view_count' => 3]]]); $query = $this->Articles->find() ->where(['author_id' => 2]) ->orWhere(['author_id' => 3]) ->andWhere(['published' => true, 'view_count >' => 10]) ->orWhere(['promoted' => true]); ・In // カラムのデータ型を明示する $query = $this->Articles->find() ->where(['id' => $ids], ['id' => 'integer[]']); // もしくはINを含める $query = $this->Articles->find() ->where(['id IN' => $ids]); ・結果を取得する // イテレートする foreach($query as $row) { } // 結果を取得する $results = $query->all(); // コレクションのメソッドを使う $ids = $query->map(function ($row) { return $row->id; }); // 最初の行だけ取得する $row = $query->first(); ・関連付くデータをロードする article毎に、authorとcommentをロードする。 $query = $this->Articles->find() ->contain(['Authors', 'Comments]); ・inner join with $query = $this->Articles->find(); $query->innerJoinWith('Tags', function ($q) { return $q->where(['Tags.name' => 'CakePHP']); }); ・データをinsertする $query = $this->Articles->query; $query->insert(['title', 'body']) ->values(['title' => 'First post', 'body' => 'Some body text']) ->values(['title' => 'Second post', 'body' => 'Another body text']) ->execute(); // または $article = $this->Articles->newEntity(); $article->title = 'this is a new article.'; $article->body = 'this is a body.'; if ($this->Articles->save($article)) { // 登録されたデータのid $id = $article->id; } ・データをupdateする $query = $this->Articles->query(); $query->update() ->set(['published' => true]) ->where(['id' => $id]) ->execute(); // または $article = $this->Articles->get(12); $article->title = 'changed title'; $this->Articles->save($article); ・データをdeleteする $query = $this->Articles->query(); $query->delete() ->where(['id' => $id]) ->execute(); ・関連テーブルのデータ追加・更新 $tag1 = $this->Articles->Tags->findByName('cakephp')->first(); $tag2 = $this->Articles->Tags->newEntity(); $tag2->name = 'fantastic'; $article = $this->Articles->get(12); $article->tags = [$tag1, $tag2]; // 外部キーは自動でセットされる。 $this->Articles->save($article); ・中間テーブルへのデータ保存 // 最初にレコードを紐づける。 $tag1 = $this->Articles->Tags->findByName('cakephp')->first(); $tag1->_joinData = $this->Articles->ArticlesTags->newEntity(); $tag1->_joinData->tagComment = 'this is a tag comment.'; $this->Articles->Tags->link($article, [$tag1]); // 既存のアソシエーションを更新する。 $article = $this->Articles->get(1, ['contain' => ['Tags']]); $article->tags[0]->_joinData->tagComment = 'this is a new comment'; // 必須 $article->dirty('tags', true); $this->Articles->save($article, ['associated' => ['Tags']]); ・SQLインジェクション対策 バインディングを使用する。 $query->where(['MATCH (comment) AGAINST (:userData)', 'created < NOW() - :moreUserData']) ->bind(':userData', $userData, 'string') ->bind(':moreUserData', $moreUserData, 'datetime'); ・UNION $query1 = $this->Articles->find() ->where(['need_review' => true]); $query2 = $this->Articles->find() ->where(['published' => false]); $query2->union($query1); ・ステートメントのロック $query->epilog('FOR UPDATE'); viaSystem Development Tips Your own website, Ameba Ownd