Slim で Doctrine を使う

このクックブックエントリでは、広く使用されているDoctrine ORMをSlim 4アプリケーションにゼロから統合する方法について説明します。

アプリケーションへのDoctrineの追加

最初のステップは、composerを使用してDoctrine ORMをプロジェクトにインポートすることです。

composer require doctrine/orm:^3.0 doctrine/dbal:^4.0 symfony/cache

2021年4月30日、Doctrineはバージョンv2.0.0をリリースした際にdoctrine/cacheを正式に非推奨としました。これにより、そのライブラリからすべてのキャッシュ実装が削除されました。それ以来、PSR-6準拠の実装であるsymfony/cacheを使用することを推奨しています。本番環境でDoctrineのメタデータをキャッシュする場合にのみ必要ですが、実行することによるデメリットはありませんので、設定方法を示します。

PHP8に移行していない場合、またはエンティティのアノテーションに従来のPHPDocコメントを引き続き使用したい場合は、doctrine/annotationsパッケージもインポートする必要があります。これは以前はdoctrine/ormの依存関係でしたが、2.10.0以降はオプションとなっています。

composer require doctrine/annotations

最初のエンティティの定義

このステップをスキップして、実際のDoctrineエンティティを使用できます。以下は単なる例です。

PHP8属性を使用していることに注意してください。必要に応じて、PHPDocアノテーションに変換してください。

<?php

// src/Domain/User.php

use DateTimeImmutable;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Table;

#[Entity, Table(name: 'users')]
final class User
{
    #[Id, Column(type: 'integer'), GeneratedValue(strategy: 'AUTO')]
    private int $id;

    #[Column(type: 'string', unique: true, nullable: false, options: ['collation' => 'NOCASE'])]
    private string $email;

    #[Column(name: 'registered_at', type: 'datetimetz_immutable', nullable: false)]
    private DateTimeImmutable $registeredAt;

    public function __construct(string $email)
    {
        $this->email = $email;
        $this->registeredAt = new DateTimeImmutable('now');
    }

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

    public function getEmail(): string
    {
        return $this->email;
    }

    public function getRegisteredAt(): DateTimeImmutable
    {
        return $this->registeredAt;
    }
}
図1:Doctrineエンティティの例。

データベース資格情報の提供

次に、Slimの設定とともにDoctrineの設定を追加します。

<?php

// settings.php

define('APP_ROOT', __DIR__);

return [
    'settings' => [
        'slim' => [
            // Returns a detailed HTML page with error details and
            // a stack trace. Should be disabled in production.
            'displayErrorDetails' => true,

            // Whether to display errors on the internal PHP log or not.
            'logErrors' => true,

            // If true, display full errors with message and stack trace on the PHP log.
            // If false, display only "Slim Application Error" on the PHP log.
            // Doesn't do anything when 'logErrors' is false.
            'logErrorDetails' => true,
        ],

        'doctrine' => [
            // Enables or disables Doctrine metadata caching
            // for either performance or convenience during development.
            'dev_mode' => true,

            // Path where Doctrine will cache the processed metadata
            // when 'dev_mode' is false.
            'cache_dir' => APP_ROOT . '/var/doctrine',

            // List of paths where Doctrine will search for metadata.
            // Metadata can be either YML/XML files or PHP classes annotated
            // with comments or PHP8 attributes.
            'metadata_dirs' => [APP_ROOT . '/src/Domain'],

            // The parameters Doctrine needs to connect to your database.
            // These parameters depend on the driver (for instance the 'pdo_sqlite' driver
            // needs a 'path' parameter and doesn't use most of the ones shown in this example).
            // Refer to the Doctrine documentation to see the full list
            // of valid parameters: https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html
            'connection' => [
                'driver' => 'pdo_mysql',
                'host' => 'localhost',
                'port' => 3306,
                'dbname' => 'mydb',
                'user' => 'user',
                'password' => 'secret',
                'charset' => 'utf-8'
            ]
        ]
    ]
];
図2:Slim設定配列。

EntityManagerサービスの定義

ここで、EntityManagerサービスを定義します。これは、コードでORMと対話する主要なポイントです。

Slim 4では、独自のPSR-11コンテナ実装を提供する必要があります。この例では、シンプルで簡潔なPSR-11コンテナであるuma/dicを使用しています。お好みのコンテナに合わせて適宜変更してください。

従来、アノテーションメタデータリーダーが最も人気がありましたが、doctrine/orm 2.10.0以降、doctrine/annotationsへの依存関係はオプションとなり、プロジェクトはユーザーが最新のPHP8属性表記に移行することを推奨しています。

ここでは、PHP8属性を使用してメタデータリーダーを構成する方法を示します。PHP8に移行していない場合、または従来のPHPDocアノテーションを使用する場合は、Composerでdoctrine/annotationsを明示的に必要とし、次の例のようにSetup::createAttributeMetadataConfiguration(...)ではなくSetup::createAnnotationMetadataConfiguration(...)を呼び出す必要があります。

<?php

// bootstrap.php

use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Psr\Container\ContainerInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use UMA\DIC\Container;

require_once __DIR__ . '/vendor/autoload.php';

$container = new Container(require __DIR__ . '/settings.php');

$container->set(EntityManager::class, static function (Container $c): EntityManager {
    /** @var array $settings */
    $settings = $c->get('settings');

    // Use the ArrayAdapter or the FilesystemAdapter depending on the value of the 'dev_mode' setting
    // You can substitute the FilesystemAdapter for any other cache you prefer from the symfony/cache library
    $cache = $settings['doctrine']['dev_mode'] ?
        new ArrayAdapter() :
        new FilesystemAdapter(directory: $settings['doctrine']['cache_dir']);

    $config = ORMSetup::createAttributeMetadataConfiguration(
        $settings['doctrine']['metadata_dirs'],
        $settings['doctrine']['dev_mode'],
        null,
        $cache
    );

    $connection = DriverManager::getConnection($settings['doctrine']['connection']);

    return new EntityManager($connection, $config);
});

return $container;
図3:EntityManagerサービスの定義。

Doctrineコンソールの作成

データベースマイグレーションの実行、クラスアノテーションの検証などを行うには、doctrine CLIアプリケーションを作成します。このファイルは、コンテナで定義したEntityManagerサービスを取得し、ConsoleRunner::run(new SingleManagerProvider($em))に渡すだけです。

.php拡張子なしでこのファイルを作成し、実行可能にするのが一般的です。プロジェクトのルートまたはbin/サブディレクトリに配置できます。このファイルは#!/usr/bin/env phpで始まり、Linuxではchmod +x doctrineコマンドで実行可能にすることができます。

#!/usr/bin/env php
<?php

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
use Slim\Container;

/** @var Container $container */
$container = require_once __DIR__ . '/bootstrap.php';

ConsoleRunner::run(new SingleManagerProvider($container->get(EntityManager::class)));
図4:Doctrineコンソールアプリの有効化。

コンソールアプリが機能することを確認してください。正しく設定されている場合、出力はおおよそ次のようになります。

$ ./doctrine 
Doctrine Command Line Interface 3.1.1.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  completion                         Dump the shell completion script
  help                               Display help for a command
  list                               List commands
 dbal
  dbal:run-sql                       Executes arbitrary SQL directly from the command line.
 orm
  orm:clear-cache:metadata           Clear all metadata cache of the various cache drivers
  orm:clear-cache:query              Clear all query cache of the various cache drivers
  orm:clear-cache:region:collection  Clear a second-level cache collection region
  orm:clear-cache:region:entity      Clear a second-level cache entity region
  orm:clear-cache:region:query       Clear a second-level cache query region
  orm:clear-cache:result             Clear all result cache of the various cache drivers
  orm:generate-proxies               [orm:generate:proxies] Generates proxy classes for entity classes
  orm:info                           Show basic information about all mapped entities
  orm:mapping:describe               Display information about mapped objects
  orm:run-dql                        Executes arbitrary DQL directly from the command line
  orm:schema-tool:create             Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output
  orm:schema-tool:drop               Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output
  orm:schema-tool:update             Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata
  orm:validate-schema                Validate the mapping files
図5:Doctrine CLIの健全性チェック。

php vendor/bin/doctrine orm:schema-tool:createを実行することで、データベースを初期化し、スキーマをロードできます。

独自のコードでのEntityManagerの使用

おめでとうございます!これで、コマンドラインからデータベースを管理し、コードに必要な場所にEntityManagerを使用できるようになりました。


// bootstrap.php

$container->set(UserService::class, static function (Container $c) {
    return new UserService($c->get(EntityManager::class));
});

// src/UserService.php

final class UserService
{
    private EntityManager $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function signUp(string $email): User
    {
        $newUser = new User($email);

        $this->em->persist($newUser);
        $this->em->flush();

        return $newUser;
    }
}
図6:独自のコードでのEntityManagerサービスの使用。

その他の資料