読者です 読者をやめる 読者になる 読者になる

Motomichi Works Blog

その日学習したことについて書いている日記です。誰かの役に立ったらそれはそれで嬉しいです。

さくらvpsとcakephp2.6.7で開発日記 その0014 記事投稿フォームと記事一覧ページを作成する HABTM(hasAndBelongsToMany)

参考にさせて頂いたページ

HABTM(hasAndBelongsToMany)について

アソシエーション: モデル同士を繋ぐ — CakePHP Cookbook 2.x ドキュメント

あの日見たHABTMの中間テーブルの使い方を僕たちはまだ知らない | 日記の間 | あかつきのお宿

HABTM(hasAndBelongsToMany)のwith項目の設定について

php - CakePHP Fatal Error Call to a member function schema() on a non-object - Stack Overflow

HABTM(hasAndBelongsToMany)のデータ保存はsaveAllを使うことについて

cakePHPでHABTMのレコードを一括保存する方法 - Qiita

saveAllの使い方について

データを保存する — CakePHP Cookbook 2.x ドキュメント

SQLのlimitなどを指定する

CakePHP hasAndBelongsToMany でページング(SQL LIMIT)とかを設定する ← Neo Inspiration

記事タグ追加フォームの作成とデータの挿入

以下の過去記事で記事タグ追加ページを作成した。

articlesテーブルとarticletagsテーブルを紐付けてデータを効率的に取得するにあたって、まずはarticletagsテーブルにデータが必要なので、あらかじめデータを挿入しておいた。

さくらvpsとcakephp2.6.7で開発日記 その0012 記事タグ追加フォームを作る - MOTOMICHI WORKS BLOG

articlesテーブルの作成

例として以下の通り。

CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) NOT NULL,
  `user_id` varchar(255) NOT NULL,
  `title` varchar(255) NOT NULL,
  `text` varchar(2000) NOT NULL,
  `is_publish` tinyint(1) NOT NULL,
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

HABTMに直接関係ないカラムも沢山あります。
HABTMに必要な条件はidカラムと、それがプライマリキーである事だけかな?
あとテーブル名も中間テーブル名に関わったり、foreignKeyとかassociationForeignKeyに関わってくるから重要かな?

articletagsテーブルの作成

例として以下の通り。

CREATE TABLE `articletags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) NOT NULL,
  `text` varchar(255) NOT NULL,
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

HABTMに直接関係ないカラムも沢山あります。
HABTMに必要な条件はidカラムと、それがプライマリキーである事だけかな?
あとテーブル名も中間テーブル名に関わったり、foreignKeyとかassociationForeignKeyに関わってくるから重要かな?

articles_articletagsテーブルの作成

例として以下の通り。

CREATE TABLE `articles_articletags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `article_id` int(11) NOT NULL DEFAULT '0',
  `articletag_id` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

articlesテーブルとarticletagsテーブルを紐付ける為の中間テーブルなので、アンダースコアを付けてアルファベット順に繋げてarticles_articletagsというテーブル名にする。
カラム名も紐付けする二つのテーブル名にならって、article_idとarticletag_idという単数形。

app/Model/Article.php

例として以下の通り。$validatesの部分は省いています。

<?php
App::uses('AppModel', 'Model');

class Article extends AppModel{

  public $hasAndBelongsToMany = array('Articletag');

}

テーブル名やカラム名、クラス名などがCakePHP命名規則にならっている場合は、$hasAndBelongsToManyに格納する値はpublic $hasAndBelongsToMany = array('Articletag');だけで良い。

特別にデフォルト値以外の設定をしたいときだけ、アソシエーション: モデル同士を繋ぐ — CakePHP Cookbook 2.x ドキュメントにあるような詳細設定をすることになる。

詳細に設定する場合には以下のように書くと動いた。

withには中間テーブル名を設定するのかな?

これについては、php - CakePHP Fatal Error Call to a member function schema() on a non-object - Stack Overflowを参考にさせて頂いた。

public $hasAndBelongsToMany = array(
  'Articletag' => array(//この連想配列keyの文字列は紐付けたいModel名
    'className' => 'Articletag',//紐付けたいModel名
    'joinTable' => 'articles_articletags',//中間テーブル名
    'foreignKey' => 'article_id',//このテーブルの外部キーarticlesテーブルなのでarticle_id(単数形)
    'associationForeignKey' => 'articletag_id',//articletagsテーブルを紐付けたいのでarticletag_id(単数形)
    'unique' => true,
    'conditions' => '',
    'fields' => '',
    'order' => '',
    'limit' => '',
    'offset' => '',
    'finderQuery' => '',
    'with' => 'articles_articletags'
  )
);

app/Controller/ArticlesController.php

例えばindexアクションで単純にfindを使って、$view_datas = $this->Article->find('all');とすれば多対多の紐付けされたデータが取得できるので、print_r($view_datas);とかで確認すると、配列構造がわかる。

例えばpostアクションでは、saveAllメソッドを使用する事と、そのsaveAllメソッドに適切な構造の配列を渡す必要があり、フォームから送信されたデータを適当に整形して、以下のような階層構造の配列を用意してやると良い。
キーは自分のテーブルに合わせて適宜変えてください。

//saveAllに渡す為のデータを準備
$params = array(
  array(
    'Article' => array(
      'code' => '文字列',
      'user_id' => '文字列',
      'title' => '文字列',
      'text' => '文字列',
      'is_publish' => '1',
      'is_deleted' => '0',
    ),
    'Articletag' => array(
      //articletagsテーブルのid
      //以下のように配列要素が3つあると、中間テーブルであるarticles_articletagsに3行追加される
      '1',
      '2',
      '3',
    )
  )
);

saveAllは上記のような階層構造の配列を、以下のような感じで渡してデータベースに保存する。

$this->Article->saveAll($params);

このとき、articlesテーブルにデータが挿入されるのはもちろん、articles_articletagsにも自動的にデータが保存される。

HABTMについてまとめ

今回の例では、以下の三つのテーブルを用意して、articletagsには予めデータを挿入しておいた。

  • articles
  • articletags
  • articles_articletags

CakePHP命名規則に則っていれば、$hasAndBelongsToManyに格納する値は、public $hasAndBelongsToMany = array('Articletag');だけで良い。

Controllerでは、普通に$view_datas = $this->Article->find('all');とするだけで多対多の紐付けされたデータが取得できるし、saveAllメソッドに適切な構造の配列を渡せば、中間テーブルにもデータが挿入される。

saveAllメソッドについては、データを保存する — CakePHP Cookbook 2.x ドキュメントを参考にした。