建立数据库实体
ORM的本质在于将一个数据库(更确切的说是其中的表格)“转换”到一个PHP对象。于是我们不用类似“select * from …
”或者“insert into …
”这样的SQL语句来操作表格中的数据,而是改用更直观、也更不容易出错的方式。比如下面这段代码的最终运行效果是在rsywx
数据库的book_place
中插入了一个新的纪录。
$place = new BookPlace();
$place->setName('Common');
$manager->persist($place);
$manager->flush();
这样的过程也许比mysqli_query($connection, 'insert into …')
多了几行代码的输入,但是出错机会少,而且也更安全。
导入MySQL数据库
我们的应用开发里程中,数据库已经建立完成(见建立数据库一节)。所以,我们需要将数据库转换到ORM中可以操作的类。
在严格的MVC框架下,这样形成的类是M(odel)层。但由于在本应用中,我们将提供数据这一任务全部放置到API中完成,所以我们导入MySQL数据库形成M层并不是必须的。我们在本教程中还是这么做是为了后面一节样本数据的需要。
进入虚拟机中项目的根目录(/vagrant/symfony
),输入如下命令:
php bin/console doctrineimport AppBundle yml
其中:
bin/console
是SF的命令行接口,对SF框架应用的操作都要通过这个接口。doctrineimport
表明我们要导入一个数据库。AppBundle
表明导入的数据库要为AppBundle
这个包所用。yml
表明我们要用YAML格式保存导出的数据库信息。
命令顺利执行后,在symfony\src\AppBundle\Resources\config\doctrine\
目录下会多出几个文件,这几个文件一一与数据库中的表格对应。比如BookPlace.orm.yml
对应的就是book_place
数据库。而它的内容也是如此:
AppBundle\Entity\BookPlace:
type: entity
table: book_place
id:
id:
type: integer
nullable: false
options:
unsigned: false
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
nullable: false
length: 255
options:
fixed: false
lifecycleCallbacks: { }
如果我们回忆一下book_place
的结构,会看到这个yml文件对表格的描述是与该表格的定义完全一致的。
我们暂时不会深入讨论各个字段定义中各个选项的意义。而且在一般情况下,我更喜欢从数据库到类的映射方式。
生成实体类
导入了数据库后,我们执行如下命令来生成实体类:
php bin/console doctrineentities AppBundle
该命令会在AppBundle
目录下生成一个新目录Entity
,其中会有若干个PHP文件。这些文件一一与上一步生成的YML文件对应,也因此一一与数据库中的表格对应。比如BookPlace.php
文件对应的是BookPlace.orm.yml
文件,并进而对应的是book_place
这个表格。它的内容如下:
<?php
namespace AppBundle\Entity;
/**
* BookPlace
*/
class BookPlace
{
/**
* @var integer
*/
private $id;
/**
* @var string
*/
private $name;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
*
* @return BookPlace
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
}
这个文件中包含一个命名空间(AppBundle\Entity
)的声明和一个类(BookPlace
)的声明。而在类的声明中,包括了两部分:
- 第一部分是成员声明。在
BookPlace
类中,只有两个成员:一个是$id
,一个是$name
。它们分别于book_place
表格中的两个字段id
和name
对应。 - 第二部分是方法声明。一般情况下,对于每个成员,都有两个方法,一个是setter,一个是getter。对于只读字段或者应该由数据库引擎自动生成的字段(如本类中表明记录唯一性的
id
)就只有一个getter。
从数据库(表格)到ORM映射,再到PHP类声明,我们完成了将数据库加以对象化的步骤。
注意:上面产生的PHP文件是自动生成的。我们对这个文件和其中的类声明不应该做任何的改动。
我们只能修改YML文件然后用doctrineentities
来生成PHP文件,用doctrineupdate
命令更新数据库;或者直接操作数据库,并在此通过上面讲到的两个步骤来更新ORM和Entity。
ORM表述和Entity类中对表间关系的描述
在结束本小节之前,我们有必要看看ORM表述和Entity类中是怎样描述表之间的关系的。
我们的book_book
表格与若干表格有“1对多”的关系。比如一本书的出版社和book_publisher
,它的购买地点和book_place
都有1对多的关系。
在BookBook.orm.yml
中,我们可以找到这样一段:
AppBundle\Entity\BookBook:
... ...
manyToOne:
place:
targetEntity: BookPlace
cascade: { }
fetch: LAZY
mappedBy: null
inversedBy: null
joinColumns:
place:
referencedColumnName: id
orphanRemoval: false
publisher:
targetEntity: BookPublisher
cascade: { }
fetch: LAZY
mappedBy: null
inversedBy: null
joinColumns:
publisher:
referencedColumnName: id
orphanRemoval: false
lifecycleCallbacks: { }
在这里我们可以清楚看到,BookBook
是多端,它与两个1端对应。
而在BookBook.php
中,我们可以找到这样的代码:
<?php
namespace AppBundle\Entity;
/**
* BookBook
*/
class BookBook
{
... ...
/**
* @var \AppBundle\Entity\BookPlace
*/
private $place;
/**
* @var \AppBundle\Entity\BookPublisher
*/
private $publisher;
... ...
/**
* Set place
*
* @param \AppBundle\Entity\BookPlace $place
*
* @return BookBook
*/
public function setPlace(\AppBundle\Entity\BookPlace $place = null)
{
$this->place = $place;
return $this;
}
/**
* Get place
*
* @return \AppBundle\Entity\BookPlace
*/
public function getPlace()
{
return $this->place;
}
/**
* Set publisher
*
* @param \AppBundle\Entity\BookPublisher $publisher
*
* @return BookBook
*/
public function setPublisher(\AppBundle\Entity\BookPublisher $publisher = null)
{
$this->publisher = $publisher;
return $this;
}
/**
* Get publisher
*
* @return \AppBundle\Entity\BookPublisher
*/
public function getPublisher()
{
return $this->publisher;
}
}
针对place
和publisher
这两个字段,在book_book
中存放的只是一个ID(一个整数),但是在根据ORM生成的PHP类中,它们以各自PHP类出现(\AppBundle\Entity\BookPlace
和\AppBundle\Entity\BookPublisher
)。对应的,它们的setter和getter也是对这两个类的操作,而不是对id
这个字段本身的操作。
在本教程中,我们不再对此进行进一步的展开。