はじめに
この記事ではORM(=Object Relational Mapper)を使うメリットやORMとはみたいな小難しい話はしません。フラットなPHPを書いていたPHPerがORMを使う世界に初めて来た場合、そもそもORMの1レコード=1オブジェクトという世界観に対してなかなかイメージがつかめないことがあるのを観測したので、そこのイメージギャップを埋められればと思って書いてみました。
そもそもクラス?オブジェクト?インスタンス?プロパティ?メソッド?みたいな用語面で躓いている場合は https://www.php.net/manual/ja/language.oop5.php 辺りを別タブで開いて行ったり来たりしながら読んでもらうと良いかもしれません。
データの読み取り
従来の考え方
上の図のようなデータベースの表に収められているデータをそのデータベースの形のまま配列として写し取ります。
PDOで書くとこんな感じです。
<?php
/** @var \PDO $pdo */
$statement = $pdo->query('SELECT id, title FROM books');
foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $item) {
$bookId = $item['id'];
$bookTitle = $item['title'];
$bookPrice = $item['price'];
}
ORMでの考え方
ORMの世界では一行一行のレコード(=1冊のbook)をBookクラスのインスタンスとして取り出します。
上の図のように、Bookクラスのインスタンスがテーブルの中に浮かんでいて、それを条件に応じて取り出して使うイメージです。
booksテーブルの一行に含まれるデータ(この例だとidとtitleとprice)はBookクラスから取り出します。
<?php
$books = $queryBuilder->findAll();
foreach ($books as $book) {
$bookId = $book->getId();
$bookTitle = $book->getTitle();
$bookPrice = $book->getPrice();
// 直接Bookのpublicプロパティとして取得できるライブラリもある
$bookId = $book->id;
$bookTitle = $book->title;
$bookPrice = $book->price;
}
上の例で $queryBuilder->findAl()
は実際にはPDOを使ってデータを取得しているのですが、
データベースから取得したデータをBookクラスのインスタンスにセットした状態で返してくれて、プログラマは
booksテーブルの一行分のレコードと一緒にBookクラスに実装された必要なメソッドを使ったり、Bookクラスを
型のように扱って、一行分のレコードを1つにまとめた状態でなにかの処理を行うクラスにそのまま渡すことが出来ます。
<?php
$book->getPriceWithTax(); // この$bookのDB上のpriceが1000だったら、10パーセントの消費税を加算して1100を返す
データの書き込み
本を新規に登録したり、本の価格を更新するときのことを考えてみます。
「本を新規に登録したい」「本の価格を更新したい」という人間用の言葉をデータベース用の言葉に書き直すと下記のようになります。
本を新規に登録したい=booksテーブルに新しく一行のレコードを作りたい(insertしたい)
本の価格を更新したい=booksテーブルの特定の一行のpriceを更新したい(updateしたい)
従来の考え方
本を登録したいときは、そのままINSERT文を構築して発行します。
本を更新したいときも、そのままUPDATE文を構築して発行します。
<?php
/** @var \PDO $pdo */
$pdo->exec('INSERT INTO books(id, title, price) VALUES(6, "本6", 2900)');
$pdo->exec('UPDATE books SET price = 1200 WHERE id = 1');
ORMでの考え方
Bookクラスのインスタンス $book
はそのままレコードの一行を表していると考えるので、新規登録したい場合はBookクラスのインスタンスである $book
を1つ新しく作り、それをデータベースに保存すると考えます。
<?php
$book = new Book(); // 新しいBookを作る
$book->setId(6);
$book->setTitle('本6');
$book->setPrice(2900);
同様に、本の価格を更新したいときは更新したい対象の $book
の price
プロパティを更新します。
<?php
$book->setPrice(1200); // いままで1000円で売られていた本の価格を1200円に更新
新しく作ったり価格を更新した $book
をどうやって保存するかは大きく分けて2つのやり方があります。
(ORMにより異なるので、自分の利用しているORMライブラリに合わせて読んでください)
アクティブレコードパターン(LaravelのEloquent, Ruby on Rails等)
$book
が保存用のメソッド( save()
など)を持っており、それを呼び出すことで保存します。
// $bookは新規に作られたBookのインスタンス、またはデータベースから呼び出した$bookのプロパティを更新したもの
$book->save();
実装としては $book
自体がデータベースへの接続インスタンス(典型的には $pdo
)を内部で持っており、save()が呼び出されるとINSERT文やUPDATE文を組み立てて実行してくれます。
DataMapperパターン(DoctrineORM, Hibernate等)
データベースに保存すべき、あるいはデータベースから取り出したレコードを表すインスタンスを一元管理する ObjectManager
や EntityManager
と呼ばれるクラスがあり、そのクラスに persist()
して flush()
することで保存します。
$entityManager->persist($book);
$entityManager->flush();
実装としては $book
自体でなく $entityManager
のほうがデータベースへの接続インスタンス(典型的には $pdo
)を内部に持っており、persist(), flush() が呼び出されるとINSERT文やUPDATE文を組み立てて実行してくれます。