弊社の多くのアプリケーションはSymfonyをベースにして構築されています。Symfonyは堅牢性と柔軟性を備えたPHPベースのオープンソースフレームワークであり、幅広いプロジェクトに適用されています。

そこで、この記事ではSymfonyについてもっと知ってほしいという気持ちを込めて、Symfonyで簡単なメモアプリを作成する方法を紹介します。

Symfony初心者の方でも迷わずに開発を進められるよう、とりあえず動くのをゴールに具体的な手順を追って紹介していきます。

目次

1. 環境

  • PHP:v8.2.7
  • Symfony:v6.3.0
  • MySQL:v8.0.33

2. 作成するもの

『メモアプリ』

※UIは全く考慮していないので見た目はよくないです🙇‍♂️

完成イメージのGitHubリポジトリ:memo-app-symfony6

全体像

memo-app-symfony

2.1. 今から作るメモアプリを動かすための準備

まずは、実際どんなものを作るのか完成イメージのmemo-app-symfony6をcloneして動かしてみようと思います。 動かすための準備手順は下記です。

  • リポジトリのcloneを行います。
$ git clone git@github.com:mako5656/memo-app-symfony6.git
  • パッケージをインストールします。

※ Composerが入っていない場合はこちらのドキュメントを確認ください。

$ composer install
  • .envファイルの設定を、ご自身の環境に合わせて変更してください
- DATABASE_URL="postgresql://root:dev@127.0.0.1:3306/app?serverVersion=15&charset=utf8"
+ DATABASE_URL="mysql://{ユーザー}:{パスワード}@127.0.0.1:3306/db_name?serverVersion=8.0&charset=utf8mb4"
  • .envで指定したdb_nameのデータベースを作成するために、次のコマンドを実行します。
$ php bin/console doctrine:database:create
Created database `db_name` for connection named default
  • 下記マイグレーションを実行して、memoテーブルをデータベースに作成します。
$ php bin/console doctrine:migrations:migrate
[notice] Migrating up to DoctrineMigrations\Version20230623031235
[notice] finished in 15.7ms, used 14M memory, 1 migrations executed, 1 sql queries
 [OK] Successfully migrated to version : DoctrineMigrations\Version20230623031235

2.2. 動作確認

Symfonyを動かすためにSymfony CLIをインストールしないといけません。

そのため公式ドキュメントの記載にある通りwgetまたは経由curl経由でインストールします。

// wget経由
$ wget https://get.symfony.com/cli/installer -O - | bash
// curl経由
$ curl -sS https://get.symfony.com/cli/installer | bash

Webサーバ起動コマンド

$ symfony server:start

{id}には作成したメモidを入れてください

これらのURLにアクセスすることで、それぞれの動作が確認できます。

3. 作成手順

メモアプリを作成する手順について説明していきます。

3.1. Symfonyプロジェクトの作成

まず、Symfonyプロジェクトを作成します。ターミナルで以下のコマンドを実行してSymfonyの雛形を作成します。

$ symfony new memo-app-symfony6

3.2. パッケージのインストール

次に、今回メモアプリを作成するにあたって必要なパッケージをインストールします。

$ composer require symfony/orm-pack symfony/twig-bundle symfony/form symfony/security-csrf
$ composer require symfony/maker-bundle --dev

それぞれのパッケージについて

  • symfony/orm-pack:Doctrine ORMを使用するために必要なパッケージです。データベース操作などをしやすくします。
  • symfony/twig-bundle:SymfonyでTwigを簡単に利用できるパッケージです。
  • symfony/form:Symfonyでフォームの作成やバリデーション、データ処理などをサポートするパッケージです。
  • symfony/security-csrf:SymfonyでCSRF攻撃から簡単な設定で守れるようにできるパッケージです。今回だと削除機能の際に利用します。
  • symfony/maker-bundle:開発作業を効率化するためのパッケージです。開発環境でしか利用しないので--devオプションをつけてます。

3.3. EntityやRepositoryの作成

メモアプリで使用するEntityとRepositoryを作成します。これらはデータベースとの対話やデータ操作を担当します。

今回のメモアプリ用にコードを追加/削除し下記のようにします。

  • /src/Entity/Memo.php
<?php

namespace App\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Memo
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: Types::INTEGER)]
    private ?int $id = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $content = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getContent(): ?string
    {
        return $this->content;
    }

    public function setContent(string $context): static
    {
        $this->content = $context;

        return $this;
    }
}
  • /src/Repository/MemoRepository.php
<?php

namespace App\Repository;

use App\Entity\Memo;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class MemoRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Memo::class);
    }

    public function findAllMemos(): array
    {
        return $this->findAll();
    }

    public function findMemoById($id): ?Memo
    {
        return $this->find($id);
    }

    public function saveMemo($memo): void
    {
        $this->_em->persist($memo);
        $this->_em->flush();
    }

    public function deleteMemo($memo): void
    {
        $this->_em->remove($memo);
        $this->_em->flush();
    }
}

慣れてきたらSymfonyのコンソールを使用して、以下のコマンドで作成も可能です。

実行すると対話形式のプロンプトが表示されフィールドの詳細を入力すると、エンティティクラスとリポジトリクラスが作成することができます。

$ php bin/console make:entity Memo

3.4. ControllerやFormの作成

次に、メモの作成や編集、削除などの操作を行うためのControllerとFormを作成します。

  • /src/Repository/MemoController.php
<?php

namespace App\Controller;

use App\Entity\Memo;
use App\Form\MemoFormType;
use App\Repository\MemoRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class MemoController extends AbstractController
{
    public function __construct(
        private readonly MemoRepository $memoRepository
    ){
    }

    #[Route(path: '/', name: 'memo_index', methods: ['GET'])]
    public function index(): Response
    {
        $memos = $this->memoRepository->findAllMemos();

        return $this->render('memo/index.html.twig', [
            'memos' => $memos,
        ]);
    }

    #[Route(path: '/create', name: 'memo_create', methods: ['GET', 'POST'])]
    public function create(Request $request): Response
    {
        $memo = new Memo();

        $form = $this->createForm(MemoFormType::class, $memo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->memoRepository->saveMemo($memo);
            return $this->redirectToRoute('memo_index');
        }

        return $this->render('memo/create.html.twig', [
            'form' => $form->createView(),
        ]);
    }

    #[Route(path: '/memo/{id}/edit', name: 'memo_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Memo $memo): Response
    {
        $form = $this->createForm(MemoFormType::class, $memo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->memoRepository->saveMemo($memo);
            return $this->redirectToRoute('memo_index');
        }

        return $this->render('memo/edit.html.twig', [
            'form' => $form->createView(),
        ]);
    }

    #[Route(path: '/memo/{id}/delete', name: 'memo_delete', methods: ['GET', 'POST'])]
    public function delete(Request $request, Memo $memo): Response
    {
        if ($this->isCsrfTokenValid('delete' . $memo->getId(), $request->request->get('_token'))) {
            $this->memoRepository->deleteMemo($memo);
            return $this->redirectToRoute('memo_index');
        }

        return $this->render('memo/delete.html.twig', [
            'memo' => $memo,
        ]);
    }
}

こちらも慣れてきたら下記コマンドで対話式に関連するルート(URL)を入力すると、Controllerクラスのテンプレート を作成する事ができます。

$ php bin/console make:controller MemoController

次に、Formを作成します。

  • /src/Form/MemoFormType.php
<?php

namespace App\Form;

use App\Entity\Memo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class MemoFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('content', TextareaType::class, [
                'label' => 'Content',
                'attr' => ['rows' => 5],
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Memo::class,
        ]);
    }
}

こちらも以下のコマンドで、関連するエンティティを入力することでFormのテンプレートを作成することができます。

$ php bin/console make:form MemoFormType

3.5. RoutingやServicesなどの設定

次に、作成したControllerやFormをアプリケーションに統合するための設定を行います。

まず、ルーティング(Routing)を設定します。config/routes.yamlファイルを開き、作成したControllerのアクションとURLの対応関係を定義します。

  • config/route.yaml
memo_index:
    path: /memo
    controller: App\Controller\MemoController::index
memo_create:
    path: /memo/create
    controller: App\Controller\MemoController::create
memo_edit:
    path: /memo/{id}/edit
    controller: App\Controller\MemoController::edit
memo_delete:
    path: /memo/{id}/delete
    controller: App\Controller\MemoController::delete

次に、必要なサービス(Services)を設定します。config/services.yamlファイルを開き、作成したFormやRepositoryなどのサービスを登録します。

このことを行うことで、$registry引数を@doctrineと指定することで、Symfonyの依存性注入コンテナからDoctrineのサービスを取得してMemoRepositoryに注入することができます。

これによって、MemoRepositoryはDoctrineの機能を利用することができます。

  • config/services.yaml
services:
    # ...
    App\Repository\MemoRepository:
        arguments:
            $registry: '@doctrine'
    # ...

次に、CSRF保護を有効にしないと削除操作が拒否されてしまうのでcsrf_protectiontrueに変更します。

  • config/packages/framework.yaml
framework:
    # ...
    csrf_protection: true
    # ...

次に、Doctrine Migrationsを使用して、データベースの変更点を検出し、マイグレーションファイルを生成します。

下記コマンドを実行することで、Entityクラスの情報を元にsrc/Migrations/にマイグレーションファイルが作成されます。

これは、データベースのスキーマ変更を管理するための役割があります。

$ php bin/console doctrine:migrations:diff

.envファイルの設定を、ご自身の環境に合わせて変更する必要があります。

- DATABASE_URL="postgresql://root:dev@127.0.0.1:3306/app?serverVersion=15&charset=utf8"
+ DATABASE_URL="mysql://{ユーザー}:{パスワード}@127.0.0.1:3306/db_name?serverVersion=8.0&charset=utf8mb4"

3.6. Twigの作成

最後に、メモの表示やフォームのレンダリングなどを行うためのTwigテンプレートを作成します。

templatesディレクトリ内に、適切なディレクトリ構造を作成し、Twigテンプレートファイルを作成します。作成したControllerからTwigテンプレートを参照し、データの表示やフォームの表示を行います。

まず、他のテンプレートで共有される基本的なHTML構造を定義します。

  • templates/base.html.twig
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
        {% block stylesheets %}
        {% endblock %}

        {% block javascripts %}
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block content %}{% endblock %}
    </body>
</html>

メモ一覧を表示するためのコードが下記です。

{% extends 'base.html.twig' %}で、base.html.twigを継承し、{% block content %}内には、メモの内容をループして表示するようにしています。

また、その下にそのメモの編集と削除ページに遷移するボタンを設置しました。

  • templates/memo/index.html.twig
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Memos</h1>
    <form action="{{ path('memo_create') }}" method="POST">
        <button type="submit">メモを作成</button>
    </form>
    <ul>
        {% for memo in memos %}
            <li>{{ memo.content }}</li>
            <a href="{{ path('memo_edit', {'id': memo.getId() }) }}">Edit</a>
            <a href="{{ path('memo_delete', {'id': memo.getId() }) }}">Delete</a>
        {% endfor %}
    </ul>
{% endblock %}

新しいメモを作成するためのコードが下記です。

{{ form_start(form) }}{{ form_end(form) }}によって、MemoFormType.phpのフォームが表示されます。

  • templates/memo/create.html.twig
{% extends 'base.html.twig' %}

{% block body %}
    <h1>Create Memo</h1>
    {{ form_start(form) }}
        {{ form_row(form.content) }}
        <button type="submit">Create</button>
    {{ form_end(form) }}
{% endblock %}

既存のメモを編集するためのコードが下記です。

こちらは、新しいメモを作成するコマンドとほぼ同じで内容を処理しているController部分が違うだけです。

  • templates/memo/edit.html.twig
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Edit Memo</h1>
    {{ form_start(form) }}
        {{ form_widget(form) }}
        <button type="submit">Save</button>
    {{ form_end(form) }}
{% endblock %}

メモを削除するためのコードが下記です。

csrf_tokenを渡してあげてURLの{id}に基づくメモの削除処理を行うようにしています。

  • templates/memo/delete.html.twig
{% extends 'base.html.twig' %}

{% block title %}Delete Memo{% endblock %}

{% block content %}
    <h1>Delete Memo</h1>
    <p>Are you sure you want to delete this memo?</p>
    <form method="post" action="{{ path('memo_delete', {'id': memo.getId()}) }}">
        <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ memo.getId()) }}">
        <button type="submit">Delete</button>
    </form>
{% endblock %}

最後に、2.1. 今から作るメモアプリを動かすための準備に記載されている準備と同様、データベースを作成しマイグレーションファイルの内容を反映させたら準備完了です。

// データベースを作成
$ php bin/console doctrine:database:create
// マイグレーションファイルの内容を反映
$ php bin/console doctrine:migrations:migrate

以上で、メモアプリの作成手順が完了です🎉🎉🎉

Webサーバーを起動($ symfony server:start)してメモを作成/編集/削除できるか確認してみましょう!!

4. まとめ

この記事では、Symfonyを使用して簡易なメモアプリを作成する手順を紹介しました。

以下の手順を追って、Symfonyプロジェクトの作成、パッケージのインストール、EntityやRepository、ControllerやFormの作成、ルーティングやサービスの設定、そしてTwigテンプレートの作成を行いました。

  • 3.1. Symfonyプロジェクトの作成
  • 3.2. 必要なパッケージをインストール
  • 3.3. EntityやRepositoryの作成
  • 3.4. ControllerやFormの作成
  • 3.5. RoutingやServicesなどの設定
  • 3.6. Twigの作成

Symfony初心者の方でも理解しやすいように手順を追って説明しましたので、ぜひ参考にしてください。

また、Symfonyの公式から「The Fast Track」という最速でSymfonyが学べる日本語サイトがあるので是非こちらもご覧ください。