(Symfony2/Doctrine2) Entity の OneToMany のアノテーションを自動生成させる
Symfony2 で、Entity の OneToMany アノテーションを自動生成させる方法についてです。Doctrineのライブラリに手を加えて実現します。
対象バージョンは Doctrine2.3.2。
じゃーいくよ。
まず基本から。Symfony2 での Entity の生成の仕方はこう。
DBのスキーマ情報をXMLに落とす(このXML作っても無視されるみたいだけど…)
app/console doctrine:mapping:convert xml src/Monmon/HmmBundle/Resources/config/doctrine/metadata/orm --from-database --force
Entity を生成。DBのカラムに対応するフィールドのみ。
app/console doctrine:mapping:import MonmonHmmBundle annotation
Entity の中身のフィールドだけ見て、getter / setter をセットする。
app/console doctrine:generate:entities MonmonHmmBundle
外部キーが張ってあるテーブルについては、参照先のエンティティを指すフィールドも作ってくれます。 ただし FROM テーブルから TO テーブルの参照(ManyToOne)だけ。逆の参照(OneToMany)はなし。
例。テーブル構造がこんなだとすると、
CREATE TABLE `category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`name` varchar(128) NOT NULL,
`price` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`),
CONSTRAINT `product_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Entityはこんな感じ。Product の方に $category フィールドが出来てるのに注目。
// src/Monmon/HmmBundle/Entity/Category.php
/**
* Category
*
* @ORM\Table(name="category")
* @ORM\Entity
*/
class Category
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=128, nullable=false)
*/
private $name;
// 以下略
}
// src/Monmon/HmmBundle/Entity/Product.php
/**
* Product
*
* @ORM\Table(name="product")
* @ORM\Entity
*/
class Product
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=128, nullable=false)
*/
private $name;
/**
* @var integer
*
* @ORM\Column(name="price", type="integer", nullable=false)
*/
private $price;
/**
* @var \Category
*
* @ORM\ManyToOne(targetEntity="Category") // ←「ManyToOne」アノテーションで表現
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="category_id", referencedColumnName="id")
* })
*/
private $category; // ←Category Entity への参照ができてる!
// 以下略
}
でもこれだけだと、こういうことはできるけど、
{{ product.category.name }}
これが出来ないよね。
{% for product in category.products %}
{{ product.name }}<br>
{% endfor %}
でもこれやりたい場面ってしょっちゅうあるじゃん。
なんでこれ自動生成してくれないんだー?
$products フィールド作って ManyToOne アノテーションつけといてくれよー。
どこかにやり方はないのかー?
って探してたら、ありました。
Doctrine のライブラリそのものを書き換えてしまいます。抵抗ある人は回れ右。修正するファイルは↓こちら。
vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php
ですが、これにはちょっとしたバグがありました。
あるテーブルから1つのテーブルに対し2つ以上の外部キーを張ってると、「フィールドが重複してんぞー」ってエラー吐かれて生成出来ません。
それってどんな場合かって?たとえばこんな。
CREATE TABLE `city` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `distance` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`city1_id` int(11) NOT NULL,
`city2_id` int(11) NOT NULL,
`price` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `city1_id` (`city1_id`),
KEY `city2_id` (`city2_id`),
CONSTRAINT `distance_ibfk_1` FOREIGN KEY (`city1_id`) REFERENCES `city` (`id`),
CONSTRAINT `distance_ibfk_2` FOREIGN KEY (`city2_id`) REFERENCES `city` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
都市間の距離を表す distance テーブルに city テーブルへの参照が2つあるため、 City エンティティに同名の $distance フィールドを2つ載せようとして失敗します。
$ app/console doctrine:mapping:import MonmonHmmBundle annotation
[Doctrine\ORM\Mapping\MappingException]
Property "distance" in "City" was already declared, but it must be declared only once
こういう場合ってどうするのが正しいんでしょうねー?
$distances1, $distances2 って連番にする?
僕はめんどくさかったので「同じテーブルから複数外部キーが張られてる場合は OneToMany 参照は持たせない!」てことにしちゃいました。
パッチはこちら。 https://github.com/monmonmon/autogen-onetomany-annotations
symfony2 のプロジェクトトップでパッチをあててね。
$ patch -p0 < patch.txt
patching file vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php
で、Entityを再生成してみると、
てってれー。Category に $product フィールドが追加されました。
/**
* Category
*
* @ORM\Table(name="category")
* @ORM\Entity
*/
class Category
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=128, nullable=false)
*/
private $name;
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $product; // ( ´ ▽ ` )ノ
/**
* Constructor
*/
public function __construct()
{
// コンストラクタも追加されます
$this->product = new \Doctrine\Common\Collections\ArrayCollection();
}
// 以下略
複数形 $products にしたいとこですけど。あとで直すかも。
じゃね。