computools/clight-orm

v0.6.5 2018-12-27 11:59 UTC

README

这个库被设计为常规的ORM。

ORM开发的目的在于创建一个快速且方便的工具,用于处理数据库和关系映射。ORM允许你将实体与一对一、一对多、多对一、多对多关系类型关联起来。

安装

composer require computools/clight-orm

结构元素

  • 实体 - 数据库表的表示。这些对象必须具有属性获取器和设置器,并扩展库中的 AbstractEntity

  • 仓库 - 基于 Repository 模式,这些对象必须具有实体对象定义,并扩展自 AbstractRepository。抽象仓库包含所有常见的(如 find、findBy 等)数据搜索和保存方法。

示例

示例请参考 tests 目录。

让我们看看

实体

AbstractEntity 继承涉及实现 getTable() 方法,该方法将定义数据库表名,以及 getFields() 方法,该方法返回规则数组。

此外,您可以提供一些可选字段,如果查询结果中存在字段,则将进行映射。例如,我们可以添加一些 count 字段,该字段不会与表相关联,但将在查询结果中呈现。

为此,您可以使用 getOptionalFields() 方法。

键必须指定为表列名。

允许的字段类型

  • IdType 带有可选参数 'identifierField',如果表的标识符列名不是 'id',则需要指定。
  • IntegerType - 整数,带有可选参数 'columnName',如果映射的字段与数据库字段不同。
  • StringType - 字符串或文本,带有可选参数 'columnName',如果映射的字段与数据库字段不同。
  • FloatType - 浮点数或小数,带有可选参数 'columnName',如果映射的字段与数据库字段不同。
  • DateTimeType - 日期时间,列名作为第一个参数,格式作为第二个参数。
  • CreatedAtType - 日期时间,值将在实体创建时自动定义。第一个参数是 columnName,第二个是 datetime 格式,两者都是可选的。
  • UpdatedAtType - 日期时间,值将在实体更新时自动定义。第一个参数是 columnName,第二个 - datetime 格式,两者都是可选的。
  • BooleanType - 布尔值,第一个参数是 column name 如果与表字段名不同,第二个参数定义字段是否必须保存为 int(默认为 true)。
  • JsonType - json,将 json 保存到数据库,并将数组转换为实体。第一个参数 - 表字段名。

允许的关系类型

  • OneToOne - 第一个参数是相关实体实例,第二个是主表字段名

  • ManyToOne - 第一个参数是相关实体实例,第二个是主表字段名。

  • OneToMany - 第一个参数是相关实体实例,第二个是相关表字段。

  • ManyToMany - 带参数

    • entity - 相关实体实例
    • table - 多对多关系表名
    • columnName - 关系表列名,对应主表
    • referencedColumnName - 关系表列名,对应引用表

    use Computools\CLightORM\Entity\AbstractEntity;

    class Book extends AbstractEntity { public function getTable(): string { return 'books'; }

      public function getFields(): array
      {
          return [
              'id' => new IdType(),
              'name' => new StringType('title'),
              'price' => new FloatType(),
              'authors' => new ManyToMany(new Author(), 'authors_books', 'book_id', 'author_id'),
              'themes' => new ManyToMany(new Theme(), 'books_theme', 'book_id', 'theme_id')
          ];
      }
      
      public functnion getOptionalFields(): array
      {
          return [
              'themes_count' => new IntegerType()
          ];     
      }
     
      private $id;
    
      private $name;
    
      private $authors = [];
    
      private $themes = [];
    
      private $price;
      
      private $themesCount;
      
      public function getThemesCount()
      {
          return $this->themesCount;
      }
    
      public function getId(): ?int
      {
          return $this->id;
      }
    
      public function getName(): ?tring
      {
          return $this->name;
      }
    
      public function setName(?string $name): void
      {
          $this->name = $name;
      }
    
      public function getAuthors(): array
      {
          return $this->authors;
      }
    
      public function getThemes(): array
      {
          return $this->themes;
      }
      
      public function getPrice(): ?float 
      {
          return $this->price;
      }
      
      public function setPrice(float $price)
      {
          $this->price = $price;
      }
    

    }

标识字段必须能够在新实体(尚未保存到数据库)中接受 null。

另一种选择是使用公共属性而不是getter和setter。这个库支持这种类型的实体。

use Computools\CLightORM\Entity\AbstractEntity;
    
class Book extends AbstractEntity
{
    public $id;

    public $name;

    public $authors;

    public $themes;

    public $price; 
}

如果您想将null设置为一对一或多对一关系,可以使用destroyToOneRelation(string $field)方法

$post->destroyToOneRelation('author');
$postRepository->save();

此操作将null保存到post记录的author_id字段。无论使用何种方法接收实体,都可以使用此功能。所以两者都有效

$postRepository->find(1, ['author']);
$post->destroyToOneRelation('author');

$postRepository->find(1);
$post->destroyToOneRelation('author');

如果您想为两个实体添加多对多关系,可以调用addRelation(EntityInterface $entity)方法,并调用removeRelation(EntityInterface $entity)来移除。

$post->addRelation($user);

$post->removeRelation($user);

ORM可以执行实体的批量赋值。因此,您可以使用此结构

$book = new Book();
$book->fill([
    'name' => 'Book name',
    'price' => 10.99
]);
$bookRepository->save($book);

代替

$book = new Book();
$book->setName('Book name');
$book->setPrice(10.99);
$bookRepository->save($book);

如果为实体设置了$allowedFields属性,则此类操作将被允许。所以它看起来像

class Book
{
    protected $allowedFields = [
        'name',
        'price'
    ];
    
    public $name;
    
    public $price;
}

这是可以使用fill()方法设置的字段列表。如果指定的字段不在列表中,则将其跳过。

仓库

use Computools\CLightORM\Repository\AbstractRepository;
use Computools\CLightORM\Test\Entity\Book;

class BookRepository extends AbstractRepository
{
    public function getEntityClass(): string
    {
        return Book::class;
    }
    
    public function findByUser(User $user): ?Book
    {
        return $this->findBy(['user_id' => $user->getId()]);
    }
}

这是实现仓库的方式。您可以编写自己的方法或使用现有方法。

要调用仓库,可以使用

Computools\CLightORM\CLightORM

以下是一个示例

$pdo = new \PDO(
            sprintf(
                '%s:host=%s;port=%s;dbname=%s',
                'mysql',
                '127.0.0.1',
                '3306',
                'test'
            ),
        'user',
        'password'
        );

$clightORM = new CLightORM($pdo);

要获取特定的实体仓库,只需使用类字符串作为参数调用create方法

$repository = $clightORM->createRepository(PostRepository::class);
$repository->find(2);

仓库方法

  • find(int $id, array $with, $expiration = 0)
  • findBy(array $citeria, ?Order $order, array $with, ?Pagination $pagination, $expiration = 0)
  • findOneBy(array $criteria, ?Order $order = null, array $with, $expiration = 0)
  • findFirst($with)
  • findLast($with)
  • save(EntityInterface $entity, array $with, $relationExistsCheck = false)
  • remove(EntityInterface $entity)

Computools\CLightORM\Tools\Order对象可以用于对查询结果进行排序。

$repository->findBy([
        'name' => 'test'
    ],
    new Order('name', 'DESC')
)

expiration参数可以用于将搜索结果存储到缓存中。如果不等于0,则第一次调用结果将存储到缓存中。然后方法调用将返回缓存中的数据,直到过期。有关详细信息,请参阅缓存部分。

With参数提供您将相关实体包含到结果中的可能性。您还可以获取相关实体的相关实体等。例如

$book = $bookRepository->findLast(['themes', 'authors']);

这将找到具有相关主题和作者的最后本书(您必须指定与关系对应的实体字段名称)

$book = $bookRepository->findLast(
    [
        'themes' => [
            'posts'
        ],
        'authors'
    ]
);

这将找到具有主题和作者的最后本书。此外,还会找到所有主题的相关帖子。对于save方法,您也可以定义$with参数,以在结果中获取相关实体。

嵌套级别不受限制,因此您可以使用此结构

 $book = $bookRepository->findLast(
        [
            'themes' => [
                'posts' => [
                    'editors' => [
                        'userProfile'
                    ]
                ]
            ],
            'authors'
        ]
    );

仓库'save'方法的第一个参数是对象链接,因此您可能无法使用方法结果覆盖对象变量。

$post = new Post(); 
$post->setUser($user);
$postRepository->save($post);
return $post;

但是,当然,您可以使用返回值

$post = new Post();
$post->setUser($user);
return $postRepository->save($post);

如果仓库结果给出的是一个集合,则它将是实体数组。

您可以使用Computools\CLightORM\Tools\Pagination作为findBy的第三个参数来分页结果。

$posts = $repository->findByUser($this->getUser(), ['theme'], (new Pagination())->setPagination(1, 20));

或者

$posts = $repository->findByUser($this->getUser(), ['theme'], (new Pagination())->setLimitOffset(20, 0));

您还可以在仓库类内部使用orm对象来执行一些自定义查询。

查询

您可以使用内置的查询对象来完成一些自定义逻辑。

CLightORM对象可以创建任何类型的查询。此对象在任何一个仓库中都是可访问的。

class PostRepository extends AstractRepository
{
    public function getEntityClass(): string
    {
        return Post::class;
    }
    
    public function findPostsCustom()
    {
        $query = $this->orm->createQuery();
        
        $query
            ->select('id, name')
            ->from($this->table)
            ->where('title', 'test')
            ->where('type', 'test')
            ->whereExpr('id < 5')
            ->whereExpr('id > 2')
            ->whereArray([
                'title' => 'test',
                'type' => 'test'
            ])
            ->groupBy('id')
            ->order('id', 'DESC')
            ->limit(10, 5)
            ->execute();
            
        return $query->getResult();
    }
}

上面的方法演示了查询的可能方法。它返回数组作为结果。如果您想将结果映射到某个实体,则不得指定选择字段,并调用mapToEntitymapToEntities方法。

$query
    ->from($this->table);
    ->whereExpr('id < 5')
    ->whereExpr('id > 2')
    ->execute();
return $this->mapToEntities($query, ['author', 'editor']);

要使用JOIN命令,可以使用Computools\CLightORM\Database\Query\Structure\Join。构造函数参数是

  • string $type (LEFT, RIGHT, INNER)

  • string $table (表名)

  • string $condition (例如,on p.user_id = u.id)

  • string $alias = null

    $query ->from('post', 'p') ->join(new Join('INNER', 'user', 'ON p.author_id = u.id', 'u')) ->where('p.id', 5) ->execute()

多对多关系保存可以检查已存在的关联。因此,如果您想避免关系重复,可以提供第三个参数作为true

$post = $postRepository->find(1);
$user = $userRepository->find(1);

$post->addRelation($user);
$postRepository->save($post, [], true);

此调用还将调用重复检查,并且不会添加相同的关联。如果您提供第三个参数为false,则检查将不会执行,并且如果数据库表有唯一索引,则会抛出异常。

缓存

缓存机制可以用来存储一些搜索结果。要使用它,您需要在创建CLightORM实例时指定缓存类型。有两种不同的选项来存储结果 - memcached和文件系统。

Computools\CLightORM\Cache\Memcache接受两个参数

  • host(string) - memcached服务器主机,默认为'localhost',
  • port(int) - memcached服务器端口,默认为'11211'

Computools\CLightORM\Cache\Filecache接受缓存目录作为参数,默认为'cache'。

因此,要使用缓存,您需要编写类似以下内容

$pdo = new \PDO(
                sprintf(
                    '%s:host=%s;port=%s;dbname=%s',
                    'mysql',
                    '127.0.0.1',
                    '3306',
                    'test'
                ),
            'user',
            'password'
            );
    
$clightORM = new CLightORM($pdo, new Filecache('response/cache'));

或者

$clightORM = new CLightORM($pdo, new Memcache('localhost', 11211));

然后所有您的仓库都将创建具有缓存作为私有属性。您可以为findBy等提供expiration参数。

$repository->findBy(array $criteria, null, array $with = [], null, $expiration = 3600)

如果expiration = 0,则不会使用缓存。如果不为0 - 如果未过期,则从缓存中获取数据。