はじめに
現場が2020年5月末で終りを迎えた。で、次の現場で使われてるフレームワークがsymfonyって言われたんだけど、、、
おおん?日本語の資料がめっちゃすくねぇー!
また英語なの!?
文句言ってるヒマはねぇや、やるしかねぇぇぇ
https://www.udemy.com/share/101WEaAkQadFdURHg=/
TODOアプリ
Project\Php は僕が作ったフォルダで、ここでphpの作品を取り扱うことにしている。当然最初は空っぽであるという前提で良い。
スケルトン作成
djangoでいう、startproject の後の状態ね
Php> composer create-project symfony/skeleton symfony4-crud "5.1.*"
Php> cd symfony4-crud
symfony4-crud> composer require symfony/requirements-checker
symfony4-crud> php -S 127.0.0.1:8000 -t public
確認(localhostにアクセス)
確認(checkにアクセス)
http://localhost:8000/check.php
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 を使います。
;opcache.enable=1
;opcache.enable_cli=0
opcache.enable=1
opcache.enable_cli=1
zend_extension=C:\php\ext\php_opcache.dll
キャッシュサイズを5M以上に
;realpath_cache_size = 4096k
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フォルダにつっこむ。デザインなど、ラクできるところはラクしましょ。動くものを作るのが目的ですから。
<!--
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>
/* 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;
}
コントローラの作成
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
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',
]);
}
}
AbstractControllerとは?
コントローラで利用する様々な機能が含まれていて、Twigへのレンダリングなども行えるため、一般的にはこれを継承しているらしい(エンタープライズになるとどうなんだろう?)
URLパラメータを取得
/**
* @Route("/xxxstatus/{id}", name="xxxstatus")
*/
public function xxxStatus($id)
{
exit('to do: switch status of the task!'. $id);
}
twig
symfony4-crud> composer require symfony/twig-bundle
twigとcontrollerの調整
{% extends 'base.html.twig' %}
{% block title %}This is specific page title!{% endblock %}
{% block body %}
This is specific page!
{% endblock %}
<?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');
}
}
確認
材料と差し替える
- base.html.twigを削除して、symfony4-crud-template.htmlをbase.html.twigにリネームする
- リネームした base.html.twig の body タグの内側を index.html.twig に移植
- controllerのRouteを変更
<!--
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>
{% 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
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/ でアクセスしたときの初期表示
- Add a task したときの表示
- Do shopping を押したときの表示
- 削除ボタンを押したときの表示 初期表示することに加えて、3つのアクション時にプレーンテキストが出たら確認OKです
<?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);
}
}
{% 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 %}
データベース作成
symfony4-crud> composer require doctrine
DATABASE_URL=mysql://root:rootpw@127.0.0.1:3306/todo-list?serverVersion=5.7
symfony4-crud> php bin/console doctrine:database:create
エンティティ(モデル)作成
・テーブル名: Task
・列1: title, string, 255, not null
・列2: status, boolean, nullable
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
migration
symfony4-crud> php bin/console make:migration
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
フォームから値を取得する
- Add a Taskのヒダリのテキストフォームに名前(name="title")をつける
- Add a Taskが押されて、コントローラでcreateが呼ばれた時にtitleをデバッグ出力する
/**
* @Route("/create", name="create_task", methods={"POST"})
*/
public function create(Request $request)
{
exit($request->request->get('title'));
}
確認
フォーム入力からの新規投稿を可能にする
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');
}
確認
データベースの内容を読み出し、テンプレートにレンダリングする
/**
* @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]);
}
{% 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 %}
確認
タスクのステータスを変更する
/**
* @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');
}
確認
タスクを削除する
symfony4-crud> composer require sensio/framework-extra-bundle "5.2.4"
/**
* @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