mirror of
https://gitee.com/ShopeX/ECShopX
synced 2026-08-06 03:56:24 +08:00
删除文件 docs/docs/framework_bundle_database-base.md
This commit is contained in:
@@ -1,397 +0,0 @@
|
||||
---
|
||||
url: framework_bundle_database-base
|
||||
---
|
||||
|
||||
# 快速入门
|
||||
|
||||
对于任何应用程序来说,一个最常见和最具挑战的任务,就是从数据库中读取和持久化数据信息。在 ECShopx 中我们采用 Doctrine ORM 替代 Laravel 自带的 Eloquent ORM ,前者更灵活,且自带 [Repository 模式](framework_architecture-repository)。本节将为大家介绍如何使用 Doctrine ORM。
|
||||
|
||||
<a name="bdfb33d0"></a>
|
||||
## 几个概念
|
||||
|
||||
<a name="2f9cd18a"></a>
|
||||
### 什么是 Doctrine ORM ?
|
||||
|
||||
Doctrine ORM 是 PHP 7.1+ 的对象关系映射器(ORM),可以将PHP对象持久化到数据库中。它以Data Mapper模式为核心,旨在将您的业务逻辑与数据库中的持久化完全分离。
|
||||
|
||||
<a name="3c4ce663"></a>
|
||||
### 什么是 Entities ?
|
||||
|
||||
实体是简单的PHP对象,其含可持久化的属性,可持久属性是实体的一个实例变量,通过Doctrine的数据映射功能可以将其保存到数据库中或从数据库中检索出来。实体类不需要扩展任何抽象基类或接口。
|
||||
|
||||
实体类不能为final,尽管它可以包含final方法。
|
||||
|
||||
<a name="1c31fb40"></a>
|
||||
### 什么是 DQL ?
|
||||
|
||||
DQL 是 Doctrine Query Language 的缩写,代表 Doctrine 查询语言,并且是对象查询语言(Object Query Language)的派生类,它与 Hibernate 查询语言(HQL)或Java持久化查询语言(JPQL)非常相似。
|
||||
|
||||
::: info
|
||||
对于初学者来说,常见的错误是将DQL误认为只是某种形式的SQL,因此尝试在查询中使用表名和列名或将任意表连接在一起。您需要考虑DQL作为对象模型而不是关系模式的查询语言。
|
||||
:::
|
||||
|
||||
<a name="909d5314"></a>
|
||||
## 定义数据库 Schema
|
||||
|
||||
在 ECOS 中可以在 dbschema 文件夹下,使用数组定义数据库 Schema,laravel 中可以书写数据库迁移(database migration)文件,而在 ECShopX 中,则需要定义 Doctrine ORM 的实体类来完成数据库 Schema定义。
|
||||
|
||||
我们从最简单的实体 `Product` 开始。创建一个 `src/DemoBundle/Entities/Product.php`包含 Product 实体定义的文件:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// src/DemoBundle/Entities/Product.php
|
||||
class Product
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
}
|
||||
```
|
||||
|
||||
创建实体类时,所有字段都应为 `private`。<br />
|
||||
这个类仅仅是一个普通的 php 类,还不能称之为 Entity 类。我们需要通过为这个类添加注解使之成为 Entity 类。
|
||||
|
||||
```php
|
||||
<?php
|
||||
// src/DemoBundle/Entities/Product.php
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="products")
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $name;
|
||||
}
|
||||
```
|
||||
|
||||
在添加完注解之后,可以通过以下命令,生成 getter 和 setter 方法:
|
||||
|
||||
```shell
|
||||
php artisan doctrine:generate:entities --filter=DemoBundle
|
||||
```
|
||||
|
||||
生成之后,一个实体类就完整了,完整的实体类如下:
|
||||
|
||||
```php
|
||||
// src/DemoBundle/Entities/Product.php
|
||||
<?php
|
||||
namespace DemoBundle\Entities;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="products")
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Product
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="f85a3082"></a>
|
||||
## 生成数据表
|
||||
|
||||
一个实体类定义了一张数据表,如何将数据表同步到数据库呢?<br />
|
||||
首先需要先运行:
|
||||
|
||||
```shell
|
||||
php artisan doctrine:migrations:diff
|
||||
```
|
||||
|
||||
运行以上命令成功后,会生成数据库迁移文件:
|
||||
|
||||
```
|
||||
Generated new migration class to "Version20191225161708" from schema differences.
|
||||
```
|
||||
|
||||
文件的路径在`database/migrations/Version20191225161708.php`<br />
|
||||
生成之后,即可运行一下命令,将数据表写入到数据库:
|
||||
|
||||
```
|
||||
php artisan doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
<a name="14b01b22"></a>
|
||||
## 持久化对象到数据库
|
||||
|
||||
现在我们有了Product实体和与之映射的product数据库表。接下里我们演示如何把数据持久化到数据库里。<br />
|
||||
为了方便演示,我们采用测试用例的方式来演示此功能:
|
||||
|
||||
```php
|
||||
//src/DemoBundle/Tests/ProductTest.php
|
||||
<?php
|
||||
namespace DemoBundle\Tests;
|
||||
|
||||
use EspierBundle\Services\TestBaseService;
|
||||
use DemoBundle\Entities\Product;
|
||||
|
||||
class RepositoryFactoryTest extends TestBaseService
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCreateProduct()
|
||||
{
|
||||
$product = new Product();
|
||||
$product->setName("EcshopX");
|
||||
|
||||
//这一行取出了Doctrine的 entity manager 对象,它负责处理数据库的持久化(写入)和取出对象的过程。
|
||||
$em = app('registry')->getManager('default');
|
||||
|
||||
// 告诉Doctrine你希望(最终)存储Product对象(还没有语句执行)
|
||||
$em->persist($product);
|
||||
|
||||
// 真正执行语句(如,INSERT 查询)
|
||||
$em->flush();
|
||||
|
||||
$this->assertNotNull($product->getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当 flush() 方法被调用时,Doctrine会遍历它管理的所有对象以确定是否需要被持久化到数据库。本例中, $product 对象的数据在库中并不存在,因此entity manager要执行 INSERT 请求,在 product 表中创建一个新行。
|
||||
|
||||
<a name="853517fd"></a>
|
||||
## 从数据库中获取对象
|
||||
|
||||
从数据库中取回对象就更简单了:
|
||||
|
||||
```php
|
||||
//src/DemoBundle/Tests/ProductTest.php
|
||||
public function testGetProduct()
|
||||
{
|
||||
$productId = 1;
|
||||
//这一行取出了Doctrine的 entity manager 对象,它负责处理数据库的持久化(写入)和取出对象的过程。
|
||||
$em = app('registry')->getManager('default');
|
||||
|
||||
$productRepository = $em->getRepository(Product::class);
|
||||
|
||||
$product = $productRepository->find($productId);
|
||||
|
||||
|
||||
$this->assertEquals($productId, $product->getId());
|
||||
}
|
||||
```
|
||||
|
||||
当你要查询某个特定类型的对象时,你总是要使用它的”respository”。你可以认为Respository是一个PHP类,它的唯一工作就是帮助你从那个特定的类中取出entity。对于一个entity类,要访问其宝库,通过:
|
||||
|
||||
```php
|
||||
app('registry')->getManager('default')->getRepository(Product::class);
|
||||
```
|
||||
|
||||
一旦有了Repository对象,你就可以访问它的全部有用的方法了。
|
||||
|
||||
```php
|
||||
$repository = app('registry')->getManager('default')->getRepository(Product::class);
|
||||
// 通过主键(通常是id)查询一件产品
|
||||
$product = $repository->find($productId);
|
||||
|
||||
// 动态方法名称,基于字段的值来找到一件产品
|
||||
$product = $repository->findOneById($productId);
|
||||
$product = $repository->findOneByName('Keyboard');
|
||||
|
||||
// 动态方法名称,基于字段值来找出一组产品
|
||||
$products = $repository->findByPrice(19.99);
|
||||
|
||||
// find *all* products / 查出 *全部* 产品
|
||||
$products = $repository->findAll();
|
||||
```
|
||||
|
||||
你也可以有效利用 `findBy` 和 `findOneBy` 方法,基于多个条件来轻松获取对象:
|
||||
|
||||
```php
|
||||
$repository = app('registry')->getManager('default')->getRepository(Product::class);
|
||||
|
||||
// 查询一件产品,要匹配给定的名称和价格
|
||||
$product = $repository->findOneBy(
|
||||
array('name' => 'Keyboard', 'price' => 19.99)
|
||||
);
|
||||
|
||||
// 查询多件产品,要匹配给定的名称和价格
|
||||
$products = $repository->findBy(
|
||||
array('name' => 'Keyboard'),
|
||||
array('price' => 'ASC')
|
||||
);
|
||||
```
|
||||
|
||||
<a name="e562dbff"></a>
|
||||
## 对象更新
|
||||
|
||||
一旦从Doctrine中获取了一个对象,更新它就很容易了:
|
||||
|
||||
```php
|
||||
public function testUpdateProduct()
|
||||
{
|
||||
$productId = 1;
|
||||
//这一行取出了Doctrine的 entity manager 对象,它负责处理数据库的持久化(写入)和取出对象的过程。
|
||||
$em = app('registry')->getManager('default');
|
||||
$productRepository = $em->getRepository(Product::class);
|
||||
$product = $productRepository->find($productId);
|
||||
|
||||
$newName = "ECShopX 2.0";
|
||||
|
||||
$product->setName($newName);
|
||||
$em->flush();
|
||||
|
||||
//判断数据是否更新成功
|
||||
$newProduct = $productRepository->find($productId);
|
||||
$this->assertEquals($newName, $newProduct->getName());
|
||||
}
|
||||
```
|
||||
|
||||
更新一个对象包括三步:
|
||||
|
||||
- 1、从Doctrine中取出对象;
|
||||
- 2、修改对象;
|
||||
- 3、调用entity manager的 flush() 方法。
|
||||
|
||||
注意调用 $em->persist($product) 是不必要的。回想一下,这个方法只是告诉Doctrine去管理或者“观察” $product 对象。此处,因为你已经取到了 $product 对象了,它已经被管理了。
|
||||
|
||||
<a name="a3efd340"></a>
|
||||
## 删除对象
|
||||
|
||||
删除一个对象十分类似,但需要从entity manager调用 remove() 方法:
|
||||
|
||||
```php
|
||||
$em->remove($product);
|
||||
$em->flush();
|
||||
```
|
||||
|
||||
你可能已经预期,`remove()` 方法通知Doctrine你想从数据库中删除指定的entity。真正的 DELETE 查询不会被真正执行,直到 `flush()` 方法被调用。
|
||||
|
||||
<a name="07cc2af8"></a>
|
||||
## 对象查询
|
||||
|
||||
你已经看到 repository 对象是如何让你执行一些基本查询而毋须做任何工作了:
|
||||
|
||||
```php
|
||||
$repository = app('registry')->getManager('default')->getRepository(Product::class);;
|
||||
|
||||
$product = $repository->find($productId);
|
||||
$product = $repository->findOneByName('Keyboard');
|
||||
```
|
||||
|
||||
当然,Doctrine 也允许你使用Doctrine Query Language(DQL)来写一些复杂的查询,DQL类似于SQL,只是它用于查询一个或者多个entity类的对象(如 product),而SQL则是查询一个数据表中的行(如 product )。
|
||||
|
||||
在Doctrine中查询时,你有两个主要选择:
|
||||
|
||||
- 编写纯正的Doctrine查询(DQL)
|
||||
- 使用Doctrine的Query Builder
|
||||
|
||||
<a name="be1cf82d"></a>
|
||||
## 使用DQL进行对象查询
|
||||
|
||||
假设你要查询价格高于 19.99 的产品,并且按价格从低到高排列。你可以使用DQL,Doctrine中类似原生SQL的语法,来构造一个用于此场景的查询:
|
||||
|
||||
```php
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$query = $em->createQuery(
|
||||
'SELECT p
|
||||
FROM DemoBundle\Entities\Product p
|
||||
WHERE p.price > :price
|
||||
ORDER BY p.price ASC'
|
||||
)->setParameter('price', 19.99);
|
||||
|
||||
$products = $query->getResult();
|
||||
```
|
||||
|
||||
如果你习惯了写SQL,那么对于DQL也会非常自然。它们之间最大的不同就是你需要就“select PHP对象”来进行思考,而不是数据表的行。正因为如此,你要 从 Product 这个 entity 来select,然后给entity一个 p 的别名。
|
||||
|
||||
> 注意 `setParameter()`` 方法。当使用 Doctrine 时,通过“占位符”来设置任意的外部值(上面例子的 :price),是一个好办法,因为它可以防止SQL注入攻击。
|
||||
|
||||
|
||||
getResult() 方法返回一个结果数组。要得到一个结果,可以使用getSingleResult()(这个方法在没有结果时会抛出一个异常)或者 getOneOrNullResult() :
|
||||
|
||||
```php
|
||||
$product = $query->setMaxResults(1)->getOneOrNullResult();
|
||||
```
|
||||
|
||||
DQL语法强大到令人难以置信,允许轻松地在entity之间进行join(稍后会覆盖relations)和group等。参考 [Doctrine Query Language](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html) 文档以了解更多。
|
||||
|
||||
<a name="7ef5bbbc"></a>
|
||||
## 使用Doctrine's Query Builder进行对象查询
|
||||
|
||||
除了去写DQL,你还可以使用一个非常有用的QueryBuilder对象,来构建查询 SQL。当你的查询取决于动态条件时,这很有用,因为随着你的连接字符串不断增加,DQL代码会越来越难以阅读:
|
||||
|
||||
```php
|
||||
|
||||
|
||||
$em = app('registry')->getManager('default');
|
||||
$productRepository = $em->getRepository(Product::class);
|
||||
|
||||
// createQueryBuilder() 自动从 AppBundle:Product 进行 select 并赋予 p 假名
|
||||
$query = $productRepository->createQueryBuilder('p')
|
||||
->where('p.price > :price')
|
||||
->setParameter('price', '19.99')
|
||||
->orderBy('p.price', 'ASC')
|
||||
->getQuery();
|
||||
|
||||
$products = $query->getResult();
|
||||
// 要得到一个结果:
|
||||
$product = $query->setMaxResults(1)->getOneOrNullResult();
|
||||
```
|
||||
|
||||
QueryBuilder对象包含了创建查询时的所有必要方法。通过调用getQuery()方法,query builder将返回一个标准的Query对象,可用于取得请求的结果集。
|
||||
|
||||
Query Builder更多信息,参考 Doctrine 的 [Query Builder](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html)文档。
|
||||
Reference in New Issue
Block a user