忙しい人のためのCakePHP入門（by ドットインストール）

# はじめに
この記事は[ドットインストールのCakePHP入門](http://dotinstall.com/lessons/basic_cakephp)において、個人的に要チェックや！と思ったことをギュッとまとめたメモです。※なお実際に動画で説明されているものとは順番が異なる箇所があります。

CakePHPビギナーの復習として、または軽い予習として使えることを目指しました。より詳しくはドットインストール、もしくはそこで紹介されている書籍を見てみましょう。:)




# CakePHP関連サイト
- [CakePHP公式サイト](http://cakephp.org/)
- [CakePHP日本語サイト](http://cakephp.jp/)
- [CakePHPCookbook日本語サイト](http://book.cakephp.org/2.0/ja/index.html)




# 覚えておくべき用語とその意味
## CoC
- Convention over Configuration: 設定より規約（書き方のルール）が優先される

## MVC
- Model: データ
- View: 見た目
- Controller: かけはし

## Model/

- validate（ページに設置したフォームから、DBに追加する値に対して）
- association（投稿記事と投稿コメントの紐付け）
- **ファイル名は単数形**

> ここではvalidateとassociationのみを設定しています

## View/
- 実際にページに表示させる情報、htmlタグを記述するテンプレート
- テンプレートは独自の拡張子（ctp）
- Controllerのaction名と名前を合わせる
- **ファイル名は単数形にする**

## Controller/
- View等からの要求の処理メソッド（action）を記述するphp
- action名はページURLにも使われる
- deleteはページを使用しないので、viewテンプレートは用意しない
- **ファイル名は複数形にする**（action名は単数形）




# 作成するアプリの概要
- ブログのような記事投稿アプリ
- 記事の投稿・一覧・編集・削除が可能
- 記事にはそれぞれコメントの投稿・削除が可能
- 記事はタイトルと本文が存在
- コメントはコメンターと本文が存在




# 主に使用するファイルの構成

```
app─┬─Config
    │ ├─database.php: 作成するDB情報を記載（ユーザ/PW/DB名）
    │ ├─core.php    : ページ下部に表示されるデバッグ情報の制御
    │ ├─routes.php  : URLのリダイレクト設定
    │
    ├─Model
    │ ├─Post.php   : 投稿のvalidateとCommentとの紐付け
    │ ├─Comment.php: Postに帰属するという指定
    │
    ├─View┬─Layouts
    │     │  ├─default.ctp: headerなど共通で読み込まれるテンプレート
    │     │
    │     ├Posts
    │        ├─index.ctp: 一覧ページ
    │        ├─view.ctp : 記事詳細ページ
    │        ├─add.ctp  : 記事編集ページ
    │        ├─edit.ctp : 記事追加ページ
    │
    ├─Controller
    │ ├─PostsController.php   : 主にView/Posts/xxx.ctpなどで使用するメソッドの定義
    │ ├─CommentsController.php: 同上 ※コメントはPosts/xxx.ctp内で表示する
    │
    ├─webroot: cssやjsなど静的ファイルを格納（今回は触れない）
    │
    ├─tmp: 書き込み権限を付与する
```

> - 今回取り上げていないファイル、ディレクトリは省略しています
> - 横の説明文は、ドットインストールで触れたことについての説明です




# 事前準備
## tmpフォルダの所有者を変える

`$ sudo chown -R apache blog/app/tmp/`

> - apacheを使う場合
> - またvagrantのrsyncを使う場合は、rsync__chown: falseを設定しておく

## 使用するDBの用意
- mySQLでDBを作成
- mySQLでユーザを作成し、作成したDBの全権限を付与する
- Config内のdatabase.php.defaultをコピーして、database.phpを用意
- database.php内のdefaultを作成したDBのものと合わせる

> - login: ユーザ名（@より前）
> - password: パスワード
> - database: DB名




# postsテーブルの用意
記事のためのpostsテーブルを作成し、テストデータを格納する
以下をmySQLで実行する（use 作成したDB_NAME）

```mysql
create table posts(
	id int not null auto_increment primary key,
	title varchar(50),
	body text,
	created datetime default null,
	modified datetime default null
);
insert into posts (title, body, created, modified) values
('title 1', 'body 1', now(), now()),
('title 2', 'body 2', now(), now()),
('title 3', 'body 3', now(), now());
```

> テーブルとデータが作成できたか確認
> `mysql > desc posts;`
> `mysql > select * from posts;`




# Model/, Controller/にひな形を用意する
**Modelは単数形、Controllerは複数形の命名に注意する**

- Model/Post.phpを作成
- Controller/PostsController.phpを作成

```php:Post.php
class Post extends AppModel {}
```
```php:PostsController.php
class PostsController extends AppController {}
```

> 以降も含め、<?php ?>は省略しています




# find()の使い方
例えば、配列を定義して引数に設定することで表示方法が変更できる

```php
// index()内
$params = array(
	'order' => 'modified desc',
	'limit' => 2
);
$this->set('posts', $this->Post->find('all', $params));
```




# 主な開発の流れ
- Controllerにアクションを作成
- View/に対応するアクション名の.ctpテンプレートファイルを作成
- View/テンプレートでアクションを呼び出すなどの処理を行う




# 記事の一覧ページを作成
## index()アクションを作成

```php:PostsController.php
class PostsController extends AppController {
	// helper関数の有効化
	public $helpers = array('Html', 'Form');
	public function index() {
		// ->set()で、viewに渡した時に、postsという変数名を使うことができる
		// 変数の中身をPost->find('all')で指定できる。※ここでは記事を全て引っ張ってくる
		$this->set('posts', $this->Post->find('all'));
	}
}
```

## Viewテンプレートを作成
```html+php:index.ctp
<ul>
	<?php foreach($posts as $post) : ?>
	<li>
		<?php
			// hはcakePHPが用意しているエスケープをするための命令（の省略形）
			echo h($post['Post']['body']);
		?>
	</li>
	<?php endforeach; ?>
</ul>
```




# 個別記事ページを作成
- /blog/posts/view/(id)
- Controllerの名前/action名がURLになる

## view()アクションを作成

```php:PostsController.php
public function view($id = null) {
	$this->Post->id = $id;
	$this->set('post', $this->Post->read());
}
```

## Viewテンプレートを作成

```html+php:view.ctp
<h2><?php echo h($post['Post']['title']); ?></h2>
<p><?php echo h($post['Post']['body']); ?></p>
```

## 一覧ページにリンクを貼る
- helper関数を有効化しておく必要がある
- link()内のテキストはエスケープ命令hは不要

```index.ctp
echo $this->Html->link($post['Post']['title'], '/posts/view/'.$post['Post']['id']);
```




# 記事の追加ページを作成
## add()アクションを作成

```php:PostsController.php
public function add() {
	if($this->request->is('post')) {
		if ($this->Post->save($this->request->data)) {
			$this->Session->setFlash('Success!');
			$this->redirect(array('action'=>'index'));
		} else {
			$this->Session->setFlash('failed!');
		}
	}
}
```

## Viewテンプレートを作成

```add.ctp
echo $this->Form->create('Post');
echo $this->Form->input('title');
echo $this->Form->input('body', array('rows'=>3));  // 第二引数にhtmlの属性を入れることも
echo $this->Form->end('Save Post');
```

## 一覧ページにリンクを貼る

```index.ctp
// arrayでcontroller名、action名を指定することもできる
echo $this->Html->link('Add post', array('controller'=>'posts', 'action'=>'add'));
```

## バリデーション
Modelにvalidationを設定する

```php:Model/Post.php
class Post extends AppModel {
	public $validate = array(
		'title' => array(
			'rule' => 'notEmpty',
			'message' => '空はダメ'
		),
		'body' => array(
			'rule' => 'notEmpty'
		)
	);
}
```




# 記事の編集ページを作成
## edit()アクションを作成

```php:PostsController.php
public function edit($id = null) {
	$this->Post->id = $id;
	if($this->request->is('get')) {
		$this->request->data = $this->Post->read();
	} else {
		if($this->Post->save($this->request->data)) {
			$this->Session->setFlash('success!');
			$this->redirect(array('action'=>'index'));
		} else {
			$this->Session->setFlash('failed!');
		}
	}
}
```

## Viewテンプレートを作成

```edit.ctp
echo $this->Form->create('Post', array('action'=>'edit'));
echo $this->Form->input('title');
echo $this->Form->input('body', array('row'=>3));
echo $this->Form->end('Save!');
```

## 一覧ページにリンクを貼る

```index.ctp
echo $this->Html->link('編集', array('action'=>'edit', $post['Post']['id']));
```




# 記事の削除
## delete()アクションを作成

```php:PostsController.php
public function delete($id) {
	// 直接URLでアクセスされた時の例外処理を入れる
	if ($this->request->is('get')) {
		throw new MethodNotAllowedException();
	}
	if ($this->Post->delete($id)) {
		$this->Session->setFlash('Deleted!');
		$this->redirect(array('action'=>'index'));
	}
}
```

## 一覧ページにリンクを貼る
専用のviewテンプレート.ctpは使用しない
隠しフォームを送るようなイメージ

```index.ctp
echo $this->Form->postLink('削除', array('action'=>'delete', $post['Post']['id']),array('confirm'=>'sure?'));
```




# 削除処理をAjax化
## delete()アクションを変更する
```php:PostsController.php
// ajax
if($this->request->is('ajax')) {
	if($this->Post->delete($id)) {
		$this->autoRender = false;
		$this->autoLayout = false;
		$response = array('id' => $id);
		$this->header('Content-Type: application/json');
		echo json_encode($response);
		exit();
	}
	$this->redirect(array('action'=>'index'));
}
```

## Formからajax処理に変更
```html+php:index.ctp
<li id="post_<?php echo h($post['Post']['id']); ?>">
<!-- ...省略 -->
<?php
	echo $this->Html->link('削除', '#', array('class'=>'delete', 'data-post-id'=>$post['Post']['id']));
?>
<!-- ...省略 -->
<script>
$(function() {
	$('a.delete').click(function(e){
		if (confirm('sure?')) {
			$.post('/blog/posts/delete/' + $(this).data('post-id'), {}, function(res) {
				$('#post_' + res.id).fadeOut();
			}, 'json');
		}
		return false;
	});
});
</script>
```




# コメント機能の実装
- 基本的な流れはPostと同じような感じ
- 記事詳細ページにコメント一覧を表示
- 記事詳細ページにコメント追加フォームを表示
- 記事詳細ページにコメント削除を表示

## 実装の流れ
- DBにpostsと同じようにcommentsテーブルを作成
- Model/Comment.phpを作成
- Model/Post.phpとModel/Comment.phpにアソシエーションを設定
- Controller/CommentsController.phpを作成

> 専用ページは用意しない（新しいviewテンプレートは用意しない）

## commentsテーブルを作成
postsテーブル作成と同じ流れ

```mysql
create table comments(
	id int not null auto_increment primary key,
	commenter varchar(255),
	body text,
	post_id int not null,
	created datetime default null,
	modified datetime default null
);
insert into comments(commenter, body, post_id, created, modified) values
('c1', 'b1', 3, now(), now()),
('c2', 'b2', 3, now(), now()),
('c3', 'b3', 3, now(), now());
```

## アソシエーションの設定
アソシエーションを設定することでPostを引っ張ってくると関連するコメントも取得できる

```php:Post.php
public $hasMany = "Comment";
```
```php:Comment.php
class Comment extends AppModel {
	// 全てのコメントはPostに帰属するという指定
	public $belongsTo = 'Post';
}
```




# コメント一覧を表示
PostsControllerにはpostしかないがアソシエーションしているため、postでコメントも取得できる

```view.ctp
<?php foreach ($post['Comment'] as $comment): ?>
	<li><?php echo h($comment['body']); ?> by <?php echo h($comment['commenter']); ?></li>
<?php endforeach; ?>
```




# コメントフォームを表示
## CommentsControllerを作成し、add()アクションを編集

> - PostsController.phpをそのまま利用
> - index(), view(), edit()は不要
> - class名とadd()を一部変更

```php:CommentsController.php
// $this->Commentに変えるのも忘れずに
// add()内
$this->redirect(array('controller'=>'posts', 'action'=>'view', $this->data['Comment']['post_id']));
```

## コメントフォームの生成

```view.ctp
echo $this->Form->create('Comment', array('action'=>'add'));
echo $this->Form->input('commenter');
echo $this->Form->input('body', array('rows'=>3));
echo $this->Form->input('Comment.post_id', array('type'=>'hidden', 'value'=>$post['Post']['id']));
echo $this->Form->end('post comment');
```




# コメントの削除を表示
## delete()アクションを編集

```php:CommentsController.php
// $this->Commentに変えるのも忘れずに
// delete()内
$this->redirect(array('controller'=>'posts', 'action'=>'index'));
```

## 削除リンクとjsを追加する
※記事の削除とほぼ同じ（postをcommentに変更するなど）なのでコードは省略します




# おわりに
ドットインストール自体がギュッとまとまっているので、思ったより絞りきれませんでした。。。間違っている点や、何かまずいことがあればご指摘くださいませ。