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