【Symfony】SymfonyでMySQLに接続してみる!
Symfony

どーも!

たかぽんです!

Symfonyを触り始めている勢いでそのままMySQLも接続してみようと思います!

では早速いってみましょー!

MySQLを準備する

さて、では早速!といきたいのですが、大前提として下記のSymfonyの環境構築が終えている想定で話を進めていきます。

もしまだの方は先に用意を済ませていただけるとスムーズに試しながら読めるかと思います。

また、すでにMySQLを設定していて、接続情報がある方はそちらで対応いただく形でも良いかと思います。

一応、本記事にて軽く解説は行うので、もしまだできていない方は参考にしてみてください。

では、まずはmysqlをインストールして、サーバーを動かし、サーバーにログインします。

brew install mysql
sudo mysql.server start
mysql -u root

上記コマンドを全て終えて、ターミナルが"mysql>"といった形で入力待機になればOKです!

次に、今回Symfonyで使用するユーザーを作成します。

mysql> CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
Query OK, 0 rows affected (0.02 sec)

mysql> GRANT ALL PRIVILEGES ON * . * TO 'newuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> FLUSH PRIVILEGES;
mysql> SET PASSWORD FOR newuser@localhost='password';

mysql> exit

最初に'newuser'として新規のユーザーを作成し、次に全権限を付与します。

(ローカル検証を楽にするため、全権限にしている点はご留意ください!)

そして"FLUSH PRIVILEGES"で設定した権限を反映させます。

そして、newuserとして、'password'というパスワードを設定しています。(ここは任意に変えていただいて大丈夫です。)

ここまでできたら、作成したユーザーができているか検証するため、一旦exitでmysqlサーバーからログアウトします。

そしたら、今度は先ほど作った"newuser"としてログインをしてみましょう!

mysql -u newuser -p

コマンドを打つとパスワードの入力を求められるので、先ほど設定したパスワード(筆者の場合は'password'でした)を入力します。

そうすると、問題なく再び"mysql> "と入力待ちになっていれば完璧です!

ここで、現在のmysqlサーバーのバージョンを確認しておきます。

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.33    |
+-----------+
1 row in set (0.00 sec)

筆者が試した際は8.0.33でした。

さて、ここまできたら一旦Mysqlの用意はOKです...!

次にSymfony側の設定を進めていきましょう!

Symfonyの設定

Symfonyでは、先ほどのDBへの接続情報をenvへ設定したり、Entityと呼ばれる、ファイルを作成し、そのテーブルをmigration(Databaseに実際のテーブルを作ったりする)を行います。

そして最後に作成したテーブルに簡易なテストデータを入れて、コントローラーから呼び出せることを確認します。

それではやっていきましょう!

MySQLの接続情報を.envファイルへ設定する

ではやっていきます!

まず最初に"my_project/.env"ファイルにある"DATABASE_URL"を編集していきます。

元々は下記のようにpostgresqlの設定向けにコメントアウトされていると思いますが...

# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
DATABASE_URL="mysql://newuser:password@127.0.0.1:3306/symfony_test?serverVersion=8.0.33&charset=utf8mb4"
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8"

今回はmysqlを使用するため、以下のように修正したものを新規で作成し、他のものは削除、もしくはコメントアウト(先頭に#をつける)します。

DATABASE_URL="mysql://newuser:password@127.0.0.1:3306/symfony_test?serverVersion=8.0.33&charset=utf8mb4"

簡単に説明しておきますと、こちら にありますが、

DATABASE_URL="mysql://{DBのユーザー名}:{DBユーザーのパスワード}@{DBのホスト}/{新規で作成するSymfony用のDB名}?serverVersion={前節で確認したMySQLのバージョン}&charset=utf8mb4"

といった具合です。

{DBのユーザー名}は先ほど設定した'newuser'

{DBユーザーのパスワード}は先ほど'newuser'に設定したパスワードである、'password'ですね。

{DBのホスト}は基本的にmysqlのデフォルトはlocalhostの3306番ポートなので、'127.0.0.1:3306'でOKです。

{新規で作成するSymfony用のDB名}は、後ほどSymfony用のデータベースを作成するのですが、その時作成するデータベースの名前を入れます。

{前節で確認したMySQLのバージョン}は先ほど"select version();"で確認したバージョンを入れます。

charsetは別になくても良さそうですが、DBの文字コードになっています。

.envに追加したら保存を忘れないようにしてください!

次に、Entityを作っていきましょう!

Entityを作成する

さて、では接続情報も設定し終えたので、Entityを作っていきます!

SymfonyのEntityは、テーブルに対して一つづつ定義される、オブジェクトを定義するためのファイルで、例えばサービスに"User"と"Comment"というオブジェクトが必要だよな...となった場合、UserテーブルとCommentテーブルを作成しますよね。

そうすると、それに合わせてプログラム側でもUserテーブルの値を持っているインスタンス、Commentテーブルの値を持っているインスタンスといった管理をすれば値が使いやすくなります。

そのために、概念としてのオブジェクトの定義をするのがEntityです。

今回作っていくテーブルは以下のようにしてみようと思います。

すごくシンプルですが、UserテーブルとCommentテーブルがあり、UserテーブルにはIDと、名前と年齢のみ。

そして、CommentにはIDと、Userテーブルに紐づくuser_id、そしてmessageの本文がある形です。

関係性としてはUser一人に対して複数コメントを許容したいので、1:0~nです。

コメントに対してユーザーは絶対に一人居なければいけないけど、ユーザーに対してコメントがなくても、コメントが複数あってもOKという感じですね。

さて、それでは上記に沿ったEntityを作っていきます!

まず最初にORMに関連するコンポーネントをインストールします。

この手順はほぼこちらを参考にしているので、ぜひ合わせて読んでいただけると理解が深まるかと思います。

先ほどmysqlサーバーに入ったターミナルとは別で、自分のプロジェクトを開き、下記コマンドを実行します。

composer require symfony/orm-pack
composer require --dev symfony/maker-bundle

もし、composerをインストールしていない方は下記でインストールできるかと思います。

brew install composer

""symfony/orm-pack", "symfony/maker-bundle"のインストールが完了したら、Symfonyで使うためのDBを新規で作成するので、下記コマンドを実行します。

php bin/console doctrine:database:create

そしたら、先ほどのmysqlのサーバーに入っていたターミナルにて、下記を実行してみてください。

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| symfony_test       |
| sys                |
| test               |
+--------------------+
6 rows in set (0.00 sec)

"symfony_test"というデータベースができていることがわかりますね!

先ほどのコマンドはdatabase:createとある通り、先ほど.envに設定した"symfony_test"というDB名でMySQLのデータベースを作成するコマンドでした。

では、今度はこのsymfony_testにUser, Commentテーブルを作成するためのEntityを作っていきます。

ここはちょっと複雑になるので先に全体像をお見せします。

taka@Taka my_project % php bin/console make:entity

 Class name of the entity to create or update (e.g. DeliciousGnome):
 > User

 created: src/Entity/User.php
 created: src/Repository/UserRepository.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):
 > name

 Field type (enter ? to see all types) [string]:
 > string

 Field length [255]:
 > 255

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

 updated: src/Entity/User.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > age

 Field type (enter ? to see all types) [string]:
 > integer

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

 updated: src/Entity/User.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

ここでやっていることは、Userエンティティの作成で、"php bin/console make:entity"というコマンドを実行すればEntityの作成を行うことができます。

Entityの作成は質問形式で...

// 新規作成または更新するEntityの名前
1. Class name of the entity to create or update 

// 新規のプロパティの名前(DBでいうカラム名)
2. New property name (press <return> to stop adding fields):

// 前項で指定したプロパティの型(DBでいうカラムの型)
3.  Field type (enter ? to see all types) [string]: 

// プロパティの文字列長(おそらく文字列の場合のみ聞かれる)
4.  Field length [255]: 

// nullを許容するかどうか
5. Can this field be null in the database (nullable) (yes/no) [no]: 

// 他にもプロパティを追加するかどうか?するならプロパティ名を入力(これ以上追加しない場合は何も入力せず"Enter")
6. Add another property? Enter the property name (or press <return> to stop adding fields): 

...

といった具合で、エンティティではありますが、テーブルのカラムを追加してくのを想像していただけるといいのかなと思います。

最初のEntityの名前がテーブル名、そして新規のプロパティ名、型、文字列長やnull許容かどうかはそのテーブルのカラムの設定になります。

今回はUserテーブルは'name'と'age'でしたね。

なので、二つの値を登録するように入力しています。

最後に"Next: When you're ready, create a migration with php bin/console make:migration"とありますが、一旦無視して、そのままCommentのEntityも作成します。

再び下記コマンドでそれぞれ適切に入れていけばOKです。

php bin/console make:entity

Commentのエンティティも作成できたら、プロジェクト上に"User.php", "UserRepository.php", "Comment.php", "CommentRepository.php"といったファイルがつくられるかと思います。

ここまでできたら次にmigrationファイルを作成してテーブルを作っていきましょう!

Migrationファイルを作成して、テーブルを作成する

さて、次はMigrationファイルを作成します!

ここでいうMigrationとは、フレームワークでよく使われるのですが、SQLの実行履歴を残すようにして実行し、履歴の確認や、実行内容を元に戻しやすくする仕組みのことです。

例えばテーブルを追加するSQLのためのMigrationファイルを作成したら、そのMigrationを実行するとデータベースにはテーブルが作成されます。

その際、合わせてテーブルをドロップする逆の処理(追加なら削除、削除なら追加といった具合...)を記載して、後から追加したカラムを消したり、削除したカラムを再度作ったりできるようになります。

では、以下のコマンドを実行しましょう!

taka@Taka my_project % php bin/console make:migration
 created: migrations/Version20230729141041.php


  Success!


 Review the new migration then run it with php bin/console doctrine:migrations:migrate
 See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html


実行をすると、上記のように"created: migrations/Version20230729141041.php"といった形で、"Version20230729141041.php"というファイルが作成されます。

これがマイグレーション用のファイルになっていて、ファイル名に時刻を入れることで、必ず異なる名前になるようになっています。

ファイルの中身を見てみると...

    ...
    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, message VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
        $this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
        $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
    }

    public function down(Schema $schema): void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->addSql('DROP TABLE comment');
        $this->addSql('DROP TABLE user');
        $this->addSql('DROP TABLE messenger_messages');
    }
    ...

前後は省略しましたが、このような形で、"up"と"down"というメソッドが定義されます。

これは、マイグレーションの実行時に実行されるのはupメソッドになっていて、今回反映したい処理になります。

そのため、先ほどEntityで設定したテーブルの作成が行われていて、逆にdownの方ではDROPしてテーブルの削除が記述されているのがわかりますね!

つまり、このMigrationを巻き戻したい場合はdownを実行すればいいわけです。

また、"messenger_messages"というのが作られていますが、これはSymfonyのmessageコンポーネントの機能を使用する場合、自動で作られるようになっているようです。(参考

今回は別にどちらでもいいのでそのまま合わせて作ってしまおうと思います。

さて、ファイルができていることが確認できたら、下記コマンドを実行してMigrationしましょう!

taka@Taka my_project % php bin/console doctrine:migrations:migrate

 WARNING! You are about to execute a migration in database "symfony_test" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]:
 > yes

[notice] Migrating up to DoctrineMigrations\Version20230729141041
[notice] finished in 18.7ms, used 22M memory, 1 migrations executed, 3 sql queries

 [OK] Successfully migrated to version : DoctrineMigrations\Version20230729141041

実行すると、"symfony_test"のスキーマを書き換えようとしているけど大丈夫か?的なことを聞かれるのでyesを。

すると、実行が完了します...!

ここまでできたら、MySQLサーバーに戻っていただいて中身を確認しておきましょう!

mysql> show tables;
+-----------------------------+
| Tables_in_symfony_test      |
+-----------------------------+
| comment                     |
| doctrine_migration_versions |
| messenger_messages          |
| user                        |
+-----------------------------+
4 rows in set (0.00 sec)

mysql> DESCRIBE user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int          | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | NO   |     | NULL    |                |
| age   | int          | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> DESCRIBE comment;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | int          | NO   | PRI | NULL    | auto_increment |
| user_id | int          | NO   |     | NULL    |                |
| message | varchar(255) | NO   |     | NULL    |                |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

無事テーブルができていそうです!!!

最後に、テーブルに適当なテストデータを入れて、Controllerから値を取得するところまで試してみます!

DBの値を取得して確認をしてみる!

さて、それでは最後にデータベースの値を適当に確認してみます!

保存は一旦MySQLのコマンドで行います。

そのため、先ほどのMySQLサーバーにて....

INSERT INTO user (name, age) VALUES
('User1', 20),
('User2', 25),
('User3', 30),
('User4', 35),
('User5', 40),
('User6', 45),
('User7', 50),
('User8', 55),
('User9', 60),
('User10', 65);

INSERT INTO comment (user_id, message) VALUES
(1, 'Message from User1'),
(2, 'Message from User2'),
(3, 'Message from User3'),
(4, 'Message from User4'),
(5, 'Message from User5'),
(6, 'Message from User6'),
(7, 'Message from User7'),
(8, 'Message from User8'),
(9, 'Message from User9'),
(10, 'Message from User10');

上記コマンドを実行すればそれぞれ10件づつのテストデータが入るはずです。(ChatGPTで作ったけどめっちゃ楽でした...w)

データを追加したら、CommentRepositoryにて、userIDでコメントのデータを取得できるようなメソッドを一つ追加します。

    // CommentRepository.phpに下記メソッドを追加

    ...
    /**
    * @return Comment[] Returns an array of Comment objects
    */
    public function findByUserId($userId): array
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.user_id = :user_id')
            ->setParameter('user_id', $userId)
            ->orderBy('c.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    ...

また、ControllerにてDI的にEntityManagerInterfaceを利用するためにはサービスの登録が必要そうだったため、"services.yaml"を一部修正します。

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:

services:
  # default configuration for services in *this* file
  _defaults:
    autowire: true # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

  # makes classes in src/ available to be used as services
  # this creates a service per class whose id is the fully-qualified class name
  App\:
    resource: "../src/"
    exclude:
      - "../src/DependencyInjection/"
      - "../src/Entity/"
      - "../src/Kernel.php"

  # ↓ここからを追加
  App\Controller\:
    resource: "../src/Controller"
    tags: ["controller.service_arguments"]
  # ↑ここまでを追加

  # add more service definitions when explicit configuration is needed
  # please note that last definitions always *replace* previous ones

最後に、LuckyController.php(前回の記事を参照)を下記のように修正します。

<?php
namespace App\Controller;

use App\Entity\User;
use App\Entity\Comment;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class LuckyController
{
    #[Route('/lucky/number')]
    public function number(EntityManagerInterface $entityManager): Response
    {
        $userId = 1; // テスト用に決め打ちで仮置き

        // userIdの情報をそれぞれ取得
        $user = $entityManager->getRepository(User::class)->find($userId);
        $comments = $entityManager->getRepository(Comment::class)->findByUserId($user->getId());

        // コメントは表示できる形に整形
        $commentStrings = array_map(function($comment) {
            return $comment->getMessage();
        }, $comments);
        $commentsOutput = implode('<br>', $commentStrings);

        return new Response(
            '<html>
                <body>
                    User: '.$user->getName() . '<br>Comment: '. $commentsOutput.'
                </body>
            </html>'
        );
    }
}

number要素何にもないやん!って感じですが許してください...w

さて、ここまで一通り保存することができたら、下記URLにアクセスしてみます...!

http://127.0.0.1:8001/lucky/number

そうすると...?

このような形でデータを取得し、表示することができました...!

これで、SymfonyでMySQLを設定して、migrationでDB, テーブルを作成し、簡単なデータを取得できることを確認できました...!

まとめ

いやぁ、長くなってしまいました...w

でも初めて触るフレームワークですが、やっぱりちゃんと動いてくれると楽しいですね!

筆者もまだまだSymfonyは初心者ですが、色々触っていけると楽しそうです!

おすすめの記事