一个使用PDO管理的实体、仓库和管理员优化的简单ORM,以获得最佳性能。
Requires
- nayjest/str-case-converter: ^1.0
- symfony/filesystem: ^3.4
Requires (Dev)
- phpunit/phpunit: ^6.0
- dev-master
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.4
- 2.0.3
- 2.0.0
- 1.8.0
- 1.7.5
- 1.7.4
- 1.7.3
- 1.7.2
- 1.7.1
- 1.7.0
- 1.6.4
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.0
- 1.1.3
- 1.1.2
- 0.1.1
- 0.1.0
- 0.0.9
- 0.0.8
- 0.0.7
- 0.0.6
- 0.0.5
- 0.0.4
- 0.0.3
- 0.0.2
- 0.0.1
- dev-master-v2
- dev-fix-sql-card-fixture/dve
This package is not auto-updated.
Last update: 2024-09-26 11:44:01 UTC
README
EntityORM是一个使用PDO管理的实体、仓库和管理员优化的ORM,以获得最佳性能。
架构如下
- 基于数据库表和字段生成实体类。
- 一个生成的实体管理器,用于访问生成的管理器和仓库。
- 默认生成的管理器和仓库包含基于表索引的预构建方法,以便易于使用索引检索数据。
- 如果需要,开发者可以创建自己的管理器和/或仓库,通过扩展默认的来扩展。
入门指南
PDO
由于此ORM使用PDO,如果您还没有创建,您必须构建一个PDO对象。
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8', 'dbhost', 'dbname');
$pdo = new \PDO($dsn, 'dbuser', 'dbpassword');
我们建议将属性"ATTR_EMULATE_PREPARES"和"ATTR_STRINGIFY_FETCHES"设置为false。这将防止PDO将数值转换为字符串。
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
实例化工厂
工厂用于获取主类,如EntityManager和类生成器。您必须为工厂设置几个参数,它将根据这些参数构建您所需的所有内容。此工厂应放在服务容器中。
$factory = new Factory();
定义数据库类型。目前仅支持mysql。
$factory->setDatabaseType(Factory::DATABASE_TYPE_MYSQL);
定义自动生成的实体写入的目录。
$factory->setEntityDirectory(__DIR__ . '/../generated/entity');
定义实体应具有的命名空间。
$factory->setEntityNamespace('Dummy\\Project\\Entities');
定义生成的实体管理器写入的目录。
$factory->setEntityManagerDirectory(__DIR__ . '/../generated/entity-manager');
定义生成的实体管理器的命名空间。
$factory->setEntityManagerNamespace('Dummy\\Project\\EntityManager');
定义生成查询构建器更新代理类的目录。
$factory->setQueryBuilderProxyDirectory(__DIR__ . '/../generated/query-builder);
定义生成的查询构建器更新代理类的命名空间。
$factory->setQueryBuilderProxyNamespace('Dummy\\Project\\QueryBuilder');
定义自定义实体仓库类的目录。自定义实体仓库是开发者通过扩展默认仓库创建的,因为需要添加一些与该仓库相关的函数。
$factory->setUserEntityRepositoryDirectory(__DIR__ . '/../src/EntityRepository');
定义自定义实体仓库应具有的命名空间。
$factory->setUserEntityRepositoryNamespace('Dummy\\Project\\EntityRepository');
定义创建自定义管理器类的目录。自定义管理器类是开发者通过扩展默认管理器创建的,因为需要添加一些与该管理器相关的函数。
$factory->setUserManagerDirectory(__DIR__ . '/../src/Manager');
定义自定义管理器应具有的命名空间。
$factory->setUserManagerNamespace('Dummy\\Project\\Manager');
Composer
您还必须配置您的composer.json以使自动加载能够加载生成的类。为此,您必须在composer.json的autoload属性中指定每个目录和命名空间。
...
"autoload": {
"psr-4": {
"Dummy\\Project\\": "src/",
"Dummy\\Project\\Entities\\": "generated/entity/",
"Dummy\\Project\\EntityManager\\": "generated/entity-manager/"
}
}
...
不要忘记执行composer update。
生成实体和实体管理器
现在您有一个可用的完全配置的工厂,您可以使用它来创建生成类。当您生成实体和实体管理器时,生成器将读取数据库结构并根据它生成。此操作只需进行一次。第一次或当数据库结构发生变化时需要刷新类。
$entityGenerator = $factory->createEntityGenerator($pdo);
$entityGenerator->generate();
$entityManagerGenerator = $factory->createEntityManagerGenerator($pdo);
$entityManagerGenerator->generate();
完成!现在检查定义的目录,您将看到所有实体和实体管理器已完全生成。
让我们看看生成的类。
实体
基于这个表结构
CREATE TABLE `car` (
`id_car` int(11) unsigned NOT NULL AUTO_INCREMENT,
`brand` varchar(25) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id_car`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
我们生成的实体类应该看起来像这样
namespace Dummy\Project\Entities;
use Anytime\ORM\EntityManager\Entity;
class Car extends Entity
{
const TABLENAME = 'car';
const PRIMARY_KEYS = ['id_car'];
protected $data = [
'id_car' => 0,
'name' => '',
];
public function setIdCar(int $id): Car
{
$this->data['id_car'] = $id;
return $this;
}
public function getIdCar(): int
{
return (int)$this->data['id_car'];
}
public function setBrand(string $id): Car
{
$this->data['brand'] = $id;
return $this;
}
public function getBrand(): string
{
return (string)$this->data['brand'];
}
}
DynamicRepositories
这个类包含每个表每个存储库的获取方法。在我们的例子中,我们将有这个方法
public function getCarEntityRepository(): EntityRepository
存储库类将帮助您创建管理者使用的查询构建器。
DynamicManager
这个类包含每个表每个管理者的获取方法。在我们的例子中,我们将有这个方法
public function getCarManager(): Manager;
管理者将帮助您在数据库中查找与表相关的数据。
目录 DefaultRepository
这个目录包含基于表索引的自动生成的存储库,包含方法。
目录 DefaultManager
这个目录包含基于表索引的自动生成的管理者,包含方法。
实例化实体管理器
完成所有这些步骤后,您就可以开始使用实体管理器了。让我们实例化它...
您应该将其放在服务容器中,并强制返回类型为生成的类型,以便在您的编辑器中获得更好的自动完成。
class SomeServiceContainer
{
public function getEntityManagerService(): DynamicEntityManager
{
$entityManager = $factory->createEntityManager($this->pdo);
}
}
就像这样,当使用它时,您将拥有所有现有方法的自动完成。
检索数据
要检索数据,您将必须使用管理者。管理者将始终使用与同一表相关的存储库。在实现您自己的管理者之前,尝试使用现有方法。
findByPrimaryKey
此方法用于检索具有主键值的记录。也支持复合主键。
$car = $entityManager->managers->getCarManager()->findByPrimaryKey(1);
print_r($car);
如果您有一个ID为"1"的记录,它将返回一个完全填充的Car实体。
如果您必须检索具有复合主键的实体,可以这样做
$someCompopsiteEntity = $entityManager->managers->getSomeCompositeManager()->findByPrimaryKey(1, 2);
print_r($someCompositeEntity);
使用查询构建器检索数据
如果您必须根据多个标准检索数据,您将必须使用查询构建器。
创建查询构建器
$queryBuilder = $entityManager->managers->getCarManager()->createQueryBuilder('c');
定义查询中使用的参数
$queryBuilder->setParameters(['BMW']);
您还可以使用命名参数
$queryBuilder->setParameters(['carName' => 'BMW']);
定义WHERE子句
$queryBuilder->where('c.brand = ?');
或者如果您使用了命名参数,可以这样
$queryBuilder->where('c.brand = :carName');
准备查询
$query = $queryBuilder->getSelectQuery();
只检索一个结果(第一个)
$car = $query->fetchOne();
逐个检索
while($car = $query->fetch()) {
print_r($car);
}
检索所有数据
$cars = $query->fetchAll();
如果您不需要实体,可以更改检索数据格式值
$query->setFetchDataFormat(SelectQuery::FETCH_DATA_FORMAT_ARRAY);
使用过滤器
您可以使用过滤器在ORM填充实体之前对检索的结果数据进行操作。
创建过滤器
- 创建一个扩展类 Anytime\ORM\EntityManager\Filter 的对象
- 定义方法 apply()(见下文)
- 在 apply 方法中,您必须返回转换后的值(或不需要转换时则不转换)
示例
<?php
namespace Dummy\Project\Filters;
use Anytime\ORM\EntityManager\Filter;
class FooFilter extends Filter
{
/**
* @param $inputValue
* @param string $entityClass
* @param string $propertyName
* @param array $resultRow
* @return mixed|null
*/
public function apply($inputValue, string $entityClass, string $propertyName, array &$resultRow)
{
if($entityClass::getEntityPropertyType($propertyName) === 'string') {
$inputValue = 'Foo ' . $inputValue;
}
return $inputValue;
}
}
此过滤器将为类型为 "string" 的每个属性添加字符串 "Foo "。
现在,如果您想要应用此过滤器,必须实例化它并将其添加到实体管理器。
当实例化过滤器时,您可以指定作用域。如果不指定,则作用域为全局。这意味着过滤器将应用于每个实体的每个属性。作用域参数是一个键/值行列表,其中键是实体类,值是要应用过滤器的属性的列表。
请注意,过滤器的名称必须是唯一的。
$secureEntityManager->addFilter(new FooFilter(
'My Super Foo Filter',
[
DummyEntity::class => [
'^(foo|bar)$', // Match "foo" and "bar" properties
'date' // Match all properties containing thestring "date"
]
]
));
您可以添加任意数量的过滤器,但请注意!ORM的性能可能会显著降低,尤其是在全局作用域中保持过滤器时。
创建自定义存储库和管理者
让我们以 "car" 表为例,并为所有者表添加一个外键。
CREATE TABLE `car` (
`id_car` int(11) unsigned NOT NULL AUTO_INCREMENT,
`owner_id` int(11) NOT NULL,
`brand` varchar(25) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id_car`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `owner` (
`id_owner` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
PRIMARY KEY (`id_owner`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
我们在配置的目录中为该表创建一个自定义存储库类。在我们的例子中,您将在 "src/EntityRepository/" 目录中创建它
<?php
namespace Dummy\Project\EntityRepository;
use Anytime\ORM\EntityManager\EntityRepository;
class CarEntityRepository extends EntityRepository
{
}
现在添加一个自定义方法,该方法将使 "car" 和 "owner" 之间进行连接
<?php
namespace Dummy\Project\EntityRepository;
class CarEntityRepository extends \Dummy\Project\EntityManager\DefaultRepository\CarEntityRepository
{
/**
* @return \Anytime\ORM\QueryBuilder\QueryBuilderInterface
*/
public function findCarWithOwner()
{
$queryBuilder = $this->createQueryBuilder('c');
$queryBuilder->join('INNER JOIN owner o ON o.id_owner = c.owner_id');
return $queryBuilder;
}
}
此存储库类现在将被加载,而不是默认存储库类
现在让我们在配置的目录中创建一个管理器类。在我们的例子中是 "src/Manager/"
<?php
namespace Dummy\Project\Manager;
class CarManager extends \Dummy\Project\EntityManager\DefaultManager\CarManager
{
}
现在让我们添加一个使用存储库的方法
<?php
namespace Dummy\Project\Manager;
class CarManager extends \Dummy\Project\EntityManager\DefaultManager\CarManager
{
public function findCarsByOwnerName(string $ownerName)
{
$repository = $this->getRepository();
$queryBuilder = $repository->createCarWithOwnerQueryBuilder();
$queryBuilder->setParameters([
'ownerName' => $ownerName
]);
$queryBuilder->where('o.name = :ownerName');
return $queryBuilder->getSelectQuery()->fetchAll();
}
}
它将返回与名为 "John Smith" 的所有者相关的所有汽车。
插入数据
插入单个实体
$car = (new Car())
->setBrand('Audi')
->setOwnerId(1)
;
$entityManager->insert($car);
echo 'record inserted with ID #'.$car->getIdCar();
插入多个实体
$entityManager->insert([$entity1, $entity2,..., $entityN]);
更新数据
更新单个实体
$entity->setSomeProperty('new value');
$entityManager->update($entity);
更新多个实体
$entityManager->update([$entity1,$entity2,...,$entityN]);
基于 WHERE 条件的大量更新
$qb = $entityManager->managers->getCarManager()->createUpdateQueryBuilder();
$qb->setParameters([
'ownerId' => 20
]);
$qb->where('car.owner_id = :ownerId');
$qb->setFieldsToUpdate([
'brand' => 'Minicooper'
]);
$affectedRows = $qb->getUpdateQuery()->execute();
echo $affectedRows . ' rows updated';
基于索引方法的大量更新
我们假设我们在 owner_id 和 brand 字段以及 "field_a" 和 "field_b" 字段上有一个多个索引
$affectedRows = $entityManager->managers->getCarManager()->updateByOwnerIdAndBrand($ownerId, $brand)
->setFieldA('A')
->setFieldB('B')
->execute()
;
您还可以使用表达式而不是特定值。
为此,您必须实例化 \Anytime\ORM\QueryBuilder\Expression 类,并定义要使用的表达式字符串。您可以在表达式中使用 %FIELD% 字符串,它将被与当前设置器匹配的表字段名称替换。
$affectedRows = $entityManager->managers->getCarManager()->updateByOwnerIdAndBrand($ownerId, $brand)
->setFieldA('A')
->setFieldB(new Expr('LOWER(%FIELD%)'))
->execute()
;
在这个例子中,最终的 SQL UPDATE 将类似于
UPDATE car SET field_A = 'A' AND field_b = LOWER(field_b);
删除数据
删除单个实体
$entityManager->delete($entity);
删除多个实体
$entityManager->delete([$entity1,$entity2,...,$entityN]);
基于 WHERE 条件的大量删除
$qb = $entityManager->managers->getCarManager()->createDeleteQueryBuilder();
$qb->setParameters(['ownerId' => 1]);
$qb->where('car.owner_id = :ownerId');
$affectedRows = $qb->getDeleteQuery()->execute();
echo $affectedRows . ' rows deleted.';
基于索引方法的大量删除
我们假设我们在 owner_id 和 brand 字段上有一个多个索引
$affectedRows = $entityManager->managers->getCarManager()->deleteByOwnerIdAndBrand($ownerId, $brand);