blast/orm

基于Doctrine 2 DBAL的无框架数据访问和持久化框架

0.6.9 2016-08-30 13:23 UTC

README

Packagist上的最新版本 软件许可 构建状态 总下载量 ![覆盖率状态][ico-coveralls]

基于Doctrine 2 DBAL的无框架数据访问和持久化。

功能

  • 数据关系映射自0.1版本以来
  • 将实体解耦为POPO(原始PHP对象)自0.3版本以来
  • 自动建议实体定义以及配置自定义定义自0.5版本以来
  • 数据注入到实体以及反向操作自0.5版本以来
  • 将仓库合约到一个单个实体类自0.5版本以来
  • 从定义中集成字段自0.5.2版本以来
  • 字段类型感知转换自0.5.2版本以来
  • 从定义中集成索引自0.6.4版本以来
  • 实体独立和连接依赖的表前缀自0.6.4版本以来
  • 实体反射和元数据缓存自0.6.4版本以来

即将推出的功能

  • 工作单元 - 实体感知事务
  • 标识符映射 - 通过存储实体按主键减少负载

安装

使用Composer

Blast ORM可在Packagist上找到,并可以使用Composer进行安装。这可以通过运行以下命令或更新您的composer.json文件来完成。

composer require blast/orm

composer.json

{
    "require": {
        "blast/orm": "~1.0"
    }
}

请确保也将您的Composer自动加载文件包含到您的项目中

<?php

require __DIR__ . '/vendor/autoload.php';

下载.zip文件

该项目还可以作为GitHub上的.zip文件下载。访问发行版页面,选择您想要的版本,然后单击“源代码(zip)”下载按钮。

要求

以下PHP版本受此版本支持。

  • PHP >= 5.5.9
  • PHP 5.6
  • PHP 7.0
  • HHVM

示例

可以在这篇博客文章中找到示例。

概念

实体

实体对象是数据库实体的内存表示。实体对象是普通对象(称为POPO)。建议在普通对象上使用访问器(获取器)和修改器(设置器)属性。

提供者

提供者是独立数据实体和数据访问之间的联系。提供者还能将数据注入实体对象并从实体对象中提取数据。

定义

定义管理实体元数据和特定于模式的配置。定义可以传递给映射器或提供者,而不是实体。

映射器

每个实体都有自己的映射器。映射器由实体提供者确定。映射器在dbal和实体之间进行中介,并提供方便的CRUD(创建、读取、更新、删除)操作。除了CRUD之外,映射器还提供方便的方法来处理关系。

查询

查询充当持久层访问器。查询类在执行时填充数据,并将结果转换为单个实体类或\ArrayObject(作为后备),实体类集合作为\SplStack,或原始数据数组。此外,查询能够接收填充选项以控制结果。创建、删除和更新始终返回一个数值!

仓库

仓库在持久层和通过映射器或查询对持久层或数据访问进行抽象之间进行中介。Blast ORM仅提供Blast\Orm\RepositoryInterface以完成!

使用方法

连接

Blast ORM通过\Blast\Orm\ConnectionManager管理所有连接。您可以直接创建连接或将连接添加到管理器缓存,稍后访问它们。

直接访问

创建新的连接

<?php

use Blast\Orm\ConnectionManager;

$connection = ConnectionManager::create('mysql://root:root@localhost/defaultdb?charset=UTF-8');

存储和命名连接

连接管理器

连接管理器按名称将其所有连接存储在其自己的缓存中。

在某些情况下,您可能希望使用新的连接管理器实例,例如在单独的容器中。

<?php

use Blast\Orm\ConnectionManager;

$connections = new ConnectionManager();
处理连接

添加连接。如果未设置第二个参数名称,则名称为default

<?php

$connections->add('mysql://root:root@localhost/defaultdb?charset=UTF-8', 'myconnectionname');

获取连接,默认连接名称始终为default

<?php

//get default connection
$defaultConnection = $connections->get();

//get connection by name
$anotherConnection = $connections->get('another');

将默认连接与另一个连接交换。

<?php
$connections->setDefaultConnection('another');

查询

查询对象提供doctrine 2查询构建器的高级API方法。

从连接创建查询

<?php

$query = $connection->createQuery();

使用实体

<?php

$query = $connection->createQuery(Post::class);

使用定义

<?php

$query = $connection->createQuery($definition);

创建新的查询实例,查询将自动从连接管理器确定当前活动连接。

<?php

use Blast\Orm\Query;

$query = new Query();

或创建一个新的查询并使用自定义连接

<?php

use Blast\Orm\Query;
use Blast\Orm\ConnectionManager;

$query = new Query(ConnectionManager::create('mysql://root:root@localhost/acme'));

或为实体创建新的查询

<?php

use Blast\Orm\Query;

$query = new Query(null, Post::class);

查询的自定义连接

<?php

use Blast\Orm\ConnectionManager;

$query->setConnection(ConnectionManager::create('mysql://root:root@localhost/acme'));

自定义查询构建器

<?php

$query->setBuilder($connection->createQueryBuilder());

选择

获取所有帖子作为包含帖子作为\ArrayObject的集合\SplStack

<?php

$result = $query->select()->from('post', 'p')->execute();

//get data from result
$title = $result['title'];
$content = $result['content'];

通过ID获取帖子作为\ArrayObject

<?php

$id = 1;
$results = $query->select()
    ->from('post', 'p')
    ->where('id = :id')
    ->setParameter('id', $id)
    ->execute();

//loop results and get data 
foreach($results as $result){
    $title = $result['title'];
    $content = $result['content'];
}

创建

创建新条目并获取受影响行数

<?php

$affectedRows = $query->insert('post')
    ->setValue('title', 'New Blog post')
    ->setValue('content', 'some blog content')
    ->execute();

更新

更新条目并获取受影响行数

<?php

$affectedRows = $query->update('post')
    ->set('title', 'New Blog post')
    ->where('id = :id')
    ->setParameter('id', 1)
    ->execute();

删除

删除条目并获取受影响行数

<?php

$affectedRows = $query->delete('post')
    ->where('id = :id')
    ->setParameter('id', 1)
    ->execute();

选择的高级执行

执行查询并获取实体结果

<?php

$post = $query->execute(\Blast\Orm\Hydrator\HydratorInterface::HYDRATE_ENTITY);

执行查询并获取集合结果

<?php

$posts = $query->execute(\Blast\Orm\Hydrator\HydratorInterface::HYDRATE_COLLECTION);

执行查询并获取原始数组结果

<?php

$result = $query->execute(\Blast\Orm\Hydrator\HydratorInterface::HYDRATE_RAW);

事件

查询能够为每种语句类型执行事件。

  • 选择
  • 插入
  • 更新
  • 删除
build.{type}

在查询执行语句之前触发此事件。

<?php

use Blast\Orm\Query\Events\QueryBuilderEvent;

$query->getEmitter()->addListener('build.select', function (QueryBuilderEvent $event) {
    $event->getBuilder()->setEntity(Post::class);
});

$result = $query->select()->from('post')->where('id = 1')->execute();
result.{type}

在查询执行语句并收到结果后触发此事件。

<?php

use Blast\Orm\Query\Events\QueryResultEvent;
use Blast\Orm\Query;

$query->getEmitter()->addListener('result.select', function (QueryResultEvent $event, Query $builder) {
    $result = $event->getResult();

    foreach ($result as $key => $value) {
        $result[$key]['contentSize'] = strlen($value['content']);
    }

    $event->setResult($result);
});

$result = $query->select()->from('post')->where('id = 1')->execute();
取消查询执行

使用给定事件的setCancel()方法取消查询执行。

在构建查询语句时

<?php

use Blast\Orm\Query\Events\QueryBuilderEvent;

$query->getEmitter()->addListener('build.select', function (QueryBuilderEvent $event) {
    $event->setCanceled(true);
});

在查询语句结果时

<?php
use Blast\Orm\Query\Events\QueryResultEvent;
use Blast\Orm\Query;

$query->getEmitter()->addListener('result.select', function (QueryResultEvent $event) {
    $event->setCanceled(true);
});

实体

实体类与Blast ORM独立。实体字段被转换为下划线以进行数据库字段映射。数据库字段被转换为驼峰式以进行实体字段映射。

<?php

class Post
{

}

定义

除了从提供者自动建议定义之外,还可以使用定义而不是实体。

<?php

use Blast\Orm\Entity\Definition;
use Blast\Orm\Mapper;
use Blast\Orm\Query;

$definition = new Definition();
$definition->setConfiguration([
    'tableName' => 'user_role'
]);

//from mapper
$mapper = new Mapper($definition);

//from query
$query = new Query($connection, $definition);

可能的配置列表

<?php

$configuration = [
        'entity' => \ArrayObject::class,
        'entityCollection' => \SplStack::class,
        'events' => [],
        'fields' => [],
        'indexes' => [],
        'primaryKeyName' => ProviderInterface::DEFAULT_PRIMARY_KEY_NAME,
        'tableName' => '',
        'mapper' => Mapper::class,
        'relations' => []
    ];
$definition->setConfiguration($configuration);

定义也可以通过属性在实体上声明为FQCN

<?php

class Post
{
    public static $definition = '\Acme\Domain\Entities\Definition\PostDefinition';
}

或通过方法。

<?php

class Post
{
    public static function definition(){
        return '\Acme\Domain\Entities\Definition\PostDefinition';
    }
}

也可以传递定义对象而不是FQCN。

<?php

class Post
{
    public static function definition(){
        return new PostDefinition;
    }
}

提供者

提供者用于确定实体定义并将数据从实体中填充到实体中,反之亦然。您可以将对象、类名或表名传递给提供者。

如果实体类没有定义,则由提供者自动确定定义。定义由静态实体方法和/或属性表示。

使用提供者访问表格,而不需要创建实体类。这对于访问连接表或临时表很有用。

<?php

use Blast\Orm\Entity\Provider;

// add an entity as class name
$provider = new Provider(Post::class);

// add an entity as object
$provider = new Provider(Post::class);

// add an entity as table name
// entity object is an array object
$provider = new Provider('user_roles');

将定义添加到实体作为公共静态属性或方法。方法名称指的是上面提到的配置密钥。

表名

返回表名作为 string

  • 默认:类名无命名空间,驼峰式转换为下划线,例如 App\Entities\Post 将有 post 作为表名。
  • 静态方法:或 YourEntity::table()
  • 静态属性: YourEntity::$table
<?php

class Post
{

    /**
     * Get table name
     *
     * @var string
     */
    public static $tableName = 'post';
}
主键名称

返回主键名称作为 string

  • 默认: id
  • 静态方法: Entity::primaryKeyName()
  • 静态属性: YourEntity::$primaryKeyName
<?php

class Post
{

    /**
     * Get primary key name
     *
     * @var string
     */
    public static $primaryKeyName = 'id';
    
}
映射器

返回映射器类名作为 stringBlast\Orm\MapperInterface 的实例。

  • 默认:一个 Blast\Orm\Mapper 的实例。
  • 静态方法: Entity::mapper()
  • 静态属性: YourEntity::$mapper
<?php

use Blast\Orm\Mapper;

class Post
{

    /**
     * Get mapper for entity
     *
     * @return string
     */
    public static $mapper = Mapper::class;
}
关系

返回关系作为包含 Blast\Orm\Relations\RelationInterface 实例的 array

  • 默认: []
  • 静态方法: Entity::relations()
<?php

use Blast\Orm\Mapper;

class Post
{

    /**
     * Get mapper for entity
     *
     * @return string
     */
    public static function relations(EntityWithRelation $entity,  Mapper $mapper){
        return [
            $mapper->hasOne($entity, 'otherTable')
        ];
    }
}
从提供者访问定义

适配器允许访问数据和管理定义,即使您的实体类根本没有定义。

<?php

use Blast\Orm\Entity\Provider;

$post = new Post;

$postProvider = new Provider(Post::class);

获取表名

<?php
$tableName = $postProvider->getTableName();

获取主键名称

<?php
$primaryKeyName = $postProvider->getPrimaryKeyName();

获取实体映射器

<?php
$mapper = $postProvider->getMapper();

获取实体关系

<?php
$mapper = $postProvider->getRelations();

将数据作为数组注入实体

<?php
$entity = $postProvider->hydrate(['title' => 'Hello World']);

将数据作为实体转换为数组

<?php
$data = $postProvider->extract();

映射器

映射器为提供实体类准备查询以进行数据持久性和访问。所有方法总是返回一个查询实例,需要手动执行。还可以为查询添加事件监听器

为实体创建新的映射器

从连接获取特定于实体的映射器

<?php

$mapper = $connection->getMapper($post);

从提供者获取特定于实体的映射器

<?php

use Blast\Orm\Entity\Provider;

$provider = new Provider($post);
$mapper = $provider->getDefinition()->getMapper();

find

通过主键获取一条条目

<?php

$post = $mapper->find(1)->execute();

选择

自定义选择查询

<?php

$post = $mapper->select()
            ->where('title = "Hello world"')
            ->setMaxResults(1)
            ->execute(EntityHydratorInterface::RESULT_ENTITY);

删除

delete 期望一个主键或主键数组。

删除一个条目

<?php

$mapper->delete(1);

删除多个条目

<?php

$mapper->delete([1, 2]);

关系

关系提供对相关、父和子实体的访问。

传递关系

通过实体类中的计算静态关系方法将关系作为 array 传递。关系将自动映射到实体。

<?php

use Blast\Orm\Mapper;

class Post {

    private $comments = null;
    
    public function getComments(){
        return $this->comments;
    }

    public static function relation(Post $entity, Mapper $mapper){
        return [
            $mapper->hasMany($entity, Comments::class)
        ];
    }
}

$post = $mapper->find(1);
$comments = $post->getComments()->execute();

您还可以使用 RelationInterface::getQuery 扩展关系查询。

HasOne(一对一)

一个实体通过与其主键关联的字段与一个相关实体关联。

  • $entity - 当前实体实例
  • $foreignEntity - 实体类名、实例或表名
  • $foreignKey - 相关实体上的字段名称。默认为 null。空外键由当前实体表名和主键名称确定,如下所示: {tableName}_{primaryKeyName},例如 user_id
示例

一个用户有一个地址。

<?php

$relation = $mapper->hasOne($user, Address::class, 'user_id');

HasMany(一对一或多对一)

一个实体通过与其主键关联的字段与多个相关实体关联。

  • $entity - 当前实体实例
  • $foreignEntity - 实体类名、实例或表名
  • $foreignKey - 相关实体上的字段名称。默认为 null。空外键由当前实体表名和主键名称确定,如下所示: {tableName}_{primaryKeyName},例如 post_id
示例

一个帖子有许多评论。

<?php

$relation = $mapper->hasMany($post, Comments::class, 'post_id');

BelongsTo(一对一或多对一)

BelongsTo 是 HasOne 或 HasMany 关系的反向。

一个实体通过与其主键关联的字段与一个相关实体关联。

  • $entity - 当前实体实例
  • $foreignEntity - 实体类名、实例或表名
  • $localKey - 当前实体上的字段名称。默认为 null。空本地键由相关实体表名和主键名称确定,如下所示: {tableName}_{primaryKeyName},例如 post_id
示例

一个帖子有一个或多个评论。

<?php

$relation = $mapper->belongsTo($comment, Post::class, 'post_id');

ManyToMany(多对多)

许多类型为 A 的实体通过一个连接表与许多相关类型为 B 的实体关联。连接表存储了类型为 A 的实体到类型为 B 的实体的关联。

  • $entity:当前实体实例
  • $foreignEntity:实体类名、实例或表名
  • $foreignKey - 相关实体的字段名。默认为 null。空外键由当前主键名称确定。
  • $localKey:当前实体的字段名。默认为 null。空外键由相关实体主键名称确定。
  • $junction:连接表名。默认为 null。空表名由实体表名和外部实体表名确定,如下所示:{tableName}_{foreignTableName},例如 post_comment
  • $junctionLocalKey:相关实体的字段名。默认为 null。空连接本地键由当前实体表名和主键名称确定,如下所示:{tableName}_{primaryKeyName},例如 post_id
  • $junctionForeignKey:当前实体的字段名。默认为 null。空连接外键由相关实体表名和主键名称确定,如下所示:{tableName}_{primaryKeyName},例如 comment_id
示例

一个用户有多个角色,一个角色有多个用户。用户的主键名称是 id,角色的主键名称是 pk(主键简称)。连接表 user_role 包含 user_idrole_id 列。

<?php

$relation = $mapper->manyToMany($user, Role::class, 'pk', 'id', 'user_role', 'user_id', 'role_id');

仓库

仓库抽象了数据持久化和访问的方法。所有方法都直接执行其查询。

Blast ORM 提供了一个仓库接口 \Blast\Orm\RepositoryInterface

仓库知道它的实体。因此我们需要传递实体作为类名或实例

从接口创建

<?php

use Blast\Orm\MapperFactoryInterface;
use Blast\Orm\MapperFactoryTrait;
use Blast\Orm\RepositoryInterface;
use Blast\Orm\Hydrator\HydratorInterface;

class PostRepository implements MapperFactoryInterface, RepositoryInterface
{
    
    use MapperFactoryTrait;
    
    /**
     * Get repository entity
     */
    public function getEntity(){
        return Post::class;
    }

    /**
     * Get a collection of all entities
     *
     * @return \SplStack|array
     */
    public function all()
    {
        return $this->createMapper($this->getEntity())->select()->execute(HydratorInterface::HYDRATE_COLLECTION);
    }

    /**
     * Find entity by primary key
     *
     * @param mixed $primaryKey
     * @return \ArrayObject|\stdClass|object
     */
    public function find($primaryKey)
    {
        return $this->createMapper($this->getEntity())->find($primaryKey)->execute(HydratorInterface::HYDRATE_ENTITY);
    }

    /**
     * Save new or existing entity data
     *
     * @param object|array $data
     * @return int|bool
     */
    public function save($data)
    {
        return $this->createMapper($data)->save($data)->execute();
    }

}

通过抽象创建仓库

<?php

use Blast\Orm\AbstractRepository;

class PostRepository extends AbstractRepository {
    
    use \Blast\Orm\Entity\EntityAwareTrait;
    
    /**
     * Init repository and bind related entity
     */
    public function __construct(){
        $this->setEntity(Post::class);
    }
}

创建仓库实例

<?php

$postRepository = new PostRepository();

find

通过主键获取一条条目

<?php

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

all

获取所有条目并作为 Blast\Orm\Data\DataObject 集合返回

<?php

$posts = $postRepository->all();

foreach($posts as $post){

    //do something

}

save

保存操作会确定实体是否是新实体,并执行 Blast\Orm\Mapper::update 或如果是新实体则执行 Blast\Orm\Mapper::create

<?php

$post = new Post();

$post['title'] = 'Hello World';
$post['content'] = 'Some content about hello world.';
$post['date'] = new \DateTime();

//create or update entity
$repository->save($post);

进一步开发

请访问我们的 里程碑

变更日志

请参阅 CHANGELOG 了解最近更改的详细信息。

测试

$ composer test

贡献

请参阅 CONTRIBUTING 了解详细信息。

安全

如果您发现任何与安全相关的问题,请通过电子邮件 mjls@web.de 而不是使用问题跟踪器。

致谢

许可证

MIT 许可证(MIT)。有关更多信息,请参阅 许可证文件

[ico-coveralls]: https://img.shields.io/coveralls/phpthinktank/blast-orm/master.svg?style=flat-square)](https://coveralls.io/github/phpthinktank/blast-orm?branch=master