FuelPHP で TODO アプリケーションを作ってみた。


こんにちは。 FuelPHP 2015/12/03 を担当させて頂きます @ken880guchi です。

今回は、前回作成した FuelPHP の環境を元に、シンプルな TODO アプリケーションを作成してみたので、その手順を纏めてみました。

## Step 0: 事前準備

MySQL をインストールされていない場合は先に MySQL のインストールを行って下さい。

## Step 1: データベースの作成

はじめに、今回の Todo アプリケーションの作成に必要なデータベース。及びテーブルとテストデータの生成を行います。

```sh
# mysql に接続
mysql -u root -p 
```
 
```sql
# データベースの作成
CREATE SCHEMA `todo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;

# todo データベースを使用
use todo

# テーブルの作成
CREATE TABLE `todos` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `note` varchar(140) NOT NULL,
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

# テストデータの投入
INSERT INTO todos (`content`, `created`) VALUES ('テスト1', '2015-12-03');
INSERT INTO todos (`content`, `created`) VALUES ('テスト2', '2015-12-04');
INSERT INTO todos (`content`, `created`) VALUES ('テスト3', '2015-12-05');
```

## Step 2: プロジェクトの作成と動作確認

次に、プロジェクトを oil コマンドから作成し動作を確認しましょう。

```sh
# TODO アプリケーションの為のプロジェクトを作成
oil create todo

# 作成したプロジェクトに移動
cd todo

# ブラウザから http://localhost:8000 にアクセスし、動作を確認)
oil server

Listening on http://localhost:8000
Document root is public
Press Ctrl-C to quit.
```
 
## Step 3: 表示機能の作成

はじめに、データベースへ事前登録している 3 つのレコードをページ上に表示する機能を作成します。

### Step 3-1: Model の作成

はじめに、アプリケーションから Todo データベースへ接続するための設定を追加します。 password の箇所は自身で設定しているものを設定してください。

```sh
# 設定ファイルを開く
fuel/app/config/development/db.php
```

```
<?php
/**
 * The development database settings. These get merged with the global settings.
 */

return array(
	'default' => array(
		'connection'  => array(
			'dsn'        => 'mysql:host=localhost;dbname=todo',
			'username'   => 'root',
			'password'   => 'root',
		),
	),
);

```

次に、データベースへの問い合わせを簡略化するためのモジュール `ORM` を常に読みこむように設定ファイルを編集します。

```sh
# 設定ファイルを開く
vim fuel/app/config/config.php
```

```fuel/app/config/config.php
# 常に読み込むパッケージに orm を設定
'always_load' => array(
    'packages' => array(
        'orm',
    ),
),
```

`fuel/app/classes/model/` に todo.php という todos テーブルへの問い合わせ用モデルクラスを作成します。

```sh
# Todo テーブルへアクセスするモデルを作成
vim fuel/app/classes/model/todo.php
```

```fuel/app/classes/model/todo.php
<?php
class Model_Todo extends Orm\Model {
  # テーブルに定義したすべての列を記述
  protected static $_properties = array('id', 'note', 'created', 'updated');
}
```

### Step 3-2: Controller の作成

`fuel/app/classes/controller/` に、 http://localhost:8000/todo にアクセスされた時に行なう処理を記述します。

```sh
# Todo controller を作成
$ vim fuel/app/classes/controller/todo.php
```

```fuel/app/classes/controller/todo.php
<?php
/**
 * The Todo Controller.
 *
 * @package  app
 * @extends  Controller
 */
// Class 名の先頭には必ず Controller_ を付与すること
class Controller_Todo extends Controller
{
    /**
     * @access  public
     * @return  Response
     */
    /*
     * 利用者からアクセスのある function 名の先頭には
     * 必ず action_ を付与すること
     * 
     * scope は public であること
     */ 
    public function action_index()
    {
        // todos テーブルに格納した 3 つのテストデータを取得
        $todos = Model_Todo::find('all');
        $valuesOfView['todos'] = $todos;

        return Response::forge(View::forge('todo/index', $valuesOfView));
    }
}
```

### Step 3-3: View の作成

`fuel/app/views/` に todo ディレクトリと、作成した todo ディレクトリ内に `index.php` を作成します。

```sh
# todo ディレクトリを作成
$ mkdir fuel/app/views/todo

# action_index にアクセスされた時に表示する view ファイルを作成
$ vim fuel/app/views/todo/index.php
```

```	fuel/app/views/todo/index.php
<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Index</title>
</head>
<body>
	<table>
		<thead>
			<tr>
				<th>id</th>
				<th>note</th>
				<th>created</th>
				<th>updated</th>
			</tr>
		</thead>
		<tbody>
		    <?php // Controller から渡された変数の表示 ?>
			<?php foreach($todos as $todo): ?>
			<tr>
				<td><?php echo $todo->id;      ?></td>
				<td><?php echo $todo->note;    ?></td>
				<td><?php echo $todo->created; ?></td>
				<td><?php echo $todo->updated; ?></td>
			</tr>
			<?php endforeach; ?>
		</tbody>
	</table>
</body>
</html>
```

### Step 3-4: 動作確認

http://localhost:8000/todo へアクセスしてみてください。
凄くダサい見た目だとは思いますが、事前に格納していた 3 つのレコードの値が表示されるはずです。
表示されない場合、下記を順に確認してみてください。

- 作成したファイル名が誤っていないか
- 編集したファイル内の記述が誤っていないか
- `oil server` コマンドを実行し、サーバーを起動しているか

また、ここから先は随時動作確認をはさみながら進まれることを推奨します。

## Step 4: 登録機能の追加

登録機能を追加します。

### Step 4-1: Controller の編集
	
下記のように action_add function を追加します。created と updated の箇所はもっとスマートに記述することが出来そうなのでご存知のかたが入れば教えて頂きたいです。

```fuel/app/classes/controller/todo.php
<?php
/**
 * The Todo Controller.
 *
 * @package  app
 * @extends  Controller
 */
class Controller_Todo extends Controller
{
	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_index()
	{
		$todos = Model_Todo::find('all');

		$valuesOfView['todos'] = $todos;
		return Response::forge(View::forge('todo/index', $valuesOfView));
	}

	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_add()
	{
		if (Input::method() === 'GET') {
			return Response::forge(View::forge('todo/add'));
		}

		$note= Input::post('note');

		$todo = Model_Todo::forge();
		$todo->note = $note;
		$todo->created = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->updated = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->save();

		return Response::redirect('todo/index');
	}
}
```

### Step 4-2: View の編集

Todo 表示用 View に Todo 追加用リンクを追加します。

```fuel/app/views/todo/index.php
<?php
	$addTodoLink = html_tag('a', array(
		'href' => Uri::create('todo/add'),
	), 'addTodo');
?>

<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Index</title>
</head>
<body>
	<?php echo $addTodoLink; ?>
	<table>
		<thead>
			<tr>
				<th>id</th>
				<th>note</th>
				<th>created</th>
				<th>updated</th>
			</tr>
		</thead>
		<tbody>
			<?php foreach($todos as $todo): ?>
			<tr>
				<td><?php echo $todo->id;      ?></td>
				<td><?php echo $todo->note;    ?></td>
				<td><?php echo $todo->created; ?></td>
				<td><?php echo $todo->updated; ?></td>
			</tr>
			<?php endforeach; ?>
		</tbody>
	</table>
</body>
</html>
```

### Step 4-3: View の追加

登録用 View を追加します。

```fuel/app/views/todo/add.php
<?php
	$showTodoLink = html_tag('a', array(
		'href' => Uri::create('todo'),
	), 'showTodo');
?>

<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Add</title>
</head>
<body>
	<?php echo $showTodoLink; ?>
	<?php echo Form::open(array('action' => 'todo/add', 'method' => 'post')); ?>
	<div>
		<label>note</label>
		<?php echo Form::input('note', 'default_value'); ?>
	</div>
	<?php echo Form::submit('post'); ?>
	<?php echo Form::close(); ?>
</body>
</html>
```

## Step 5: 更新機能の追加

つぎに Todo の更新機能を追加します。

### Step 5-1: Controller の編集

下記のように function action_update を追記しました。URL から *todoId* を取得したあと、該当する todo のオブジェクトを取得します。最後に、登録時同様に save() を実行することで 更新処理を実現しています。

```fuel/app/classes/controller/todo.php
<?php
/**
 * The Todo Controller.
 *
 * @package  app
 * @extends  Controller
 */
class Controller_Todo extends Controller
{
	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_index()
	{
		$todos = Model_Todo::find('all');

		$valuesOfView['todos'] = $todos;
		return Response::forge(View::forge('todo/index', $valuesOfView));
	}

	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_add()
	{
		if (Input::method() === 'GET') {
			return Response::forge(View::forge('todo/add'));
		}

		$note= Input::post('note');

		$todo = Model_Todo::forge();
		$todo->note = $note;
		$todo->created = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->updated = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->save();

		return Response::redirect('todo/index');
	}

	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_update($todoId = null)
	{
		if ($todoId === null) {
			return Response::redirect('todo/index');
		}

		if (Input::method() === 'GET') {
			$valuesOfView['todoId'] = $todoId;
			return Response::forge(View::forge('todo/update', $valuesOfView));
		}

		$note= Input::post('note');

		$todo = Model_Todo::find($todoId);
		$todo->note = $note;
		$todo->updated = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->save();

		return Response::redirect('todo/index');
	}

}
```

### Step 5-2: View の編集

Todo 表示用 View に Todo 更新用リンクを追加します。

```fuel/app/views/todo/index.php
<?php
	$addTodoLink = html_tag('a', array(
		'href' => Uri::create('todo/add'),
	), 'addTodo');

?>

<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Index</title>
</head>
<body>
	<?php echo $addTodoLink; ?>
	<table>
		<thead>
			<tr>
				<th>id</th>
				<th>note</th>
				<th>created</th>
				<th>updated</th>
				<th></th>
			</tr>
		</thead>
		<tbody>
			<?php foreach($todos as $todo): ?>
			<tr>
				<td><?php echo $todo->id;      ?></td>
				<td><?php echo $todo->note;    ?></td>
				<td><?php echo $todo->created; ?></td>
				<td><?php echo $todo->updated; ?></td>
				<td>
					<?php
						echo html_tag('a', array(
							'href' => Uri::create("todo/update/{$todo->id}"),
						), 'update');
					?>
				</td>
			</tr>
			<?php endforeach; ?>
		</tbody>
	</table>
</body>
</html>
```

### Step 5-3: View の追加

更新用 View を追加します。ページ内の Form を生成する際に action の url 末尾に todoId をパラメーターとして付与しており、この値をキーにして action_update にて該当する Todo を更新します。

```php
// fuel/app/views/todo/update.php

<?php
	$showTodoLink = html_tag('a', array(
		'href' => Uri::create('todo'),
	), 'showTodo');
?>

<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Update</title>
</head>
<body>
	<?php echo $showTodoLink; ?>
	<?php echo Form::open(array('action' => "todo/update/{$todoId}", 'method' => 'post')); ?>
	<div>
		<label>note</label>
		<?php echo Form::input('note', 'default_value'); ?>
	</div>
	<?php echo Form::submit('post'); ?>
	<?php echo Form::close(); ?>
</body>
</html>
```

## Step 6: 削除機能の追加

最後に　Todo の削除機能を追加します。

### Step 6-1: Controller の編集

下記のように function action_delete を追記しました。更新処理の時同様に URL から *todoId* を取得したあと、該当する todo のオブジェクトを取得します。最後に、取得したオブジェクトの delete() を実行することで 削除処理を実現しています。

```php
// fuel/app/classes/controller/todo.php

<?php
/**
 * The Todo Controller.
 *
 * @package  app
 * @extends  Controller
 */
class Controller_Todo extends Controller
{
	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_index()
	{
		$todos = Model_Todo::find('all');

		$valuesOfView['todos'] = $todos;
		return Response::forge(View::forge('todo/index', $valuesOfView));
	}

	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_add()
	{
		if (Input::method() === 'GET') {
			return Response::forge(View::forge('todo/add'));
		}

		$note= Input::post('note');

		$todo = Model_Todo::forge();
		$todo->note = $note;
		$todo->created = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->updated = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->save();

		return Response::redirect('todo/index');
	}

	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_update($todoId = null)
	{
		if ($todoId === null) {
			return Response::redirect('todo/index');
		}

		if (Input::method() === 'GET') {
			$valuesOfView['todoId'] = $todoId;
			return Response::forge(View::forge('todo/update', $valuesOfView));
		}

		$note= Input::post('note');

		$todo = Model_Todo::find($todoId);
		$todo->note = $note;
		$todo->updated = Date::forge()->format("%Y-%m-%d %H:%M:%S");
		$todo->save();

		return Response::redirect('todo/index');
	}

	/**
	 * @access  public
	 * @return  Response
	 */
	public function action_delete($todoId = null)
	{
		if ($todoId === null) {
			return Response::redirect('todo/index');
		}

		if (Input::method() === 'GET') {
			$valuesOfView['todoId'] = $todoId;
			return Response::forge(View::forge('todo/delete', $valuesOfView));
		}

		$note= Input::post('note');

		$todo = Model_Todo::find($todoId);
		$todo->delete();

		return Response::redirect('todo/index');
	}
}
```

### Step 6-2: View の編集

Todo 表示用 View に Todo 削除用リンクを追加します。

```php
// fuel/app/views/todo/index.php

<?php
	$addTodoLink = html_tag('a', array(
		'href' => Uri::create('todo/add'),
	), 'addTodo');
?>

<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Index</title>
</head>
<body>
	<?php echo $addTodoLink; ?>
	<table>
		<thead>
			<tr>
				<th>id</th>
				<th>note</th>
				<th>created</th>
				<th>updated</th>
				<th></th>
				<th></th>
			</tr>
		</thead>
		<tbody>
			<?php foreach($todos as $todo): ?>
			<tr>
				<td><?php echo $todo->id;      ?></td>
				<td><?php echo $todo->note;    ?></td>
				<td><?php echo $todo->created; ?></td>
				<td><?php echo $todo->updated; ?></td>
				<td>
					<?php
						echo html_tag('a', array(
							'href' => Uri::create("todo/update/{$todo->id}"),
						), 'update');
					?>
				</td>
				<td>
					<?php
						echo html_tag('a', array(
							'href' => Uri::create("todo/delete/{$todo->id}"),
						), 'delete');
					?>
				</td>
			</tr>
			<?php endforeach; ?>
		</tbody>
	</table>
</body>
</html>
```

### Step 6-3: View の追加

削除ページ用の View を追加します。更新処理の時同様なので説明は省略します。

```php
// fuel/app/views/todo/delete.php

<?php
	$showTodoLink = html_tag('a', array(
		'href' => Uri::create('todo'),
	), 'showTodo');
?>

<!DOCTYPE html>
<html>
<head>
	<title>Todo Application - Delete</title>
</head>
<body>
	<?php echo $showTodoLink; ?>
	<?php echo Form::open(array('action' => "todo/delete/{$todoId}", 'method' => 'post')); ?>
	<?php echo Form::submit('post'); ?>
	<?php echo Form::close(); ?>
</body>
</html>
```

## おわりに

FuelPHP の基礎について軽くですが学ぶことが出来たと思います。実際の運用ではバリデーションやセキュリティ処理などが加わってくるため、もう少し大変な作業になるかと思います。今後、さらに FuelPHP を触ってその辺りについてもかけると良いなと思っています。