CakePHP / カテゴリーデータとショップデータを紐づけて管理してみる(HABTM)
さいしょに
TreeBehaviorを使ってシンプルなカテゴリー管理画面(CRUD)を作る にショップデータを紐づけて、管理を出来るようにしました。
完成図
action: index
# 店舗情報がわかる
# 所属カテゴリがわかる
# ソート機能
# 店舗の登録へ
# 店舗の編集へ
action: add or edit
# 店舗の登録 or 変更ができる
# カテゴリの登録 or 変更ができる(1〜2つ)
※カテゴリ数増えたら流すぎてやばそう…
# バリデーション チェック
※ カテゴリーの削除へ(editの場合のみ、右側にリンク表示)
action: delete
# 店舗の削除ができる
# 紐付いてるカテゴリーも削除する
準備:DBテーブルを登録
CREATE TABLE IF NOT EXISTS `categories_shops` ( `id` int(11) NOT NULL AUTO_INCREMENT, `shop_id` int(8) unsigned zerofill NOT NULL, `category_id` int(11) NOT NULL DEFAULT '1', PRIMARY KEY (`id`), KEY `shop_id` (`shop_id`), KEY `category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=15 ; INSERT INTO `categories_shops` (`id`, `shop_id`, `category_id`) VALUES (1, 00000001, 3), (2, 00000002, 4), (3, 00000002, 5), (4, 00000003, 7), (5, 00000003, 11), (6, 00000004, 15), (12, 00000005, 1), (13, 00000005, 3), (14, 00000006, 5); CREATE TABLE IF NOT EXISTS `shops` ( `id` int(8) unsigned zerofill NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `kana` varchar(255) DEFAULT NULL, `priority` int(5) NOT NULL DEFAULT '99999', `display` int(1) NOT NULL DEFAULT '0', `created` datetime DEFAULT NULL, `modified` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `priority` (`priority`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=9 ; INSERT INTO `shops` (`id`, `name`, `kana`, `priority`, `display`, `created`, `modified`) VALUES (00000001, '焼肉きんきん', 'ヤキニクキンキン', 99999, 0, '2013-05-07 17:14:30', '2013-05-07 17:14:30'), (00000002, '京京亭', 'キョウキョウテイ', 99999, 0, '2013-05-07 17:16:04', '2013-05-07 17:16:04'), (00000003, 'くら川', 'クラヤマ', 99999, 0, '2013-05-07 17:16:36', '2013-05-07 17:16:36'), (00000004, 'カラテの空', 'カラテノソラ', 99999, 0, '2013-05-07 17:17:20', '2013-05-07 17:17:20'), (00000005, 'とみ次郎', 'トミジロウ', 99999, 0, '2013-05-07 17:20:18', '2013-05-07 17:43:37'), (00000006, 'いかつぼ', 'イカツボ', 99999, 0, '2013-05-07 17:26:17', '2013-05-07 17:44:23');
準備:コード
# app/Model/Category.php
<?php class Category extends AppModel { public $displayField = 'name'; public $actsAs = array('Tree'); public $validate = array( 'name' => array( array( 'rule' => 'notEmpty', 'message' => 'カテゴリー名を入力して下さい', ), array( 'rule' => array('maxLength', 30), 'message' => 'カテゴリー名を30文字以内で入力して下さい。', ), ), ); }
# app/Controller/ShopsController.php
<?php class ShopsController extends AppController { public $uses = array('Shop'); public $paginate = array( 'Shop' => array( 'order' => array( 'modified' => 'DESC', ), 'limit' => 30, ), ); public function beforeRender() { $this->set('categoryList', $this->Shop->Category->find('list', array('order' => array('Category.lft ASC')))); } public function index(){ $shopList = $this->paginate(); $this->set(compact('shopList')); } public function delete($id) { if($this->request->isDelete()) { if ($this->Shop->delete($id)) { $this->Session->setFlash('店舗を削除しました'); } else { $this->Session->setFlash('店舗の削除に失敗しました'); } $this->redirect(array('action'=> 'index')); } $this->request->data = $this->Shop->findById($id); if (empty($this->request->data)) { $this->Session->setFlash('店舗が見つかりませんでした'); $this->redirect(array('action'=> 'index')); } } public function add() { return $this->edit(); } public function edit($id = null) { if($this->request->isPost() || $this->request->isPut()) { if($this->Shop->save($this->request->data)) { $this->Session->setFlash('店舗情報を保存しました'); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash('入力に間違いがあります'); } } else { if($id !== null) { $this->request->data = $this->Shop->findById($id); if(empty($this->request->data)) { $this->Session->setFlash('店舗が見つかりませんでした'); $this->redirect(array('action'=> 'index')); } } } $this->render('edit'); } }
# app/Model/Shop.php
<?php class Shop extends AppModel { public $displayField = 'name'; public $hasAndBelongsToMany = array( 'Category', ); public $validate = array( 'name' => array( array( 'rule' => 'notEmpty', 'message' => '店舗名を入力して下さい', ), array( 'rule' => array('maxLength', 255), 'message' => '店舗名を255文字以内で入力して下さい。', ), ), 'kana' => array( array( 'rule' => array('maxLength', 255), 'message' => 'フリガナを255文字以内で入力して下さい。', ), ), 'priority' => array( array( 'rule' => array('range', -1, 100000), 'message' => '0 〜 99999 の数値で入力して下さい。', ), ), ); public function afterValidate() { // カテゴリーを1〜2つ選択して下さい。 if (empty($this->data['Category']['Category']) || count($this->data['Category']['Category']) > 2) { $this->validationErrors['Shop'][] = 'カテゴリーを1〜2つ選択して下さい。'; } } }
# app/View/Shops/index.ctp
<div> <h2>メニュー</h2> <ul> <li><?php echo $this->Html->link('店舗登録', array('action' => 'add')); ?></li> </ul> </div> <div> <h2>店舗一覧</h2> <table> <tr> <th><?php echo $this->Paginator->sort('id', 'ID'); ?></th> <th><?php echo $this->Paginator->sort('name', '店舗名'); ?></th> <th><?php echo $this->Paginator->sort('Category.name', 'カテゴリー名'); ?></th> <th><?php echo $this->Paginator->sort('modified', '最終更新時間'); ?></th> <th>操作</th> </tr> <?php foreach($shopList as $shop): ?> <tr> <td><?php echo h($shop['Shop']['id']); ?></td> <td><?php echo h($shop['Shop']['name']); ?></td> <td> <?php foreach($shop['Category'] as $category): ?> 【 <?php echo $category['name']; ?> 】 <?php endforeach; ?> </td> <td><?php echo h($shop['Shop']['modified']); ?></td> <td> <?php echo $this->Html->link('編集', array('action' => 'edit', $shop['Shop']['id'])); ?> </td> </tr> <?php endforeach; ?> </table> </div> <div class="paginateLinks"> <?php echo $this->Paginator->prev(); ?> <?php echo $this->Paginator->numbers(); ?> <?php echo $this->Paginator->next(); ?> </div>
# app/View/Shops/edit.ctp
<?php $errorMessages = $this->validationErrors['Shop']; ?> <div> <ul> <?php foreach($errorMessages as $messages): ?> <?php foreach($messages as $message): ?> <li> <?php echo h($message); ?> </li> <?php endforeach; ?> <?php endforeach; ?> </ul> </div> <div> <h2>店舗<?php echo empty($this->data['Shop']['id']) ? '追加' : '編集'; ?></h2> </div> <div> <?php echo $this->Form->create('Shop'); ?> <?php echo empty($this->data['Shop']['id']) ? null : $this->Form->input('id', array('type' => 'hidden')); ?> <h3>店舗ID</h3> <p><?php echo empty($this->data['Shop']['id']) ? '新規登録' : h($this->data['Shop']['id']) ?></p> <h3>店舗名</h3> <p><?php echo $this->Form->inout('name', array('placeholder' => '店舗名を入力して下さい。')); ?></p> <h3>店舗名(フリガナ)</h3> <p><?php echo $this->Form->inout('kana', array('placeholder' => 'フリガナを入力して下さい。')); ?></p> <h3>優先順位(0 〜 99999 )<span> ※ 0 に近いほど上位に表示</span></h3> <p><?php echo $this->Form->inout('priority', array('default' => 99999)); ?></p> <h3>カテゴリーの選択(1〜2つ)</h3> <p><?php echo $this->Form->input('Category.Category', array('label' => false, 'multiple' => 'checkbox', 'options' => $categoryList)); ?></p> <?php echo $this->Form->end(empty($this->data['Shop']['id']) ? ' 追加 ' : ' 編集'); ?> </div> <div style="text-align: right;"> <?php echo empty($this->data['Shop']['id']) ? null : $this->Html->link('削除する', array('action' => 'delete', $this->data['Shop']['id'])); ?> </div> <div class="pageLinks"> <p><?php echo $this->Html->link('戻る', array('action' => 'index')); ?></p> </div>
# app/View/Shops/delete.ctp
<div> <h2>店舗削除</h2> </div> <div> <?php echo $this->Form->create('Shop', array('type' => 'delete')); ?> <?php echo $this->Form->input('id', array('type' => 'hidden')); ?> <h3>店舗ID</h3> <p><?php echo h($this->data['Shop']['id']) ?></p> <h3>店舗名</h3> <p><?php echo h($this->data['Shop']['name']); ?> <h3>※解除されるカテゴリー</h3> <ul> <?php if (count($this->data['Category']) <= 0): ?> <li>なし</li> <?php else: ?> <?php foreach($this->data['Category'] as $deleteCategory): ?> <li> <?php echo h($deleteCategory['name']); ?> </li> <?php endforeach; ?> <?php endif; ?> </ul> <p>よろしければ「削除」ボタンを押してください</p> <?php echo $this->Form->end(' 削除 '); ?> </div> <div class="pageLinks"> <p><?php echo $this->Html->link('戻る', array('action' => 'edit', $this->data['Shop']['id'])); ?></p> </div>
最後に
ほぼ自分用のメモになってる。
カテゴリの内容入れ替えたりとか、店舗のデータ追加してみたりすればそれっぽくなるかも