1
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-12-07

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の責務から離れていることで、テストが非常にしやすくなっています。ぜひいちど試してもらえればと思います。

1
0
0

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
  3. You can use dark theme
What you can do with signing up
1
0