tom__bo’s Blog

MySQL!! MySQL!! @tom__bo

Doctrine ORMのチュートリアル(1)

最近PHPのマイクロフレームワークであるsilexを使っていて、そろそろORMも使っていきたいので、symfonyでもおなじみのDoctrine ORMのチュートリアルを英語の勉強も兼ねて和訳・実行していくことにしました。
英語がきっちり訳せず理解できず変な部分もあるので、公開後も修正していくと思います (||´ロ`)o
 
ではでは始めていきます( -_-)ノ
 
Getting Started with Doctrine 
 
以下のことがわかるらしい
・ Doctrine ORMのインスコと構成の仕方
・ PHPオブジェクトをDBにマッピングする
・ PHPオブジェクトからDBスキーマを生成する
・ EntityManagerを使ってDBにCRUD操作をする
 
Guide Assumptions
 
Doctirine ORM使ったことないビギナー向け。
以下は必要
・ PHP 5.4以上
・ composer
 
What is Doctrine?
 
Doctrine2はPHP5.4以上に向けて提供されるORM、ビジネスロジックDBMSの完全な分離を目的に作られています。
Doctrineを使う利点はオブジェクト指向におけるビジネスロジックに集中する事ができ、永続性に関しての不安を二の次にすることが出来ます。(これはDoctrine2が永続化を過小評価してるわけではありません。)ただ、永続化とエンティティが分離されていれば、OOPに利益をもたらすと信じています。
 
What are Entities?
 
エンティティとはプライマリキーやユニークな指定詞によって、多くのリクエストを実行できるPHPのオブジェクトです。これらのクラスは抽象クラスやインターフェースを拡張する必要はなく、finalのついたメソッドであってはなりません。
 
An Example Model: Bug Tracker
 
Zend Db TableのドキュメントにあるBag Tracker domain modelを利用するらしい。
詳細は書きませんが、どうやらユーザがバグのレポートをエンジニアに対して出し、エンジニアが解決したらレポートを閉じるようなシステムを想定しているようです。
システムの内容云々は今回はいいので、進めていきます。
 
Project Setup
 
{
    "require": {
        "doctrine/orm": "2.4.*",
        "symfony/yaml": "2.*"
    },
    "autoload": {
        "psr-0": {"": "src/"}
    }
}
 
をcomposer.jsonに書いて、composer install します。
 
 
でcomposer.pharを落としてきます。
 
チュートリ通り、以下のディレクトリを加えます
doctrine2-tutorial
|-- config
|   |-- xml
|   `-- yaml
`-- src
 
Obtaining the EntityManager
 
DoctrineのインターフェースはEntityManagerで、エンティティと永続化部分のライフサイクルマネジメントのアクセスポイントを提供しているらしい。自分のエンティティをdoctrineで構成し作成する必要があるようです。まずは作成し、徐々に議論していくらしいので、とにかく写経していきます。
 
<?php
// bootstrap.php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;

require_once "vendor/autoload.php";

// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);

// database configuration parameters
$conn = array(
    'driver' => 'pdo_sqlite',
    'path' => __DIR__ . '/db.sqlite',
);

// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);
 
詳細は大したことないので、省略。sqliteじゃなくてmysqlにしたいので、dbalのドキュメントに従って、$conn = …の部分を以下にしてみます。
 
$conn = array(
     ‘dbname’ => ‘dtest',
     ‘user’ => ‘silex’,
     ‘password’ => ‘password’,
     ‘host’ => ‘localhost’,
     ‘driver’ => ‘pro_mysql’,
);
 
Generating the Database Schema
 
これでマッピングとエンティティマネージャが出来たので、ここからRDBスキーマを生成します。Doctrineの提供してくれているコマンドラインインターフェースで実現してくれるらしい。
cli-config.phpをルートディレクトリに作って実行してみます。
<?php
// cli-config.php
require_once "bootstrap.php";

return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
 
実行コマンド
$ cd project/
$ vendor/bin/doctrine orm:schema-tool:create
 
説明でもあるようにNo Metadata Classes to processが出てもDon’t worry!!
     次の章でやってくれるらしい。個々ではエンティティのメタデータがDBスキーマと同期されてることが大事だと気づけばいいそうです。
以下のコマンドで、recreateできます。
$ vendor/bin/doctrine orm:schema-tool:drop --force
$ vendor/bin/doctrine orm:schema-tool:create

 
もしくはupdateを使います
$ vendor/bin/doctrine orm:schema-tool:update --force
 
 
Starting with the Product
 
 
ドキュメントに従って、productエンティティの定義から始めます。src/Product.phpに以下を作成します。
<?php
// src/Product.php
class Product
{
    /**
     * @var int
     */
    protected $id;
    /**
     * @var string
     */
    protected $name;

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

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

    public function setName($name)
    {
        $this->name = $name;
    }
}
 
idのセット以外はgetterとsetter(mutator)を作って定義します基本的にidに対してはsetterをかく必要はないようです。次にProductエンティティの構造をDoctrineのメタデータ言語を使って定義します。フィールド定義の部分を以下のようにします。
<?php
// src/Product.php
/**
 * @Entity @Table(name="products")
 **/
class Product
{
    /** @Id @Column(type="integer") @GeneratedValue **/
    protected $id;
    /** @Column(type="string") **/
    protected $name;

    // .. (other code)
}
 
クラス名がDBのテーブル名に、フィールド名がDBのカラム名に対応していることがわかります。ここでDBの構成をアップデートします。
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
実行すると
CREATE TABLE products (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
 
Updating database schema...
Database schema updated successfully! "1" queries were executed
と結果が返り、dtestデータベースにProductsテーブルが無事出来ていました!いい!
 
ここで新しいスクリプトでDBにレコードを追加してみます。
<?php
// create_product.php
require_once "bootstrap.php";

$newProductName = $argv[1];

$product = new Product();
$product->setName($newProductName);

$entityManager->persist($product);
$entityManager->flush();

echo "Created Product with ID " . $product->getId() . "\n";
 
コマンドラインからこれを呼び出すことで新しいレコードが追加されます。
$ php create_product.php ORM
$ php create_product.php DBAL
 
persist()とflush()が分離されていることで、flushが呼ばれたときにすべてのトランザクションを実行することが可能になっているのがわかります。これを利用することでDBに対する書き込みのパフォーマンスが格段に良くなるそうです。DoctrineはUnitOfWorkパターンに従うことでエンティティへの変更を検出できるようにしているそうです。

 

 

次のステップとして、すべてのプロダクトリストをフェッチしてみます。
<?php
// list_products.php
require_once "bootstrap.php";

$productRepository = $entityManager->getRepository('Product');
$products = $productRepository->findAll();

foreach ($products as $product) {
    echo sprintf("-%s\n", $product->getName());
}
 
EntityManagerのgetRepository()メソッドはエンティティごとのfinderオブジェクトを作成することができ、Doctrineによって提供されるfindAll()などのメソッドを使えるようになります。
<?php
// show_product.php <id>
require_once "bootstrap.php";

$id = $argv[1];
$product = $entityManager->find('Product', $id);

if ($product === null) {
    echo "No product found.\n";
    exit(1);
}

echo sprintf("-%s\n", $product->getName());
 
続けてIDに従ってプロダクトの名前を表示してみます。UnitOfWorkパターンを使うことでエンティティを見つけるだけでそれらの変更はDBに適用してくれます。
<?php
// update_product.php <id> <new-name>
require_once "bootstrap.php";

$id = $argv[1];
$newName = $argv[2];

$product = $entityManager->find('Product', $id);

if ($product === null) {
    echo "Product $id does not exist.\n";
    exit(1);
}

$product->setName($newName);

$entityManager->flush();
 
このスクリプトを存在するプロダクトに対して呼んだ後、show_product.phpスクリプトを呼ぶことでプロダクトの名前を変更することができます。
 
コード量も多くないですし、実行結果も想像がつくのでここでは試さず次にいこうと思います。
 
と思いましたがエンティティによってDBを操作することが出来たここまでで次回に続けます。
 
TSUDUKU ⊂゚U┬───┬~