LoginSignup
2
5

More than 3 years have passed since last update.

symfonyでTODOアプリを作る

Last updated at Posted at 2020-06-07

はじめに

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

また英語なの!?

文句言ってるヒマはねぇや、やるしかねぇぇぇ
https://www.udemy.com/share/101WEaAkQadFdURHg=/
image.png

TODOアプリ

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

スケルトン作成

djangoでいう、startproject の後の状態ね

Project\Php(symfony4-crudというアプリ名)
Php> composer create-project symfony/skeleton symfony4-crud "5.1.*"

image.png

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
image.png

確認(checkにアクセス)

http://localhost:8000/check.php
image.png
image.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 を使います。

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

キャッシュサイズを5M以上に

php.ini(変更前)
;realpath_cache_size = 4096k
php.ini(変更後)
realpath_cache_size = 5M

確認

> 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

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>
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;
}

コントローラの作成

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のような形になっている

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
image.png

AbstractControllerとは?

コントローラで利用する様々な機能が含まれていて、Twigへのレンダリングなども行えるため、一般的にはこれを継承しているらしい(エンタープライズになるとどうなんだろう?)

URLパラメータを取得

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

twig

Console(templatesフォルダができてbaseファイルができることを確認)
symfony4-crud> composer require symfony/twig-bundle

twigとcontrollerの調整

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 %}

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
image.png

材料と差し替える

  1. base.html.twigを削除して、symfony4-crud-template.htmlをbase.html.twigにリネームする
  2. リネームした base.html.twig の body タグの内側を index.html.twig に移植
  3. controllerのRouteを変更
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>
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 %}

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
image.png

まずは固定データでコントローラとテンプレートの接続

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

image.png

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);
    }
}
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 %}

データベース作成

Console(doctrineという名のORMです)
symfony4-crud> composer require doctrine
.env(DATABASE_URLを変更。todo-listというdbをつくる宣言)
DATABASE_URL=mysql://root:rootpw@127.0.0.1:3306/todo-list?serverVersion=5.7
Console
symfony4-crud> php bin/console doctrine:database:create

image.png

エンティティ(モデル)作成

・テーブル名: Task
・列1: title, string, 255, not null
・列2: status, boolean, nullable

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

migration

Console
symfony4-crud> php bin/console make:migration

image.png

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

フォームから値を取得する

  1. Add a Taskのヒダリのテキストフォームに名前(name="title")をつける
  2. Add a Taskが押されて、コントローラでcreateが呼ばれた時にtitleをデバッグ出力する
ToDoListController.php
    /**
     * @Route("/create", name="create_task", methods={"POST"})
     */
    public function create(Request $request)
    {
        exit($request->request->get('title'));
    }

確認

image.png
image.png
image.png

フォーム入力からの新規投稿を可能にする

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
image.png
image.png
まぁデータベースの内容を読み出すのは次で

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

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]);
    }
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
image.png

タスクのステータスを変更する

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
image.png

タスクを削除する

Console(この教材のやりかたをする場合にインストールが必要)
symfony4-crud> composer require sensio/framework-extra-bundle "5.2.4"
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

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5