Databases and the Doctrine ORM

Screencast

スクリーンキャスト

Do you prefer video tutorials? Check out the Doctrine screencast series.

ビデオチュートリアルが好きですか? Doctrine スクリーンキャスト シリーズをご覧ください。

Symfony provides all the tools you need to use databases in your applications thanks to Doctrine, the best set of PHP libraries to work with databases. These tools support relational databases like MySQL and PostgreSQL and also NoSQL databases like MongoDB.

Symfony は、アプリケーションでデータベースを使用するために必要なすべてのツールを提供します。これは、データベースを操作するための最適な PHP ライブラリ セットである Doctrine のおかげです。これらのツールは、MySQL や PostgreSQL などのリレーショナル データベースと、MongoDB などの NoSQL データベースをサポートします。

Databases are a broad topic, so the documentation is divided in three articles:

データベースは幅広いトピックであるため、ドキュメントは次の 3 つの記事に分かれています。
  • This article explains the recommended way to work with relational databases in Symfony applications;
    この記事では、Symfony アプリケーションでリレーショナル データベースを操作するための推奨される方法について説明します。
  • Read this other article if you need low-level access to perform raw SQL queries to relational databases (similar to PHP's PDO);
    リレーショナル データベース (PHP の PDO と同様) に対して生の SQL クエリを実行するために低レベルのアクセスが必要な場合は、この別の記事をお読みください。
  • Read DoctrineMongoDBBundle docs if you are working with MongoDB databases.
    MongoDB データベースを使用している場合は、DoctrineMongoDBBundle のドキュメントをお読みください。

Installing Doctrine

First, install Doctrine support via the orm Symfony pack, as well as the MakerBundle, which will help generate some code:

まず、orm Symfony パックと MakerBundle を介して Doctrine サポートをインストールします。これにより、コードの生成が容易になります。
1
2
$ composer require symfony/orm-pack
$ composer require --dev symfony/maker-bundle

Configuring the Database

The database connection information is stored as an environment variable called DATABASE_URL. For development, you can find and customize this inside .env:

データベース接続情報は、DATABASE_URL という環境変数として保存されます。開発の場合、.env 内でこれを見つけてカスタマイズできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# .env (or override DATABASE_URL in .env.local to avoid committing your changes)

# customize this line!
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"

# to use mariadb:
DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=mariadb-10.5.8"

# to use sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"

# to use postgresql:
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"

# to use oracle:
# DATABASE_URL="oci8://db_user:db_password@127.0.0.1:1521/db_name"

Caution

注意

If the username, password, host or database name contain any character considered special in a URI (such as +, @, $, #, /, :, *, !), you must encode them. See RFC 3986 for the full list of reserved characters or use the urlencode function to encode them. In this case you need to remove the resolve: prefix in config/packages/doctrine.yaml to avoid errors: url: '%env(resolve:DATABASE_URL)%'

ユーザー名、パスワード、ホストまたはデータベース名に、URI で特殊と見なされる文字 (+、@、$、#、/、:、*、! など) が含まれている場合は、それらをエンコードする必要があります。予約文字の完全なリストについては、RFC 3986 を参照するか、urlencode 関数を使用してそれらをエンコードします。この場合、config/packages/doctrine.yaml の resolve: プレフィックスを削除して、エラーを回避する必要があります:url: '%env(resolve:DATABASE_URL)%'

Now that your connection parameters are setup, Doctrine can create the db_name database for you:

接続パラメータが設定されたので、Doctrine は db_name データベースを作成できます:
1
$ php bin/console doctrine:database:create

There are more options in config/packages/doctrine.yaml that you can configure, including your server_version (e.g. 5.7 if you're using MySQL 5.7), which may affect how Doctrine functions.

config/packages/doctrine.yaml には、Doctrine の機能に影響を与える可能性のある server_version (たとえば、MySQL 5.7 を使用している場合は 5.7) など、構成できるオプションが他にもあります。

Tip

ヒント

There are many other Doctrine commands. Run php bin/console list doctrine to see a full list.

Doctrine コマンドは他にもたくさんあります。完全なリストを表示するには、php bin/console list doctrine を実行します。

Creating an Entity Class

Suppose you're building an application where products need to be displayed. Without even thinking about Doctrine or databases, you already know that you need a Product object to represent those products.

製品を表示する必要があるアプリケーションを構築しているとします。Doctrine やデータベースについて考えなくても、これらの製品を表すために Product オブジェクトが必要であることはすでにわかっています。

You can use the make:entity command to create this class and any fields you need. The command will ask you some questions - answer them like done below:

make:entity コマンドを使用して、このクラスと必要なフィールドを作成できます。コマンドはいくつかの質問をします - 以下のように答えてください:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ php bin/console make:entity

Class name of the entity to create or update:
> Product

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

New property name (press <return> to stop adding fields):
> price

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

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

New property name (press <return> to stop adding fields):
>
(press enter again to finish)

Whoa! You now have a new src/Entity/Product.php file:

うわあ!新しい src/Entity/Product.php ファイルが作成されました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Entity/Product.php
namespace App\Entity;

use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as ORM;

 #[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private int $id;

    #[ORM\Column(length: 255)]
    private string $name;

    #[ORM\Column]
    private int $price;

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

    // ... getter and setter methods
}

Note

ノート

Starting in v1.44.0 - MakerBundle only supports entities using PHP attributes.

v1.44.0 以降 - MakerBundle は、PHP 属性を使用するエンティティのみをサポートします。

Note

ノート

Confused why the price is an integer? Don't worry: this is just an example. But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues.

なぜ価格が整数なのか混乱していますか?心配しないでください。これは単なる例です。ただし、価格を整数として保存すると (例: 100 = $1 USD)、丸めの問題を回避できます。

Note

ノート

If you are using an SQLite database, you'll see the following error: PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL column with default value NULL. Add a nullable=true option to the description property to fix the problem.

SQLite データベースを使用している場合、次のエラーが表示されます: PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULLcolumn with default value NULL.この問題を解決するには、description プロパティに nullable=true オプションを追加します。

Caution

注意

There is a limit of 767 bytes for the index key prefix when using InnoDB tables in MySQL 5.6 and earlier versions. String columns with 255 character length and utf8mb4 encoding surpass that limit. This means that any column of type string and unique=true must set its maximum length to 190. Otherwise, you'll see this error: "[PDOException] SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes".

MySQL 5.6 以前のバージョンで InnoDB テーブルを使用する場合、インデックス キー プレフィックスには 767 バイトの制限があります。 255 文字の長さと utf8mb4 エンコーディングの文字列列は、その制限を超えています。つまり、string 型で unique=true の列は、その最大長を 190 に設定する必要があります。そうしないと、次のエラーが表示されます:"[PDOException] SQLSTATE[42000]: Syntax error or access violation:1071 Specified key was too long; maxキーの長さは 767 バイトです。」

This class is called an "entity". And soon, you'll be able to save and query Product objects to a product table in your database. Each property in the Product entity can be mapped to a column in that table. This is usually done with attributes: the #[ORM\Column(...)] comments that you see above each property:

このクラスは「エンティティ」と呼ばれます。そしてすぐに、データベース内の製品テーブルに Productobjects を保存してクエリできるようになります。 Productentity の各プロパティは、そのテーブルの列にマップできます。これは通常、各プロパティの上に表示される属性: #[ORM\Column(...)] コメントで行われます。

The make:entity command is a tool to make life easier. But this is your code: add/remove fields, add/remove methods or update configuration.

make:entity コマンドは、作業を楽にするツールです。ただし、これはコードです。フィールドの追加/削除、メソッドの追加/削除、または構成の更新です。

Doctrine supports a wide variety of field types, each with their own options. To see a full list, check out Doctrine's Mapping Types documentation. If you want to use XML instead of annotations, add type: xml and dir: '%kernel.project_dir%/config/doctrine' to the entity mappings in your config/packages/doctrine.yaml file.

Doctrine はさまざまなフィールド タイプをサポートしており、それぞれに独自のオプションがあります。完全なリストを確認するには、Doctrine のマッピング タイプのドキュメントを参照してください。注釈の代わりに XML を使用する場合は、type: xml および dir: '%kernel.project_dir% を追加します。 /config/doctrine' を yourconfig/packages/doctrine.yaml ファイルのエンティティ マッピングに。

Caution

注意

Be careful not to use reserved SQL keywords as your table or column names (e.g. GROUP or USER). See Doctrine's Reserved SQL keywords documentation for details on how to escape these. Or, change the table name with #[ORM\Table(name: 'groups')] above the class or configure the column name with the name: 'group_name' option.

予約済みの SQL キーワードをテーブルまたは列の名前として使用しないように注意してください (例: GROUP または USER)。これらをエスケープする方法の詳細については、Doctrine の予約済み SQL キーワードのドキュメントを参照してください。または、クラスの上の #[ORM\Table(name: 'groups')] でテーブル名を変更するか、name: 'group_name' オプションで列名を設定します。

Migrations: Creating the Database Tables/Schema

The Product class is fully-configured and ready to save to a product table. If you just defined this class, your database doesn't actually have the product table yet. To add it, you can leverage the DoctrineMigrationsBundle, which is already installed:

Product クラスは完全に構成され、製品テーブルに保存する準備ができています。このクラスを定義したばかりの場合、データベースには実際にはまだ製品テーブルがありません。追加するには、すでにインストールされている DoctrineMigrationsBundle を利用できます。
1
$ php bin/console make:migration

If everything worked, you should see something like this:

すべてが機能した場合、次のように表示されます。
1
2
3
4
SUCCESS!

Next: Review the new migration "migrations/Version20211116204726.php"
Then: Run the migration with php bin/console doctrine:migrations:migrate

If you open this file, it contains the SQL needed to update your database! To run that SQL, execute your migrations:

このファイルを開くと、データベースの更新に必要な SQL が含まれています。その SQL を実行するには、移行を実行します。
1
$ php bin/console doctrine:migrations:migrate

This command executes all migration files that have not already been run against your database. You should run this command on production when you deploy to keep your production database up-to-date.

このコマンドは、データベースに対してまだ実行されていないすべての移行ファイルを実行します。実稼働データベースを最新の状態に保つために、展開するときにこのコマンドを実稼働で実行する必要があります。

Migrations & Adding more Fields

But what if you need to add a new field property to Product, like a description? You can edit the class to add the new property. But, you can also use make:entity again:

しかし、説明などの新しいフィールド プロパティを Product に追加する必要がある場合はどうすればよいでしょうか。クラスを編集して、新しいプロパティを追加できます。ただし、make:entity を再度使用することもできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ php bin/console make:entity

Class name of the entity to create or update
> Product

New property name (press <return> to stop adding fields):
> description

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

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

New property name (press <return> to stop adding fields):
>
(press enter again to finish)

This adds the new description property and getDescription() and setDescription() methods:

これにより、新しい description プロパティと getDescription() および setDescription() メソッドが追加されます。
1
2
3
4
5
6
7
8
9
10
11
12
// src/Entity/Product.php
  // ...

  class Product
  {
      // ...

+     #[ORM\Column(type: 'text')]
+     private $description;

      // getDescription() & setDescription() were also added
  }

The new property is mapped, but it doesn't exist yet in the product table. No problem! Generate a new migration:

新しいプロパティがマップされていますが、製品テーブルにはまだ存在していません。問題ない!新しい移行を生成します。
1
$ php bin/console make:migration

This time, the SQL in the generated file will look like this:

今回は、生成されたファイルの SQL は次のようになります。
1
ALTER TABLE product ADD description LONGTEXT NOT NULL

The migration system is smart. It compares all of your entities with the current state of the database and generates the SQL needed to synchronize them! Like before, execute your migrations:

移行システムはスマートです。すべてのエンティティをデータベースの現在の状態と比較し、それらを同期するために必要な SQL を生成します!前と同様に、移行を実行します。
1
$ php bin/console doctrine:migrations:migrate

This will only execute the one new migration file, because DoctrineMigrationsBundle knows that the first migration was already executed earlier. Behind the scenes, it manages a migration_versions table to track this.

DoctrineMigrationsBundle は最初の移行が以前に実行されたことを認識しているため、これは 1 つの新しい移行ファイルのみを実行します。舞台裏では、これを追跡するために migration_versions テーブルを管理します。

Each time you make a change to your schema, run these two commands to generate the migration and then execute it. Be sure to commit the migration files and execute them when you deploy.

スキーマを変更するたびに、これら 2 つのコマンドを実行して移行を生成し、それを実行します。必ず移行ファイルをコミットし、デプロイ時に実行してください。

Tip

ヒント

If you prefer to add new properties manually, the make:entity command can generate the getter & setter methods for you:

新しいプロパティを手動で追加したい場合は、make:entity コマンドで getter および setter メソッドを生成できます。
1
$ php bin/console make:entity --regenerate

If you make some changes and want to regenerate all getter/setter methods, also pass --overwrite.

いくつかの変更を行い、すべての getter/setter メソッドを再生成したい場合は、--overwrite も渡します。

Persisting Objects to the Database

It's time to save a Product object to the database! Let's create a new controller to experiment:

Product オブジェクトをデータベースに保存する時が来ました!実験する新しいコントローラーを作成しましょう。
1
$ php bin/console make:controller ProductController

Inside the controller, you can create a new Product object, set data on it, and save it:

コントローラー内で、新しい Product オブジェクトを作成し、データを設定して保存できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Controller/ProductController.php
namespace App\Controller;

// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ProductController extends AbstractController
{
    #[Route('/product', name: 'create_product')]
    public function createProduct(ManagerRegistry $doctrine): Response
    {
        $entityManager = $doctrine->getManager();

        $product = new Product();
        $product->setName('Keyboard');
        $product->setPrice(1999);
        $product->setDescription('Ergonomic and stylish!');

        // tell Doctrine you want to (eventually) save the Product (no queries yet)
        $entityManager->persist($product);

        // actually executes the queries (i.e. the INSERT query)
        $entityManager->flush();

        return new Response('Saved new product with id '.$product->getId());
    }
}

Try it out!

やってみて!

http://localhost:8000/product

http://localhost:8000/product

Congratulations! You just created your first row in the product table. To prove it, you can query the database directly:

おめでとう! product テーブルに最初の行を作成しました。それを証明するために、データベースに直接クエリを実行できます。
1
2
3
4
$ php bin/console dbal:run-sql 'SELECT * FROM product'

# on Windows systems not using Powershell, run this command instead:
# php bin/console dbal:run-sql "SELECT * FROM product"

Take a look at the previous example in more detail:

前の例を詳しく見てみましょう。
  • line 13 The ManagerRegistry $doctrine argument tells Symfony to inject the Doctrine service into the controller method.
    13 行目 ManagerRegistry の $doctrine 引数は、コントローラー メソッドに Doctrine サービスを注入するよう Symfony に指示します。
  • line 15 The $doctrine->getManager() method gets Doctrine's entity manager object, which is the most important object in Doctrine. It's responsible for saving objects to, and fetching objects from, the database.
    15 行目 $doctrine->getManager() メソッドは、Doctrine で最も重要なオブジェクトである Doctrine のセンティマネージャ オブジェクトを取得します。オブジェクトをデータベースに保存したり、データベースからオブジェクトをフェッチしたりします。
  • lines 17-20 In this section, you instantiate and work with the $product object like any other normal PHP object.
    行 17 ~ 20 このセクションでは、他の通常の PHP オブジェクトと同様に、$productobject をインスタンス化し、操作します。
  • line 23 The persist($product) call tells Doctrine to "manage" the $product object. This does not cause a query to be made to the database.
    23 行目 persist($product) 呼び出しは、Doctrine に $product オブジェクトを「管理」するように指示します。これにより、データベースに対してクエリが実行されることはありません。
  • line 26 When the flush() method is called, Doctrine looks through all of the objects that it's managing to see if they need to be persisted to the database. In this example, the $product object's data doesn't exist in the database, so the entity manager executes an INSERT query, creating a new row in the product table.
    26 行目 flush() メソッドが呼び出されると、Doctrine は管理しているすべてのオブジェクトを調べて、データベースに永続化する必要があるかどうかを確認します。この例では、$product オブジェクトのデータがデータベースに存在しないため、エンティティ マネージャーは INSERT クエリを実行し、product テーブルに新しい行を作成します。

Note

ノート

If the flush() call fails, a Doctrine\ORM\ORMException exception is thrown. See Transactions and Concurrency.

flush() 呼び出しが失敗した場合、Doctrine\ORM\ORMException 例外がスローされます。トランザクションと同時実行を参照してください。

Whether you're creating or updating objects, the workflow is always the same: Doctrine is smart enough to know if it should INSERT or UPDATE your entity.

オブジェクトを作成する場合でも更新する場合でも、ワークフローは常に同じです。Doctrine は、エンティティを INSERT するか UPDATE するかを判断できるほどスマートです。

Validating Objects

The Symfony validator reuses Doctrine metadata to perform some basic validation tasks:

Symfony バリデーターは Doctrine メタデータを再利用していくつかの基本的な検証タスクを実行します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
// ...

class ProductController extends AbstractController
{
    #[Route('/product', name: 'create_product')]
    public function createProduct(ValidatorInterface $validator): Response
    {
        $product = new Product();
        // This will trigger an error: the column isn't nullable in the database
        $product->setName(null);
        // This will trigger a type mismatch error: an integer is expected
        $product->setPrice('1999');

        // ...

        $errors = $validator->validate($product);
        if (count($errors) > 0) {
            return new Response((string) $errors, 400);
        }

        // ...
    }
}

Although the Product entity doesn't define any explicit validation configuration, Symfony introspects the Doctrine mapping configuration to infer some validation rules. For example, given that the name property can't be null in the database, a NotNull constraint is added automatically to the property (if it doesn't contain that constraint already).

Product エンティティは明示的な検証構成を定義していませんが、Symfony は Doctrinemapping 構成をイントロスペクトしていくつかの検証ルールを推測します。たとえば、データベースで name プロパティを null にできない場合、NotNull 制約がプロパティに自動的に追加されます (その制約がまだ含まれていない場合)。

The following table summarizes the mapping between Doctrine metadata and the corresponding validation constraints added automatically by Symfony:

次の表は、Doctrine メタデータと、Symfony によって自動的に追加される対応する検証制約との間のマッピングをまとめたものです。
Doctrine attribute Validation constraint Notes
nullable=false NotNull Requires installing the PropertyInfo component
type Type Requires installing the PropertyInfo component
unique=true UniqueEntity  
length Length  

Because the Form component as well as API Platform internally use the Validator component, all your forms and web APIs will also automatically benefit from these automatic validation constraints.

フォーム コンポーネントと API プラットフォームは Validator コンポーネントを内部で使用するため、すべてのフォームと Web API も自動的にこれらの自動検証制約の恩恵を受けます。

This automatic validation is a nice feature to improve your productivity, but it doesn't replace the validation configuration entirely. You still need to add some validation constraints to ensure that data provided by the user is correct.

この自動検証は生産性を向上させる優れた機能ですが、検証構成を完全に置き換えるわけではありません。ユーザーによって提供されたデータが正しいことを確認するために、いくつかの検証制約を追加する必要があります。

Fetching Objects from the Database

Fetching an object back out of the database is even easier. Suppose you want to be able to go to /product/1 to see your new product:

データベースからオブジェクトを取得するのはさらに簡単です。 /product/1 にアクセスして新しい製品を表示できるようにしたいとします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}', name: 'product_show')]
    public function show(ManagerRegistry $doctrine, int $id): Response
    {
        $product = $doctrine->getRepository(Product::class)->find($id);

        if (!$product) {
            throw $this->createNotFoundException(
                'No product found for id '.$id
            );
        }

        return new Response('Check out this great product: '.$product->getName());

        // or render a template
        // in the template, print things with {{ product.name }}
        // return $this->render('product/show.html.twig', ['product' => $product]);
    }
}

Another possibility is to use the ProductRepository using Symfony's autowiring and injected by the dependency injection container:

もう 1 つの可能性は、Symfony のオートワイヤリングを使用して ProductRepository を使用し、依存性注入コンテナーによって注入されることです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}', name: 'product_show')]
    public function show(int $id, ProductRepository $productRepository): Response
    {
        $product = $productRepository
            ->find($id);

        // ...
    }
}

Try it out!

やってみて!

http://localhost:8000/product/1

http://localhost:8000/product/1

When you query for a particular type of object, you always use what's known as its "repository". You can think of a repository as a PHP class whose only job is to help you fetch entities of a certain class.

特定のタイプのオブジェクトを照会するときは、常に「リポジトリ」と呼ばれるものを使用します。リポジトリは、特定のクラスのエンティティの取得を支援することだけを目的とした PHP クラスと考えることができます。

Once you have a repository object, you have many helper methods:

リポジトリ オブジェクトを作成すると、多くのヘルパー メソッドが作成されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$repository = $doctrine->getRepository(Product::class);

// look for a single Product by its primary key (usually "id")
$product = $repository->find($id);

// look for a single Product by name
$product = $repository->findOneBy(['name' => 'Keyboard']);
// or find by name and price
$product = $repository->findOneBy([
    'name' => 'Keyboard',
    'price' => 1999,
]);

// look for multiple Product objects matching the name, ordered by price
$products = $repository->findBy(
    ['name' => 'Keyboard'],
    ['price' => 'ASC']
);

// look for *all* Product objects
$products = $repository->findAll();

You can also add custom methods for more complex queries! More on that later in the Databases and the Doctrine ORM section.

より複雑なクエリ用のカスタム メソッドを追加することもできます。これについては、後のデータベースと Doctrine ORM セクションで詳しく説明します。

Tip

ヒント

When rendering an HTML page, the web debug toolbar at the bottom of the page will display the number of queries and the time it took to execute them:

HTML ページをレンダリングすると、ページの下部にある Web デバッグ ツールバーに、クエリの数と実行にかかった時間が表示されます。

If the number of database queries is too high, the icon will turn yellow to indicate that something may not be correct. Click on the icon to open the Symfony Profiler and see the exact queries that were executed. If you don't see the web debug toolbar, install the profiler Symfony pack by running this command: composer require --dev symfony/profiler-pack.

データベース クエリの数が多すぎる場合、アイコンが黄色に変わり、何かが正しくない可能性があることを示します。アイコンをクリックして Symfony プロファイラーを開き、実行された正確なクエリを確認します。 Web デバッグ ツールバーが表示されない場合は、次のコマンドを実行してプロファイラー Symfony パックをインストールします: composer require --dev symfony/profiler-pack。

Automatically Fetching Objects (EntityValueResolver)

6.2

6.2

Entity Value Resolver was introduced in Symfony 6.2.

Entity Value Resolver は Symfony 6.2 で導入されました。

2.7.1

2.7.1

Autowiring of the EntityValueResolver was introduced in DoctrineBundle 2.7.1.

EntityValueResolver の自動配線は DoctrineBundle 2.7.1 で導入されました。

In many cases, you can use the EntityValueResolver to do the query for you automatically! You can simplify the controller to:

多くの場合、EntityValueResolver を使用して自動的にクエリを実行できます。コントローラーを次のように単純化できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}')]
    public function show(Product $product): Response
    {
        // use the Product!
        // ...
    }
}

That's it! The bundle uses the {id} from the route to query for the Product by the id column. If it's not found, a 404 page is generated.

それでおしまい!バンドルは、ルートからの {id} を使用して、id 列によって Product を照会します。見つからない場合は、404 ページが生成されます。

This behavior is enabled by default on all your controllers. You can disable it by setting the doctrine.orm.controller_resolver.auto_mapping config option to false.

この動作は、すべてのコントローラーでデフォルトで有効になっています。 doctrine.orm.controller_resolver.auto_mappingconfig オプションを false に設定することで無効にできます。

When disabled, you can enable it individually on the desired controllers by using the MapEntity attribute:

無効になっている場合は、MapEntity 属性を使用して、目的のコントローラーで個別に有効にすることができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/{id}')]
    public function show(
        #[MapEntity]
        Product $product
    ): Response {
        // use the Product!
        // ...
    }
}

Tip

ヒント

When enabled globally, it's possible to disabled the behavior on a specific controller, by using the MapEntity set to disabled.

グローバルに有効にすると、無効に設定された MapEntity を使用して、特定のコントローラーの動作を無効にすることができます。
public function show(
#[CurrentUser] #[MapEntity(disabled: true)] User $user
#[CurrentUser]#[MapEntity(disabled: true)]User $user
): Response {
// User is not resolved by the EntityValueResolver // ...
// ユーザーは EntityValueResolver によって解決されません // ...

}

}

Fetch Automatically

If your route wildcards match properties on your entity, then the resolver will automatically fetch them:

ルート ワイルドカードがエンティティのプロパティと一致する場合、リゾルバーはそれらを自動的に取得します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * Fetch via primary key because {id} is in the route.
 */
#[Route('/product/{id}')]
public function showByPk(Post $post): Response
{
}

/**
 * Perform a findOneBy() where the slug property matches {slug}.
 */
#[Route('/product/{slug}')]
public function showBySlug(Post $post): Response
{
}

Automatic fetching works in these situations:

自動取得は、次の状況で機能します。
  • If {id} is in your route, then this is used to fetch by primary key via the find() method.
    {id} がルートにある場合、これは find() メソッドを介して主キーで取得するために使用されます。
  • The resolver will attempt to do a findOneBy() fetch by using all of the wildcards in your route that are actually properties on your entity (non-properties are ignored).
    リゾルバは、実際にはエンティティのプロパティであるルート内のすべてのワイルドカードを使用して、findOneBy() フェッチを実行しようとします (プロパティ以外は無視されます)。

You can control this behavior by actually adding the MapEntity attribute and using the MapEntity options.

実際に MapEntity 属性を追加し、MapEntity オプションを使用することで、この動作を制御できます。

Fetch via an Expression

If automatic fetching doesn't work, you can write an expression using the ExpressionLanguage component:

自動フェッチが機能しない場合は、ExpressionLanguage コンポーネントを使用して式を記述できます。
1
2
3
4
5
6
#[Route('/product/{product_id}')]
public function show(
    #[MapEntity(expr: 'repository.find(product_id)')]
    Product $product
): Response {
}

In the expression, the repository variable will be your entity's Repository class and any route wildcards - like {product_id} are available as variables.

式では、リポジトリ変数はエンティティのRepository クラスになり、任意のルート ワイルドカード ({product_id} など) を変数として使用できます。

This can also be used to help resolve multiple arguments:

これは、複数の引数を解決するためにも使用できます。
1
2
3
4
5
6
7
#[Route('/product/{id}/comments/{comment_id}')]
public function show(
    Product $product,
    #[MapEntity(expr: 'repository.find(comment_id)')]
    Comment $comment
): Response {
}

In the example above, the $product argument is handled automatically, but $comment is configured with the attribute since they cannot both follow the default convention.

上記の例では、$product 引数は自動的に処理されますが、両方ともデフォルトの規則に従うことができないため、$comment は属性で構成されます。

MapEntity Options

A number of options are available on the MapEntity annotation to control behavior:

動作を制御するために、MapEntity アノテーションでいくつかのオプションを使用できます。
id

If an id option is configured and matches a route parameter, then the resolver will find by the primary key:

id オプションが構成されていて、ルート パラメーターと一致する場合、リゾルバーは主キーで検索します。
1
2
3
4
5
6
#[Route('/product/{product_id}')]
public function show(
    #[MapEntity(id: 'product_id')]
    Product $product
): Response {
}
mapping

Configures the properties and values to use with the findOneBy() method: the key is the route placeholder name and the value is the Doctrine property name:

findOneBy() メソッドで使用するプロパティと値を設定します: キーはルート プレースホルダー名で、値は Doctrine プロパティ名です:
1
2
3
4
5
6
7
8
#[Route('/product/{category}/{slug}/comments/{comment_slug}')]
public function show(
    #[MapEntity(mapping: ['category' => 'category', 'slug' => 'slug'])]
    Product $product,
    #[MapEntity(mapping: ['comment_slug' => 'slug'])]
    Comment $comment
): Response {
}
exclude

Configures the properties that should be used in the findOneBy() method by excluding one or more properties so that not all are used:

すべてが使用されないように、1 つ以上のプロパティを除外して、findOneBy() メソッドで使用する必要があるプロパティを構成します。
1
2
3
4
5
6
7
#[Route('/product/{slug}/{date}')]
public function show(
    #[MapEntity(exclude: ['date'])]
    Product $product,
    \DateTime $date
): Response {
}
stripNull
If true, then when findOneBy() is used, any values that are null will not be used for the query.
true の場合、findOneBy() を使用すると、null の値はクエリに使用されません。
entityManager

By default, the EntityValueResolver uses the default entity manager, but you can configure this:

デフォルトでは、EntityValueResolver は defaultentity マネージャーを使用しますが、これを構成できます。
1
2
3
4
5
6
#[Route('/product/{id}')]
public function show(
    #[MapEntity(entityManager: ['foo'])]
    Product $product
): Response {
}
evictCache
If true, forces Doctrine to always fetch the entity from the database instead of cache.
true の場合、Doctrine は常にキャッシュではなくデータベースからエンティティを取得します。
disabled
If true, the EntityValueResolver will not try to replace the argument.
true の場合、EntityValueResolver は引数を置き換えようとしません。

Updating an Object

Once you've fetched an object from Doctrine, you interact with it the same as with any PHP model:

Doctrine からオブジェクトを取得したら、他の PHP モデルと同じように操作します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...

class ProductController extends AbstractController
{
    #[Route('/product/edit/{id}', name: 'product_edit')]
    public function update(ManagerRegistry $doctrine, int $id): Response
    {
        $entityManager = $doctrine->getManager();
        $product = $entityManager->getRepository(Product::class)->find($id);

        if (!$product) {
            throw $this->createNotFoundException(
                'No product found for id '.$id
            );
        }

        $product->setName('New product name!');
        $entityManager->flush();

        return $this->redirectToRoute('product_show', [
            'id' => $product->getId()
        ]);
    }
}

Using Doctrine to edit an existing product consists of three steps:

Doctrine を使用して既存の製品を編集するには、次の 3 つのステップで構成されます。
  1. fetching the object from Doctrine;
    Doctrine からオブジェクトを取得します。
  2. modifying the object;
    オブジェクトを変更します。
  3. calling flush() on the entity manager.
    エンティティマネージャで flush() を呼び出します。

You can call $entityManager->persist($product), but it isn't necessary: Doctrine is already "watching" your object for changes.

$entityManager->persist($product) を呼び出すことができますが、必須ではありません: Doctrine はすでにオブジェクトの変更を「監視」しています。

Deleting an Object

Deleting an object is very similar, but requires a call to the remove() method of the entity manager:

オブジェクトの削除は非常に似ていますが、エンティティ マネージャーの remove() メソッドを呼び出す必要があります。
1
2
$entityManager->remove($product);
$entityManager->flush();

As you might expect, the remove() method notifies Doctrine that you'd like to remove the given object from the database. The DELETE query isn't actually executed until the flush() method is called.

ご想像のとおり、remove() メソッドは、指定されたオブジェクトをデータベースから削除したいことを Doctrine に通知します。 flush() メソッドが呼び出されるまで、DELETE クエリは実際には実行されません。

Querying for Objects: The Repository

You've already seen how the repository object allows you to run basic queries without any work:

リポジトリ オブジェクトを使用すると、基本的なクエリを何もせずに実行できることがわかりました。
1
2
3
// from inside a controller
$repository = $doctrine->getRepository(Product::class);
$product = $repository->find($id);

But what if you need a more complex query? When you generated your entity with make:entity, the command also generated a ProductRepository class:

しかし、より複雑なクエリが必要な場合はどうすればよいでしょうか? make:entity でエンティティを生成すると、コマンドによって ProductRepository クラスも生成されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Repository/ProductRepository.php
namespace App\Repository;

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

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

When you fetch your repository (i.e. ->getRepository(Product::class)), it is actually an instance of this object! This is because of the repositoryClass config that was generated at the top of your Product entity class.

リポジトリを取得すると (つまり ->getRepository(Product::class))、実際にはこのオブジェクトのインスタンスです!これは、Product エンティティ クラスの先頭で生成された repositoryClassconfig が原因です。

Suppose you want to query for all Product objects greater than a certain price. Add a new method for this to your repository:

特定の価格を超えるすべての Product オブジェクトを照会するとします。このための新しいメソッドをリポジトリに追加します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Product::class);
    }

    /**
     * @return Product[]
     */
    public function findAllGreaterThanPrice(int $price): array
    {
        $entityManager = $this->getEntityManager();

        $query = $entityManager->createQuery(
            'SELECT p
            FROM App\Entity\Product p
            WHERE p.price > :price
            ORDER BY p.price ASC'
        )->setParameter('price', $price);

        // returns an array of Product objects
        return $query->getResult();
    }
}

The string passed to createQuery() might look like SQL, but it is Doctrine Query Language. This allows you to type queries using commonly known query language, but referencing PHP objects instead (i.e. in the FROM statement).

createQuery() に渡される文字列は SQL のように見えるかもしれませんが、Doctrine Query Language です。これにより、一般的に知られているクエリ言語を使用してクエリを入力できますが、代わりに PHP オブジェクトを参照します (つまり、FROM ステートメントで)。

Now, you can call this method on the repository:

これで、リポジトリでこのメソッドを呼び出すことができます:
1
2
3
4
5
6
// from inside a controller
$minPrice = 1000;

$products = $doctrine->getRepository(Product::class)->findAllGreaterThanPrice($minPrice);

// ...

See Service Container for how to inject the repository into any service.

リポジトリを任意のサービスに注入する方法については、Service Container を参照してください。

Querying with the Query Builder

Doctrine also provides a Query Builder, an object-oriented way to write queries. It is recommended to use this when queries are built dynamically (i.e. based on PHP conditions):

Doctrine は、クエリを作成するためのオブジェクト指向の方法である Query Builder も提供します。クエリが動的に構築されている場合 (つまり、PHP 条件に基づいている場合)、これを使用することをお勧めします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function findAllGreaterThanPrice(int $price, bool $includeUnavailableProducts = false): array
    {
        // automatically knows to select Products
        // the "p" is an alias you'll use in the rest of the query
        $qb = $this->createQueryBuilder('p')
            ->where('p.price > :price')
            ->setParameter('price', $price)
            ->orderBy('p.price', 'ASC');

        if (!$includeUnavailableProducts) {
            $qb->andWhere('p.available = TRUE');
        }

        $query = $qb->getQuery();

        return $query->execute();

        // to get just one result:
        // $product = $query->setMaxResults(1)->getOneOrNullResult();
    }
}

Querying with SQL

In addition, you can query directly with SQL if you need to:

さらに、次のことが必要な場合は、SQL で直接クエリを実行できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Repository/ProductRepository.php

// ...
class ProductRepository extends ServiceEntityRepository
{
    public function findAllGreaterThanPrice(int $price): array
    {
        $conn = $this->getEntityManager()->getConnection();

        $sql = '
            SELECT * FROM product p
            WHERE p.price > :price
            ORDER BY p.price ASC
            ';
        $stmt = $conn->prepare($sql);
        $resultSet = $stmt->executeQuery(['price' => $price]);

        // returns an array of arrays (i.e. a raw data set)
        return $resultSet->fetchAllAssociative();
    }
}

With SQL, you will get back raw data, not objects (unless you use the NativeQuery functionality).

SQL を使用すると、オブジェクトではなく生データが返されます (NativeQuery 機能を使用しない場合)。

Configuration

See the Doctrine config reference.

Doctrine 構成リファレンスを参照してください。

Relationships and Associations

Doctrine provides all the functionality you need to manage database relationships (also known as associations), including ManyToOne, OneToMany, OneToOne and ManyToMany relationships.

Doctrine は、ManyToOne、OneToMany、OneToOne、ManyToManyrelationships など、データベースの関係 (関連付けとも呼ばれます) を管理するために必要なすべての機能を提供します。

For info, see How to Work with Doctrine Associations / Relations.

詳細については、Doctrine Associations / Relations を使用する方法を参照してください。

Database Testing

Read the article about testing code that interacts with the database.

データベースと対話するコードのテストに関する記事を読んでください。

Doctrine Extensions (Timestampable, Translatable, etc.)

Doctrine community has created some extensions to implement common needs such as "set the value of the createdAt property automatically when creating an entity". Read more about the available Doctrine extensions and use the StofDoctrineExtensionsBundle to integrate them in your application.

Doctrine コミュニティは、「エンティティの作成時に createdAt プロパティの値を自動的に設定する」などの一般的なニーズを実装するために、いくつかの拡張機能を作成しました。

Learn more