此包的最新版本(2.2.2)没有可用的许可信息。

一个使用PDO管理的实体、仓库和管理员优化的简单ORM,以获得最佳性能。

2.2.2 2020-03-04 14:29 UTC

README

EntityORM是一个使用PDO管理的实体、仓库和管理员优化的ORM,以获得最佳性能。

架构如下

  1. 基于数据库表和字段生成实体类。
  2. 一个生成的实体管理器,用于访问生成的管理器和仓库。
  3. 默认生成的管理器和仓库包含基于表索引的预构建方法,以便易于使用索引检索数据。
  4. 如果需要,开发者可以创建自己的管理器和/或仓库,通过扩展默认的来扩展。

入门指南

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);