1. blogコントローラの作成
>php bin/console make:controller BlogController
created: src/Controller/BlogController.php
created: templates/blog/index.html.twig
Success!
Next: Open your new controller class and add some pages!
2.app/Resources/views/blog/index.html.twigの修正
<h1>Blog posts</h1>
3. エンティティ(モデル)の作成
テーブルの作成
CREATE TABLE `post` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `comment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`post_id` bigint(20) NOT NULL,
`author` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `comment_post_id_idx` (`post_id`),
CONSTRAINT `post_id` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
既存のテーブルからエンティを作成
php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity
Getters&SettersまたはPHPクラスの生成
// generates getter/setter methods for all Entities php bin/console make:entity --regenerate App // generates getter/setter methods for one specific Entity php bin/console make:entity --regenerate App\\Entity\\Country
4.コントローラの修正
use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* @Route("/blog", name="blog")
*/
public function index(): Response
{
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository(Post::class)->findAll();
return $this->render('blog/index.html.twig', [
'posts' => $posts,
]);
}
}
5.テンプレートの修正
<h1>Blog posts</h1>
{% if posts | length > 0 %}
<table class="table" border="1">
<thead>
<tr>
<td>ID</td>
<td>タイトル</td>
<td>作成日</td>
<td>更新日</td>
</tr>
</thead>
<tbody>
{# posts配列をループして、投稿記事の情報を表示 #}
{% for post in posts %}
<tr>
<td><a href="#">{{ post.id }}</a></td>
<td>{{ post.title }}</td>
<td>{{ post.createdAt|date('Y/m/d H:i') }}</td>
<td>{{ post.updatedAt|date('Y/m/d H:i') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No Posts</p>
{% endif %}
6.データの挿入
INSERT INTO Post (title, content, createdAt, updatedAt) values ('初めての投稿', '初めての投稿です。', NOW(), NOW());
7. 記事詳細ページの作成
コントローラの追加
/**
* @Route("/blog/{id}", name="blog_show")
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$post = $em->getRepository(Post::class)->find($id);
if (!$post) {
throw $this->createNotFoundException('The post does not exist');
}
return $this->render('blog/show.html.twig', ['post' => $post]);
}
テンプレートの追加
<h1>{{ post.title }}</h1>
<p><small>Created: {{ post.createdAt|date('Y/m/d H:i') }}</small></p>
<p>{{ post.content|nl2br }}</p>
8.ページをリンクで結ぶ
blog/show.html.twig
{# posts配列をループして、投稿記事の情報を表示 #}
{% for post in posts %}
<tr>
{# 詳細ページにリンク #}
<td><a href="{{ path('blog_show', {id: post.id}) }}">{{ post.id }}</a></td>
<td>{{ post.title }}</td>
<td>{{ post.createdAt|date('Y/m/d H:i') }}</td>
<td>{{ post.updatedAt|date('Y/m/d H:i') }}</td>
</tr>
{% endfor %}
blog/index.html.twig
<h1>{{ post.title }}</h1>
<p><small>Created: {{ post.createdAt|date('Y/m/d H:i') }}</small></p>
<p>{{ post.content|nl2br }}</p>
{# 戻るリンクを追加 #}
<p><a href="{{ path('blog_index') }}">一覧に戻る</a></p>
9.テンプレートの継承
base2.html.twig
<!doctype html>
<html>
<head>
<meta charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
<title>{% block title %}{{ block('page_title') }}
- Symfony Blog{% endblock title %}</title>
</head>
<body>
<header class="navbar navbar-dark bg-primary">
<div class="container">
<h1 class="navbar-brand">Symfony Blog</h1>
</div>
</header>
<div class="container">
<div class="row">
<h2>{% block page_title %}{% endblock page_title %}</h2>
</div>
{% block content %}{% endblock content %}
<footer>
<p>© 2021 青山システムズ</p>
</footer>
</div>
</body>
</html>
blog/index.html.twig
{% extends 'base2.html.twig' %}
{% block page_title %}Blog posts{% endblock %}
{% block content %}
<div class="row">
{% if posts | length > 0 %}
<table class="table table-bordered">
<thead>
<tr>
<td>ID</td>
<td>タイトル</td>
<td>作成日</td>
<td>更新日</td>
</tr>
</thead>
<tbody>
{# posts配列をループして、投稿記事の情報を表示 #}
{% for post in posts %}
<tr>
<td><a href="{{ path('blog_show', {id: post.id}) }}">
{{ post.id }}</a></td>
<td>{{ post.title }}</td>
<td>{{ post.createdAt|date('Y/m/d H:i') }}</td>
<td>{{ post.updatedAt|date('Y/m/d H:i') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No Posts</p>
{% endif %}
</div>
{% endblock %}
blog/show.html.twig
{% extends 'base2.html.twig' %}
{% block page_title %}{{ post.title }}{% endblock %}
{% block content %}
<div class="row">
<dl>
<dt>作成日</dt>
<dd><small>{{ post.createdAt|date('Y/m/d H:i') }}</small></dd>
<dt>内容</dt>
<dd>{{ post.content|nl2br }}</dd>
</dl>
</div>
<div class="row">
<a href="{{ path('blog_index') }}" class="btn btn-light">一覧に戻る</a>
</div>
{% endblock %}
10.新規作成ページ
コントローラの追加と修正
use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
・・・・・・・
/**
* @Route("/blog/{id}", name="blog_show", requirements={"id"="\d+"})
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$post = $em->getRepository(Post::class)->find($id);
if (!$post) {
throw $this->createNotFoundException('The post does not exist');
}
return $this->render('blog/show.html.twig', ['post' => $post]);
}
/**
* @Route("/blog/new", name="blog_new")
*/
public function newAction(Request $request)
{
// フォームの組立
$form = $this->createFormBuilder(new Post())
->add('title')
->add('content')
->getForm();
return $this->render('blog/new.html.twig', [
'form' => $form->createView(),
]);
}
}
blog/new.html.twigの追加
{% extends 'base2.html.twig' %}
{% form_theme form 'bootstrap_4_horizontal_layout.html.twig' %}
{% block page_title '新規作成' %}
{% block content %}
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="btn btn-primary">作成</button>
{{ form_end(form) }}
{% endblock content %}
index.html.twig に「新しい記事を書く」ボタンを設置
{% block content %}
<div class="row">
<a class="btn btn-primary" href="{{ path('blog_new') }}">新しい記事を書く</a>
</div>
<div class="row">
{% if posts | length > 0 %}
11.登録機能
コントローラの修正
/**
* @Route("/blog/new", name="blog_new")
*/
public function newAction(Request $request)
{
// フォームの組立
$post = new Post();
$form = $this->createFormBuilder($post)
->add('title')
->add('content')
->getForm();
// PSST判定&バリデーション
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// エンティティを永続化
$post->setCreatedAt(new \DateTime());
$post->setUpdatedAt(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
return $this->redirectToRoute('blog_index');
}
return $this->render('blog/new.html.twig', [
'form' => $form->createView(),
]);
}
12. バリデーションの追加
ロケールを変更し、エラー文言などを日本語化
services.yaml
parameters:
from_address: 'xxx@yyy.zzz'
locale: ja
default_locale: '%locale%'
translation.yaml
framework:
default_locale: '%locale%'
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- '%locale%' # en
エンティのPost.phpの変更
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Post
*
* @ORM\Table(name="post")
* @ORM\Entity
*/
class Post
{
/**
* @var int
*
* @ORM\Column(name="id", type="bigint", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="title", type="string", length=255)
* @Assert\NotBlank()
* @Assert\Length(min="2", max="50")
*/
private $title;
/**
* @var string
*
* @ORM\Column(name="content", type="text")
* @Assert\NotBlank()
* @Assert\Length(min="10")
*/
private $content;
titleに10文字未満だと日本語でエラーが表示される
13.削除機能
削除アクションとテンプレートに削除ボタンを追加します。
コントローラーにdeleteActionを追加します。
/**
* @Route("/blog/{id}/delete",
* name="blog_delete",
* requirements={"id"="\d+"})
*/
function deleteAction($id)
{
$em = $this->getDoctrine()->getManager();
$post = $em->getRepository(Post::class)->find($id);
if (!$post) {
throw $this->createNotFoundException(
'No post found for id '.$id
);
}
// 削除
$em->remove($post);
$em->flush();
return $this->redirectToRoute('blog_index');
}
blog/index.html.twig に追記
<td>操 作</td>
</tr>
</thead>
<tbody>
{# posts配列をループして、投稿記事の情報を表示 #}
{% for post in posts %}
<tr>
<td><a href="{{ path('blog_show', {id: post.id}) }}">
{{ post.id }}</a></td>
<td>{{ post.title }}</td>
<td>{{ post.createdAt|date('Y/m/d H:i') }}</td>
<td>{{ post.updatedAt|date('Y/m/d H:i') }}</td>
<td><a class="btn btn-danger"
href="{{ path('blog_delete', {'id':post.id}) }}">削除</a></td>
14.編集機能
コントローラーにeditActionを追加します。
/**
* @Route("/blog/new", name="blog_new")
*/
public function newAction(Request $request)
{
// ・・・
return $this->render('blog/new.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
/**
* @Route("/blog/{id}/edit",
* name="blog_edit",
* requirements={"id"="\d+"})
*/
public function editAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$post = $em->getRepository(Post::class)->find($id);
if (!$post) {
throw $this->createNotFoundException(
'No post found for id '.$id
);
}
$form = $this->createFormBuilder($post)
->add('title')
->add('content')
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// フォームから送信されてきた値と一緒に更新日時も更新して保存
$post->setUpdatedAt(new \DateTime());
$em->flush();
return $this->redirectToRoute('blog_index');
}
// 新規作成するときと同じテンプレートを利用
return $this->render('blog/new.html.twig', [
'post' => $post,
'form' => $form->createView(),
]);
}
/blog/new.html.twigの修正
{% block content %}
{{ form_start(form) }}
{{ form_widget(form) }}
<a class="btn btn-light" href="{{ path('blog_index') }}">一覧に戻る</a>
<button type="submit" class="btn btn-primary">{{ post.id ? '編集' : '作成' }}</button>
{{ form_end(form) }}
{% endblock content %}
/blog/index.html.twigの追加
<td>
<a class="btn btn-info"
href="{{ path('blog_edit', {'id': post.id}) }}">編集</a>
<a class="btn btn-danger"
href="{{ path('blog_delete', {'id':post.id}) }}">削除</a>
</td>
まだいろいろなことがあるが今回はここまでです
第2シリーズはあるかな?