symfonyでTODOアプリを作る

# はじめに
現場が2020年5月末で終りを迎えた。で、次の現場で使われてるフレームワークがsymfonyって言われたんだけど、、、
おおん？日本語の資料がめっちゃすくねぇー！

# また英語なの！？
文句言ってるヒマはねぇや、やるしかねぇぇぇ
https://www.udemy.com/share/101WEaAkQadFdURHg=/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/c7c299e2-0015-16ed-9c9b-b8ad2965bf7e.png)

# TODOアプリ
Project\Php は僕が作ったフォルダで、ここでphpの作品を取り扱うことにしている。当然最初は空っぽであるという前提で良い。

## スケルトン作成
djangoでいう、startproject の後の状態ね

```terminal:Project\Php（symfony4-crudというアプリ名）
Php> composer create-project symfony/skeleton symfony4-crud "5.1.*"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/2282f255-be7e-0807-8de4-aa0752cc7809.png)

```terminal:Console
Php> cd symfony4-crud
symfony4-crud> composer require symfony/requirements-checker
symfony4-crud> php -S 127.0.0.1:8000 -t public
```

## 確認（localhostにアクセス）
http://localhost:8000
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/492384bd-01be-e746-9d44-a2a74061d86d.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

## 確認（checkにアクセス）
http://localhost:8000/check.php
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/1dc87a37-c94f-5885-80e1-91710ceb5b5c.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

### PHP accelerato
PHP入れただけの状態でほんとの一発目にアプリを作ろうとしてcheck.phpを見てみると、
1. PHP accelerator を入れろ！
2. php.ini のキャッシュサイズを5M以上にしろ！
などと書いてある
https://www.php.net/manual/ja/opcache.installation.php#opcache.installation.bundled
>Windows の場合は zend_extension=C:\path\to\php_opcache.dll を使います。

```terminal:php.ini（変更前）
;opcache.enable=1
;opcache.enable_cli=0
```
```terminal:php.ini（変更後）
opcache.enable=1
opcache.enable_cli=1
```
```terminal:php.ini（最終行に追記）
zend_extension=C:\php\ext\php_opcache.dll
```

### キャッシュサイズを5M以上に
```terminal:php.ini（変更前）
;realpath_cache_size = 4096k
```
```terminal:php.ini（変更後）
realpath_cache_size = 5M
```

### 確認
```terminal（zendナントカとopcacheって情報が付与されるようになった）
> php -v
  PHP 7.4.6 (cli) (built: May 12 2020 11:38:54) ( ZTS Visual C++ 2017 x64 )
  Copyright (c) The PHP Group
  Zend Engine v3.4.0, Copyright (c) Zend Technologies
      with Zend OPcache v7.4.6, Copyright (c), by Zend Technologies
```

## vhost
```
<VirtualHost *:80>
  ServerName symfony.henojiya.net
  DocumentRoot "/var/www/symfony"
</VirtualHost>
```

## 材料
材料支給された「静的ファイル」はこちら。publicフォルダにつっこむ。デザインなど、ラクできるところはラクしましょ。動くものを作るのが目的ですから。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/f6ffb81d-bd71-d44d-9cb4-f1c4c5176dee.png)

```html:symfony4-crud-template.html
<!--
  source: https://www.w3schools.com/howto/howto_js_todolist.asp  (modified by netprogs.pl for the course purposes)
-->
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="./css/styles.css">
  </head>
  <body>
    <div class="header">
      <h2>My To Do List</h2>
      <form action="">
        <input type="text" id="myInput" placeholder="Task title..." />
        <button type="submit" class="addBtn">Add a task</button>
      </form>
    </div>

    <ul>
      <li>
        <a href="done"> <span class="task-list-item">Do shopping </span> </a
        ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
      </li>
      <li class="checked">
        <a href="undone"> <span class="task-list-item">Buy eggs </span> </a
        ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
      </li>
      <li>
        <a href="done"> <span class="task-list-item">Meet John </span> </a
        ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
      </li>
      <li>
        <a href="done"> <span class="task-list-item">Read a book </span> </a
        ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
      </li>
      <li>
        <a href="done"> <span class="task-list-item">Learn Doctrine ORM </span> </a
        ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
      </li>
      <li>
        <a href="done"> <span class="task-list-item">Prepare a meal </span> </a
        ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
      </li>
    </ul>
  </body>
</html>
```
```css:styles.css
/* source: https://www.w3schools.com/howto/howto_js_todolist.asp  (modified by netprogs.pl for the course purposes) */
body {
  margin: 0;
  min-width: 250px;
}

/* Include the padding and border in an element's total width and height */
* {
  box-sizing: border-box;
}

/* Remove margins and padding from the list */
ul {
  margin: 0;
  padding: 0;
}

a {
  text-decoration: none;
  color: #7c580d;
}

/* Style the list items */
ul li {
  cursor: pointer;
  position: relative;
  padding: 12px 8px 12px 40px;
  list-style-type: none;
  background: #fcf2cb;
  font-size: 18px;
  transition: 0.2s;

  /* make the list items unselectable */
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* Set all odd list items to a different color (zebra-stripes) */
ul li:nth-child(odd) {
  background: #fcf2cb;
}

/* Darker background-color on hover */
ul li:hover {
  background: #ff8926;
}

/* When clicked on, add a background color and strike out text */
ul li.checked {
  background: #ff8926;
  color: #fff;
  text-decoration: line-through;
}

/* Add a "checked" mark when clicked on */
ul li.checked::before {
  content: "";
  position: absolute;
  border-color: #ffb00d;
  border-style: solid;
  border-width: 0 2px 2px 0;
  top: 10px;
  left: 16px;
  transform: rotate(45deg);
  height: 15px;
  width: 7px;
}

/* Style the close button */
.close {
  position: absolute;
  right: 0;
  top: 0;
  padding: 12px 16px 12px 16px;
}

.close:hover {
  background-color: #bc2d19;
  color: white;
}

/* Style the header */
.header {
  background-color: #1e4363;
  padding: 30px 40px;
  color: white;
  text-align: center;
}

/* Clear floats after the header */
.header:after {
  content: "";
  display: table;
  clear: both;
}

/* Style the input */
input {
  margin: 0;
  border: none;
  border-radius: 0;
  width: 75%;
  padding: 10px;
  float: left;
  font-size: 16px;
}

/* Style the "Add" button */
.addBtn {
  padding: 8px;
  width: 25%;
  background: #bbb;
  color: #1e4363;
  float: left;
  text-align: center;
  font-size: 16px;
  cursor: pointer;
  transition: 0.3s;
  border-radius: 0;
  font-weight: bold;
}

.addBtn:hover {
  background-color: #ffb00d;
}

.task-list-item {
    display: block;
}
```

## コントローラの作成
```terminal:Console
symfony4-crud> composer require symfony/maker-bundle --dev
symfony4-crud> composer require doctrine/annotations
symfony4-crud> php bin/console make:controller ToDoListController
```

いま、こうして出来上がったものは「<http://localhost:8000/to/do/list>」というアクセスでJsonを返すAPIのような形になっている

```php:ToDoListController.php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ToDoListController extends AbstractController
{
    /**
     * @Route("/to/do/list", name="to_do_list")
     */
    public function index()
    {
        return $this->json([
            'message' => 'Welcome to your new controller!',
            'path' => 'src/Controller/ToDoListController.php',
        ]);
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/aed0e57f-da34-f02f-6342-30a8f1a68eb3.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

### AbstractControllerとは？
コントローラで利用する様々な機能が含まれていて、Twigへのレンダリングなども行えるため、一般的にはこれを継承しているらしい（エンタープライズになるとどうなんだろう？）
### URLパラメータを取得
```php:aa
    /**
     * @Route("/xxxstatus/{id}", name="xxxstatus") 
     */
    public function xxxStatus($id)
    {
        exit('to do: switch status of the task!'. $id);
    }
```

## twig
```terminal:Console（templatesフォルダができてbaseファイルができることを確認）
symfony4-crud> composer require symfony/twig-bundle
```

## twigとcontrollerの調整
```twig:symfony4-crud\templates\index.html.twig（新規）
{% extends 'base.html.twig' %}

{% block title %}This is specific page title!{% endblock %}

{% block body %}
This is specific page!
{% endblock %}

```
```php:symfony4-crud\src\Controller\ToDoListController.php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ToDoListController extends AbstractController
{
    /**
     * @Route("/to/do/list", name="to_do_list")
     */
    public function index()
    {
        return $this->render('index.html.twig');
    }
}
```

## 確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/1d137e40-b7e2-727b-3348-198513bd06f8.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

## 材料と差し替える
1. base.html.twigを削除して、symfony4-crud-template.htmlをbase.html.twigにリネームする
2. リネームした base.html.twig の body タグの内側を index.html.twig に移植
3. controllerのRouteを変更

```twig:base.html.twig
<!--
  source: https://www.w3schools.com/howto/howto_js_todolist.asp  (modified by netprogs.pl for the course purposes)
-->
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="./css/styles.css">
  </head>
  <body>
    {% block content %}{% endblock %}
  </body>
</html>
```
```twig:index.html.twig
{% extends 'base.html.twig' %}

{% block content %}

<div class="header">
    <h2>My To Do List</h2>
    <form action="">
    <input type="text" id="myInput" placeholder="Task title..." />
    <button type="submit" class="addBtn">Add a task</button>
    </form>
</div>

<ul>
    <li>
    <a href="done"> <span class="task-list-item">Do shopping </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li class="checked">
    <a href="undone"> <span class="task-list-item">Buy eggs </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Meet John </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Read a book </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Learn Doctrine ORM </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Prepare a meal </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
</ul>

{% endblock %}

```
```php:ToDoListController.php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ToDoListController extends AbstractController
{
    /**
     * @Route("/", name="to_do_list")
     */
    public function index()
    {
        return $this->render('index.html.twig');
    }
}
```

## 確認
http://localhost:8000/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/e4d5eca3-ce50-9ac5-259b-069fbcc02c39.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

## まずは固定データでコントローラとテンプレートの接続
1. <http://localhost:8000/> でアクセスしたときの初期表示
2. Add a task したときの表示
3. Do shopping を押したときの表示
4. 削除ボタンを押したときの表示
初期表示することに加えて、3つのアクション時にプレーンテキストが出たら確認OKです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/ac3e7efb-00c9-cb54-4ae0-98963971b722.png)

```php:ToDoListController.php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ToDoListController extends AbstractController
{
    /**
     * @Route("/", name="to_do_list")
     */
    public function index()
    {
        return $this->render('index.html.twig');
    }

    /**
     * @Route("/create", name="create_task", methods={"POST"})
     */
    public function create()
    {
        exit('to do: create a new task!');
    }

    /**
     * @Route("/switch-status/{id}", name="switch_status") 
     */
    public function switchStatus($id)
    {
        exit('to do: switch status of the task!'. $id);
    }

    /**
     * @Route("/delete/{id}", name="delete_task") 
     */
    public function delete($id)
    {
        exit('to do: delete a task with the id of !'. $id);
    }
}
```

```twig:index.html.twig（li要素のリンク先がコントローラへ向いている）
{% extends 'base.html.twig' %}

{% block content %}

<div class="header">
    <h2>My To Do List</h2>
    <form method="POST" action="{{ path('create_task') }}">
    <input type="text" id="myInput" placeholder="Task title..." />
    <button type="submit" class="addBtn">Add a task</button>
    </form>
</div>

<ul>
    <li>
    <a href="{{ path('switch_status', {'id': 1}) }}"> <span class="task-list-item">Do shopping </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="{{ path('delete_task', {'id': 1}) }}"><span class="close">X</span></a>
    </li>
    <li class="checked">
    <a href="undone"> <span class="task-list-item">Buy eggs </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Meet John </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Read a book </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Learn Doctrine ORM </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
    <li>
    <a href="done"> <span class="task-list-item">Prepare a meal </span> </a
    ><a onclick="return confirm('Are you sure to delete?')" href="delete"><span class="close">X</span></a>
    </li>
</ul>

{% endblock %}

```

## データベース作成
```terminal:Console（doctrineという名のORMです）
symfony4-crud> composer require doctrine
```
```text:.env（DATABASE_URLを変更。todo-listというdbをつくる宣言）
DATABASE_URL=mysql://root:rootpw@127.0.0.1:3306/todo-list?serverVersion=5.7
```
```terminal:Console
symfony4-crud> php bin/console doctrine:database:create
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/eb12ce61-4458-f2c8-7d66-c2f2e98bcab2.png)

## エンティティ（モデル）作成
・テーブル名： Task
・列1： title, string, 255, not null
・列2： status, boolean, nullable

```terminal:Console
symfony4-crud> php bin/console make:entity
  Class name of the entity to create or update (e.g. OrangePuppy):
    > Task

  created: src/Entity/Task.php
  created: src/Repository/TaskRepository.php
  Entity generated! Now let's add some fields!
  You can always add more fields later manually or by re-running this command.

  New property name (press <return> to stop adding fields):
    > title
  Field type (enter ? to see all types) [string]:
    > 
  Field length [255]:
    >
  Can this field be null in the database (nullable) (yes/no) [no]:
    > 
  updated: src/Entity/Task.php

  Add another property? Enter the property name (or press <return> to stop adding fields):       
    > status
  Field type (enter ? to see all types) [string]:
    > boolean
  Can this field be null in the database (nullable) (yes/no) [no]:
    > yes
  updated: src/Entity/Task.php

  Add another property? Enter the property name (or press <return> to stop adding fields):       
    >
  Success! 
 
  Next: When you're ready, create a migration with php bin/console make:migration
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/cafbe38c-4b49-8412-f6a0-3b6c37f9a054.png)

## migration
```terminal:Console
symfony4-crud> php bin/console make:migration
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/ddfba7b0-7631-f157-56b8-123dd7c22cb2.png)

```terminal:Console
symfony4-crud> php bin/console doctrine:migrations:migrate

  Application Migrations

  (※テーブル構造いまから変えちゃっていっすか？)
  WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)
    > y
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/c5c6b0f0-48cb-0107-41c6-cf982c53a2c6.png)

## フォームから値を取得する
1. Add a Taskのヒダリのテキストフォームに名前（name="title"）をつける
2. Add a Taskが押されて、コントローラでcreateが呼ばれた時にtitleをデバッグ出力する

```php:ToDoListController.php
    /**
     * @Route("/create", name="create_task", methods={"POST"})
     */
    public function create(Request $request)
    {
        exit($request->request->get('title'));
    }
```

## 確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/8c44705f-e787-bdd3-01be-f467ade2f6a4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/d8efd003-8f57-de3f-0717-838a445d7742.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

## フォーム入力からの新規投稿を可能にする
```php:ToDoListController.php
    public function create(Request $request)
    {
        // input data
        $title = trim($request->request->get('title'));

        // check
        if(empty($title))
            return $this->redirectToRoute('to_do_list');

        // insert
        $em = $this->getDoctrine()->getManager();
        $task = new Task;
        $task->setTitle($title);
        $em->persist($task);
        $em->flush();

        // return
        return $this->redirectToRoute('to_do_list');
    }
```

## 確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/8c44705f-e787-bdd3-01be-f467ade2f6a4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/ff4350b5-b26a-9827-2b93-d45ddbf26b56.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)
まぁデータベースの内容を読み出すのは次で

## データベースの内容を読み出し、テンプレートにレンダリングする

```php:ToDoListController.php
    /**
     * @Route("/", name="to_do_list")
     */
    public function index()
    {
        $tasks = $this->getDoctrine()->getRepository(Task::class)->findBy([], ['id'=>'DESC']);
        return $this->render('index.html.twig', ['tasks'=>$tasks]);
    }
```
```twig:symfony4-crud\templates\index.html.twig
{% extends 'base.html.twig' %}

{% block content %}

<div class="header">
    <h2>My To Do List</h2>
    <form method="POST" action="{{ path('create_task') }}">
    <input name="title" type="text" id="myInput" placeholder="Task title..." />
    <button type="submit" class="addBtn">Add a task</button>
    </form>
</div>

<ul>
    {% for task in tasks %}
    <li {% if task.status %}class="checked"{% endif %}>
        <a href="{{ path('switch_status', {'id': task.id }) }}"> <span class="task-list-item">{{ task.title }} </span> </a>
        <a onclick="return confirm('Are you sure to delete?')" href="{{ path('delete_task', {'id': task.id }) }}"><span class="close">X</span></a>
    </li>
    {% endfor %}
</ul>
{% endblock %}
```

## 確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/a4a10a36-fb1a-106a-39d4-aa45c8c90506.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

## タスクのステータスを変更する
```php:ToDoListController.php
    /**
     * @Route("/switch-status/{id}", name="switch_status") 
     */
    public function switchStatus($id)
    {
        // read
        $em = $this->getDoctrine()->getManager();
        $task = $em->getRepository(Task::class)->find($id);

        // change status
        $task->setStatus(!$task->getStatus());
        $em->flush();

        // return
        return $this->redirectToRoute('to_do_list');
    }
```

## 確認
クリックするたびに色が反転することを確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/73149979-31ff-c5d3-1b61-42c914463286.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94562/b5ad22f6-93f3-6556-b4dc-074ba44f28c8.png)

## タスクを削除する
```terminal:Console（この教材のやりかたをする場合にインストールが必要）
symfony4-crud> composer require sensio/framework-extra-bundle "5.2.4"
```
```php:ToDoListController.php（URLパラメータをインスタンスで受け取って削除するというやり方をしている）
    /**
     * @Route("/delete/{id}", name="delete_task") 
     */
    public function delete(Task $id)
    {
        // delete
        $em = $this->getDoctrine()->getManager();
        $em->remove($id);
        $em->flush();

        // return
        return $this->redirectToRoute('to_do_list');
    }
```
削除が成立すればOK