csrdelft / orm
为 csrdelft.nl 定制的创新命名对象关系映射库
Requires
- php: >=7.0.27
- ext-json: *
- ext-pdo: *
- psr/container: ^1.0
- zumba/json-serializer: ^2.2
Requires (Dev)
- phpunit/dbunit: ^4.0
- phpunit/php-code-coverage: ^6.0
- phpunit/phpunit: ^7.0
README
C.S.R. Delft ORM
一个简单的 PHP 对象关系映射器。我们目前在 csrdelft.nl 上使用此库进行生产。
安装
使用 composer 安装
composer require csrdelft/orm
在可以使用 ORM 之前,必须初始化缓存和数据库。memcache 需要一个 UNIX 套接字或主机和端口,数据库和数据库管理员需要主机、数据库、用户名和密码。之后,任何模型都可以访问数据库。
CsrDelft\Orm\Configuration::load([ 'cache_path' => '/path/to/data/dir/cache.socket', // Host or unix socket 'cache_port' => 11211, // Optional if cache_path is a host 'db' => [ 'host' => 'localhost', 'db' => 'myDatabase', 'user' => 'myUser', 'pass' => 'myPass' ] ]);
使用
ORM 依赖于模型和实体。模型是与数据库交互的类。实体是包含数据库表定义的数据类。本文档将简要概述您开始之前需要了解的基本内容。
实体
实体是一个包含数据的对象,例如汽车、人等。
当您想要将实体保存到数据库中时,您必须扩展 PersistentEntity
类。实体必须包含一些变量,如下所述。实体必须只包含关于自身的逻辑,而不是关于其他类或同一实体的其他实例的逻辑。这应该在模型(或不是此库部分的控制器)中完成。
实体放在 model/entity/
文件夹中,并命名为 EntityName.php
。
实体中的变量
对于实体的每个属性,都必须有一个公共变量。这些将在从数据库加载时由模型使用。
public $id; public $num_wheels; public $color;
$table_name
数据库中的表名。
protected static $table_name = 'cars';
$persistent_attributes
一个包含实体属性的数组,映射到类型。
类型是一个数组,具有以下值。
- 0:来自
T
(PersistentAttributeType.enum
)的类型 - 1:此变量是否可为空?
- 如果 0 是
T::Enumeration
,则枚举类(扩展PersistentEnum
)。否则 'extra',例如auto_increment
或注释。
protected static $persistent_attributes = [ 'id' => [T::Integer, false, 'auto_increment'], 'num_wheels' => [T::Integer], 'color' => [T::Enumeration, false, 'ColorEnum'] ];
$primary_key
包含完整主键的数组。
protected static $primary_key = ['id'];
$computed_attributes
一个包含计算属性的数组。这将这些属性映射到函数并添加到 jsonSerialize
protected static $computed_attributes = [ 'my_val' => [T::Integer], ]; protected function getMyVal() { return 42; }
示例
model/entities/Car.php
class Car extends PersistentEntity { public $id; public $num_wheels; public $color; public function carType() { if ($this->num_wheels == 4) { return "Normal car"; } else { return "Weird car"; } } protected static $table_name = 'cars'; protected static $persistent_attributes = [ 'id' => [T::Integer, false, 'auto_increment'], 'num_wheels' => [T::Integer], 'color' => [T::Enumeration, false, 'ColorEnum'] ]; protected static $primary_key = ['id']; }
模型
模型必须扩展 PersistenceModel
类。模型是特定实体的所有者。模型可以通过公共静态 instance()
方法在任何地方访问。然而,应尽可能避免这样做。
模型应放置在 model/
目录中。
模型中的变量
模型有一些静态变量必须定义。
ORM
常量 ORM
定义了这个模型是哪个实体的所有者。这是一个字符串或类。
const ORM = 'Car';
const ORM = Car::class;
$default_order
这是选择数据库时使用的默认排序值。
protected static $default_order = 'num_wheels DESC';
模型中的函数
以下函数可以在模型中使用
find($criteria, $criteria_params, ...) : PersistentEntity[]
根据条件在数据库中查找实体。如果您以前使用过PHP中的PDO,那么这个语法的用法应该很熟悉。这里的 $criteria
是基础选择语句的 WHERE
子句,您可以在变量所在的位置放置 ?
。条件参数是填充这些变量的地方。条件参数会自动过滤并安全用于用户输入。
CarModel::instance()->find('num_wheels = ? AND color = ?', [$normal_car_wheels, $car_color]);
count($criteria, $criteria_params) : int
计算满足条件的实体数量,与 find(..)
相同。创建类似于 SELECT COUNT(*) ...
的语句,这比在PHP中计数更快。
exists($entity) : boolean
检查实体是否存在于数据库中。
create($entity) : string
将新实体保存到数据库中。返回插入实体的ID。
update($entity) : int
将实体存储在数据库中,替换具有相同主键的实体。
delete($entity) : int
从数据库中删除实体。
示例
model/CarModel.php
require_once 'model/entity/Car.php'; class CarModel extends PersistenceModel { const ORM = 'Car'; public function findByColor($color) { return $this->find('color = ?', [$color]); } }
index.php
require_once 'model/CarModel.php'; $model = CarModel::instance(); $cars = $model->find(); $actual_cars = $model->find('num_wheels = 4'); $yellow_cars = $model->findByColor('yellow');
数据库更新
此ORM可以为您检查模型。要启用此功能,您需要将全局常量 DB_CHECK
定义为 true
。之后,您可以检查 DatabaseAdmin 中是否有任何更新的表。这仅检查正在使用的表,必须在所有模型使用后执行。还有可能通过将全局常量 DB_MODIFY
定义为 true
来自动更新数据库。这将根据您的模型更新表。这不会尝试迁移数据,所以请小心。 DB_MODIFY
不会删除表。为此,您需要将 DB_DROP
定义为 true
。
if (DB_CHECK) { $queries = DatabaseAdmin::instance()->getQueries(); if (!empty($queries)) { if (DB_MODIFY) { header('Content-Type: text/x-sql'); header('Content-Disposition: attachment;filename=DB_modify_' . time() . '.sql'); foreach ($queries as $query) { echo $query . ";\n"; } exit; } else { var_dump($queries); } } }
JSON字段
通过使用T::JSON,可以将数据库字段映射为数组或对象。在保存时,数据会被序列化为JSON格式,之后可以进行反序列化。在示例中,$reviews属性是一个JSON字段。
$model = CarModel::instance(); $car = $model->find()[0]; $review = new CarReview(); $review->userId = '1801'; $review->reviewText = 'Very good car!'; $car->reviews = [$review]; $model->update($car);
为了防止远程代码执行,只有允许的类才能进行序列化和反序列化。在属性定义的第三个元素中应指定允许的类列表。也可以传递null以允许所有类。
数据库事务
要在一个事务中包装多个数据库调用,可以使用Database::transaction(Closure)
。这个函数将另一个函数包装在数据库事务中。任何抛出的异常都会导致事务回滚。如果数据库已经在一个事务中,这个函数将只会调用Closure
而不会尝试创建新的事务。
$car = new Car(); Database::transaction(function () use ($car) { CarModel::instance()->create($car); CarWheelModel::instance()->create($car->getWheels()); });
依赖注入
您可以为DependencyManager
提供自己的ContainerInterface
,这个容器将用于一切。您仍然需要向容器提供Database
、DatabaseAdmin
和OrmMemcache
的实例。
$container = $kernel->getContainer(); DependencyManager::setContainer($container); $container->set(OrmMemcache::class, new OrmMemcache($cachePath)); $container->set(Database::class, new Database($pdo)); $container->set(DatabaseAdmin::class, new DatabaseAdmin($pdo));
还可以利用orm提供的依赖注入功能。orm使用一种非常简单的方式进行依赖注入。当创建模型实例时,它试图查找任何扩展DependencyManager
的依赖项。如果找到,它们将被连接到模型中并可供使用。模型只能有一个版本,这由DependencyManager
跟踪。
示例
class OwnerModel extends PersistenceModel { const ORM = 'Owner'; /** @var CarModel */ protected $carModel; public function __construct(CarModel $carModel) { $this->carModel = $carModel; } public function getCarsForOwner(Owner $owner) { return $this->carModel->find('owner = ?', [$owner->id]); } } class CarModel extends PersistenceModel { const ORM = 'Car'; public function findByColor($color) { return $this->find('color = ?', [$color]); } }
$owner = OwnerModel::instance()->find('id = 1'); $cars = OwnerModel::instance()->getCarsForOwner($owner);