search
LoginSignup
0

posted at

updated at

SymfonyでDBを扱うセット、"ORM Pack"

Symfony Component Advent Calendar 2022の8日目の記事です。

最初に

SymfonyはPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
(SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。)

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。

SymfonyでDBを扱うセット、"ORM Pack"

ORM Packは、SymfonyでDBを扱うためのライブラリをセットにしたコンポーネントです。ほぼSymfony専用コンポーネントです。

個人的にSymfonyの推しポイントのひとつです。去年のアドベントカレンダーでも取り上げました。

インストール

composer require symfony/orm-pack

Doctrine

このORM PackにはDoctrineというライブラリが含まれており、これを利用してDBを扱います。
このDoctrineはData Mapper型のORMで、以下の特徴があります。

  • EntityがPOPO (Plain Old PHP Object、何にも依存しない純粋なオブジェクト)である。
  • DB参照はRepository、DBへの永続化はEntityManagerが行う
  • RepositoryやEntityManagerはオートワイヤリングにより、Entity/DependencyInjection以外であれば、どこでも注入可能

詳しくはこちら

LaravelのEloquentとの違いに関しては@77webさんのこちらのアドベントカレンダーをご覧ください。

Entity

Entityは上記の通りPOPOですが、DBのテーブルやフィールドとの紐付けはAttributesで行います。

Item.php
namespace App\Entity;

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

#[ORM\Entity(repositoryClass: ItemRepository::class)]
class Item
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

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

    #[ORM\Column]
    private ?int $price = null;

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getPrice(): ?int
    {
        return $this->price;
    }

    public function setPrice(int $price): self
    {
        $this->price = $price;

        return $this;
    }

#[ORM\Entity(repositoryClass: ItemRepository::class)]で紐づくRepositoryのクラス名を指定します。

#[ORM\Column]で紐づくテーブルのフィールドを指定します。指定がない場合は、プロパティ名をスネークケースしたフィールド名を自動的に紐づけます。
#[ORM\Id]はPKを表し、#[ORM\GeneratedValue]はオートインクリメント(正確には自動生成)を表します。

Repository

RepositoryはServiceEntityRepositoyの子クラスとなります。

ItemRepository.php
namespace App\Repository;

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

/**
 * @extends ServiceEntityRepository<Item>
 *
 * @method Item|null find($id, $lockMode = null, $lockVersion = null)
 * @method Item|null findOneBy(array $criteria, array $orderBy = null)
 * @method Item[]    findAll()
 * @method Item[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class ItemRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Item::class);
    }
}

parent::__construct()で紐づくEntityクラスを指定します。上記のコメントの通り、データを取得するメソッドがいくつか用意されています。

データの取得

上記の通り、既存の取得メソッドを使えば簡単にデータを取得できますが、複雑な条件の場合はQueryBuilderを利用します。

ItemRepository.php

    public function findWithComplexCondition(): array
    {
        $queryBuilder = $this->createQueryBuilder('i');
        $queryBuilder
            ->andWhere('i.name = :name')
            ->setParameter('name', '商品名')
            ->orderBy('i.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;            
    }

createQueryBuilder()では、引数にテーブルのエイリアス名を指定します。andWhere(), orWhere()などの条件で:{パラメータ名}を指定し、setParameter('パラメータ名', '値')を呼ぶことで、値をバインドすることができます。

データの保存、削除

データの保存はEntityManagerを使います。

ItemController.php
    
    public function create(Request $request, EntityManagerInterface $entityManager): Response
    {
        $item = new Item();
        $item
            ->setName($request->request->get('name'))
            ->setPrice($request->request->get('price'))
        ;
        
        $entityManager->persist($item); // DBで扱う(永続化)データとして登録
        $entityManager->flush(); // 永続化、DBと同期

        ...
    }

    public function delete(int $id, ItemRepository $itemRepository, EntityManagerInterface $entityManager): Response
    {
        $item = $itemRepository->find($id); // 該当データの取得
        
        $entityManager->remove($item); // DBで扱うデータから削除( = DBから削除)
        $entityManager->flush(); // DBと同期

    }

オートワイヤリングにより、ControllerのメソッドにEntityManagerInterfaceを引数指定すれば、EntityManagerオブジェクトが手に入ります。あとは、このオブジェクトでpersist(), flush()することで、データを保存できます。

データの削除もEntityManagerで行います。remove()で削除したいオブジェクトを指定することで、DB上から削除できます。

内部ではトランザクションを貼っていますが、flush()することで自動的にコミットします。明治的にトランザクションを貼ることも可能です。


$entityManager->transactional(function ($entityManager) {
    $item = new Item();
    ...
    $entityManager->persist($item);
});

ネストも可能です。

まとめ

今回はORM Packを紹介しました。SymfonyはActiveRecord型のORMと比べると、ちょっと特殊に感じるかもしれません。ただ、データの取得や保存がEntityの責務から離れていることで、テストが非常にしやすくなっています。ぜひいちど試してもらえればと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0