FuelPHPでToDoアプリを作る

FuelPHPでToDoアプリを作る方法を紹介します。
今回作るのはこんな感じのやつです。⬇︎
![qiiita.gif](https://qiita-image-store.s3.amazonaws.com/0/320755/375cab1e-1bf6-e270-813d-44f9e7a65b0a.gif)


<br>


まずは準備から

データベースはMAMPを使います。
MAMPのインストールは<a href="https://www.mamp.info/en/">こちら</a>

<br>

はじめにFuelphpのクイックインストーラーをダウンロードしましょう。

```
$ curl https://get.fuelphp.com/oil | sh
```

このコマンドでインストールできます。

インストールできたら、MAMP下のhtdocsで↓のコマンドを打って「todo」というfuelPHP用のディレクトリを作ります。

```
$ oil　create todo
```

これで下準備は完成です。

次はデータベースの設定です。

todosというデータベースを作って、その中にtodosというデーブルを作ります。（名前一緒ですみません）。
構造はid,note,createdです。

```sql
CREATE TABLE `todos` (
  `id` int(11) NOT NULL,
  `note` varchar(140) NOT NULL,
  `created` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for dumped tables
--

--
-- Indexes for table `todos`
--
ALTER TABLE `todos`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `todos`
--
ALTER TABLE `todos`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
```

これでデータベースの設定は完了です。

次はFuelPHPの設定をします。
まずはデータベースへの接続の設定。
config/development/db.phpでデータベースを指定します。

```php:/htdocs/todo/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=todos',
			'username'   => 'root',
			'password'   => 'root',
		),
	),
);
```

dbnameをtodosに設定します。
<br>

次に、ORMを使うための設定。（ORMについては<a href="http://fuelphp.jp/docs/1.5/packages/orm/intro.html">こちら</a>）
config/config.phpの下の方にあるAlways Loadのところを変えます。

```php:/htdocs/todo/fuel/app/config/config.php
/**************************************************************************/
	/* Always Load                                                            */
	/**************************************************************************/
	'always_load'  => array(　　//この行をコメントアウトする

		/**
		 * These packages are loaded on Fuel's startup.
		 * You can specify them in the following manner:
		 *
		 * array('auth'); // This will assume the packages are in PKGPATH
		 *
		 * // Use this format to specify the path to the package explicitly
		 * array(
		 *     array('auth'	=> PKGPATH.'auth/')
		 * );
		 */
		'packages'  => array(　　//この行をコメントアウトする
			'orm',　　　　//この行をコメントアウトする
		),　　　//この行をコメントアウトする

		/**
		 * These modules are always loaded on Fuel's startup. You can specify them
		 * in the following manner:
		 *
		 * array('module_name');
		 *
		 * A path must be set in module_paths for this to work.
		 */
		// 'modules'  => array(),

		/**
		 * Classes to autoload & initialize even when not used
		 */
		// 'classes'  => array(),

		/**
		 * Configs to autoload
		 *
		 * Examples: if you want to load 'session' config into a group 'session' you only have to
		 * add 'session'. If you want to add it to another group (example: 'auth') you have to
		 * add it like 'session' => 'auth'.
		 * If you don't want the config in a group use null as groupname.
		 */
		// 'config'  => array(),

		/**
		 * Language files to autoload
		 *
		 * Examples: if you want to load 'validation' lang into a group 'validation' you only have to
		 * add 'validation'. If you want to add it to another group (example: 'forms') you have to
		 * add it like 'validation' => 'forms'.
		 * If you don't want the lang in a group use null as groupname.
		 */
		// 'language'  => array(),
	),　　　//この行をコメントアウトする

);
```

「この行をコメントアウトする」と書いてある行の//を消してコメントアウトしてください。

'always_load'  => array(

この部分とその閉じ括弧（一番下）と、

'packages'  => array(
            'orm',
        ),
この部分の//を消してコメントアウトします。
<br>
これでORMが使えるようになったので、classes/model下にtodo.phpを作ってデータベース接続用のモデルを作ります。


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

これでデータベース接続の設定が完了しました。
<br>

<br>


次にtodo作成機能を作っていきます。

まずはviewのindex.php(表示の部分)の全体を載せておきます。


```php:/htdocs/todo/fuel/app/views/todo/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>Todo</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <?php echo Asset::css('app.css'); ?>
</head>
<body>
    <div class="main">
    <?php $msg = Session::get_flash('slide-msg'); ?>
    <?php if(!empty($msg)){ ?>
    <p id="js-slide" class="slide-msg"><?php echo $msg ?></p>
    <?php } ?>
    <div class="todo">
        <p id="add-empty" class="slide-msg slide-msg-add">todoを記入してください！</p>
        <div class="add">
            <?php echo Form::open(array('action' => 'todo/add', 'method' => 'post')); ?>
            <?php echo Form::input('note','', array('type' => 'text', 'autocomplete' => 'off', 'placeholder' => 'ここにtodoを記入', 'class' => 'write')); ?>
            <?php echo Form::submit('post', '追加', array('type' => 'submit', 'class' => 'btn-add', 'id' => 'add')); ?>
            <?php echo Form::close(); ?>
        </div>
        <table class="list">
            <tbody>
                <?php foreach($todos as $todo): ?>
                <?php $todoId = $todo->id; ?>
                <tr class="item">
                <td class="container">
                    <?php echo Form::open(array('action' => "todo/delete/{$todoId}", 'method' => 'post')); ?>
                    <?php echo Form::submit('post', '削除', array('class' => 'btn-todo btn-delete')); ?>
                    <?php echo Form::close(); ?>
                </td>
                <td class="container char"><?php echo Security::htmlentities($todo->note); ?></td>
                <td class="container">
                    <?php echo Form::open(array('action' => "todo/done/{$todoId}", 'method' => 'post')); ?>
                    <?php echo Form::submit('post', '完了', array('class' => 'btn-todo btn-finish')); ?>
                    <?php echo Form::close(); ?>
                </td>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    </div>
<?php echo Asset::js('jquery.js'); ?>
<?php echo Asset::js('app.js'); ?>
</body>
</html>
```

head内でapp.cssを、bodyの一番下でjQueryのファイルとapp.jsを読み込んでいます。jQueryはcdnでもいいです。
jQuery→https://jquery.com/download/

この辺は最後に説明します。
<br>
controllerのtodo.phpもまずは全体像から

```php:/htdocs/todo/fuel/app/classes/controller/todo.php
<?php
class Controller_Todo extends Controller
{
    public function action_index()
    {
        $todos = Model_Todo::find('all');
        $todoVal['todos'] = $todos;

        return Response::forge(View::forge('todo/index', $todoVal));

    }

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

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

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

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

    }

    public function action_delete($todoId = null)
    {

        if ($todoId === null) {
            return Response::redirect('todo/index');
        }

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

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

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

    public function action_done($todoId = null)
    {

        if ($todoId === null) {
            return Response::redirect('todo/index');
        }

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

        }

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

        Session::set_flash('slide-msg','お疲れ様でした！');

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

todoを作成する部分を見ていきます。

```php:/htdocs/todo/fuel/app/views/todo/index.php
<div class="add">
  <?php echo Form::open(array('action' => 'todo/add', 'method' => 'post')); ?>
  <?php echo Form::input('note','', array('type' => 'text', 'autocomplete' => 'off', 'placeholder' => 'ここにtodoを記入', 'class' => 'write')); ?>
  <?php echo Form::submit('post', '追加', array('type' => 'submit', 'class' => 'btn-add', 'id' => 'add')); ?>
  <?php echo Form::close(); ?>
</div>
```

Formメソッドを使って、todoの入力フォームを作ります。
Form::openの引数、actionでコントローラーtodoのaddを指定しています。
<br>

これで、追加ボタンが押されたらcontroller/todo.php内のaddというアクションが実行されます。

そのclasses/controller/todo.phpのファイルを見ていきます。

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

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

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

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

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

    }

```

「Input::post（'note'）」で入力されたToDoの情報を取り出し、$noteに格納して、他のデータとともに保存しています。

<br>
次にToDoを表示する部分を見ていきます。

まずは、コントローラーでデータベースからToDoの内容を取ってくる処理をします。

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

public function action_index()
{
    $todos = Model_Todo::find('all');
    $todoVal['todos'] = $todos;

    return Response::forge(View::forge('todo/index', $todoVal));

}

```

$todoValにToDoの内容を格納して、View::forgeの第二引数にセットします。

それをViewのtodo/index.phpで表示します。↓

```php:/htdocs/todo/fuel/app/views/todo/index.php
<tbody>
     <?php foreach($todos as $todo): ?>
  <tr>
               
     <td class="container char"><?php echo Security::htmlentities($todo->note); ?></td>
               
  </tr>
     <?php endforeach; ?>
</tbody>
```

Security::htmlentitiesはXSS対策です。（XSSについては<a href="https://viral-community.com/blog/xss-1835/">こちら</a>）
<br>

次に削除機能です。
まず、index.phpで削除ボタンを作ります。

```php:/htdocs/todo/fuel/app/views/todo/index.php
<td class="container">
   <?php $todoId = $todo->id; ?>
   <?php echo Form::open(array('action' => "todo/delete/{$todoId}", 'method' => 'post')); ?>
   <?php echo Form::submit('post', '削除', array('class' => 'btn-todo btn-delete')); ?>
   <?php echo Form::close(); ?>
</td>
```

Form::openのactionに、コントローラーtodoのdeleteを指定して、表示しているToDoのidをくっつけます。

コントローラーでそのidのついているものを削除します。

```php:/htdocs/todo/fuel/app/classes/controller/todo.php
public function action_delete($todoId = null)
    {

        if ($todoId === null) {
            return Response::redirect('todo/index');
        }

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


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

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

次に、完了ボタンの機能を作っていきます。
まずはindex.phpに完了ボタンを設置して、削除と同じようにForm::openのactionに、コントローラーtodoのdoneを指定して、表示しているToDoのidをくっつけます。

```php:/htdocs/todo/fuel/app/views/todo/index.php
<td class="container">
   <?php echo Form::open(array('action' => "todo/done/{$todoId}", 'method' => 'post')); ?>
   <?php echo Form::submit('post', '完了', array('class' => 'btn-todo btn-finish')); ?>
   <?php echo Form::close(); ?>
</td>
```

コントローラーdeleteと同じように削除処理をします。ただ、完了ボタン（done）の場合は、Session::set_flashを使ってindex.phpの画面の上の方にメッセージが表示されるようにします。

```php:/htdocs/todo/fuel/app/classes/controller/todo.php
public function action_done($todoId = null)
    {

        if ($todoId === null) {
            return Response::redirect('todo/index');
        }

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

        }

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

        Session::set_flash('slide-msg','お疲れ様でした！');

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

set_flashでメッセージをつけて、それをindex.php内で取り出して表示します。

```php:/htdocs/todo/fuel/app/views/todo/index.php
<?php $msg = Session::get_flash('slide-msg'); ?>
<?php if(!empty($msg)){ ?>
<p id="js-slide" class="slide-msg"><?php echo $msg ?></p>
<?php } ?>
```

メッセージ表示はjQueryで行なっています。

```js:/htdocs/todo/public/assets/js/app.js
$(function(){
    $('#add').on('click', function(e) {
        if($('.write').val() == 0){
        e.preventDefault();
        let $slide = $('#add-empty');
        $slide.slideToggle(300);
        setTimeout(function(){$slide.slideToggle('fast'); }, 1500);
        }
    });
    var $toggleMsg = $('#js-slide');
    if($toggleMsg.length){
        $toggleMsg.slideDown(500);
        setTimeout(function(){ $toggleMsg.slideUp(); },2000);
    }
});
```

また、ToDoが空の状態で追加ボタンが押された場合は、送信をストップして、「todoを記入してください！」というメッセージが表示されるようにしています。

```php:/htdocs/todo/fuel/app/views/todo/index.php
<p id="add-empty" class="slide-msg slide-msg-add">todoを記入してください！</p>
```

これでtodoアプリの機能の作成は完了です。

cssはこんな感じ↓ですが、細かい説明は省きます。

```css:/htdocs/todo/public/assets/css/app.css
*, *::after, *::before{
    box-sizing: border-box;
}
input::placeholder{
    color: rgb(172, 167, 167);
}
input[type="submit"],
a{
    outline: none;
}
input[type="text"]:focus{
    outline: 3px solid rgba(145, 64, 170, 0.4);
}
body{
    font-size: 62.5%;
    padding: 0;
    margin: 0;
}

/* ------------------------------------
* todo
+ ---------------------------------- */
.main{
    position: relative;
    padding-top: 20vh;
}
.add{
    font-size: 0;
}
.add input::placeholder{
    font-size: 1.2rem;
}
.write{
    display: block;
    height: 40px;
    width: 100%;
    font-size: 1.2rem;
    padding: 5px;
    border-radius: 5px;
}
.btn-add{
    display: block;
    margin: 0 auto;
    margin-top: 20px;
    font-weight: bold;
    font-size: 0.8rem;
    height: 30px;
    background:rgba(30, 30, 31, 0.8);
    width: 80px;
    color: #fff;
    border-radius: 5px;
    outline: none;
}
.btn-add:hover{
    opacity: 0.7;
    cursor: pointer;
}
.list{
    margin-top: 40px;
    width: 100%;
    margin-bottom: 20px;
}
.item{
    display: block;
    height: 40px;
    margin-bottom: 20px;
    line-height: 40px;
}
.char{
    border: none;
    width: 100%;
    font-size: 1rem;
    text-align: center;
    border-radius: 2px;
    box-shadow: 1px 1px 2px rgba(99, 97, 97, 0.4) inset;
}
.btn-todo{
    font-size: 0.7rem;
    height: 40px;
    width: 40px;
    font-weight: bold;
    box-shadow: 0 0 5px rgba(99, 97, 97, 0.4) inset;
    border-radius: 50%;
    background: #fff;
}
.btn-todo:hover{
    opacity: 0.7;
    cursor: pointer;
}
.btn:hover{
    opacity: 0.7;
    cursor: pointer;
}
.btn-finish{
    color: rgba(137, 74, 209, 0.6);
}
.btn-delete{
    color: rgba(233, 60, 60, 0.4);
}
.slide-msg{
    display: none;
    box-shadow: 0 0 30px rgba(145, 64, 170, 0.2) inset;
    border-radius: 2px;
    font-size: 1.1rem;
    text-align: center;
    margin: 0;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 40px;
    line-height: 40px;
    color: rgb(90, 86, 86);
}
.todo{
    margin: 0 auto;
    width: 30%;
    position: relative;
}
.slide-msg-add{
    background: rgb(229, 242, 245);
    top: 0;
    border-radius: 5px;
    box-shadow: none;
}
```

ひとつひとつのメソッドなどの詳細は公式のリファレンスで確認してください。
公式リファレンス→http://fuelphp.jp/docs/1.5/index.html