symlex/doctrine-active-record

面向对象的Doctrine DBAL CRUD

v4.6.1 2024-07-07 12:18 UTC

README

Latest Stable Version License Test Coverage Build Status Documentation

作为Doctrine ORM的轻量级替代品,这个经过实战检验的库提供了封装了Doctrine DBAL的商务模型和数据库访问对象(DAO)类,以提供高性能的面向对象的CRUD(创建、读取、更新、删除)功能。它比Datamapper ORM实现更快、更简单。请参阅TRADEOFFS.md

文档:https://docs.symlex.org/en/latest/doctrine-active-record/

Doctrine ActiveRecord

基本示例

<?php

use Doctrine\ActiveRecord\Dao\Factory as DaoFactory;
use Doctrine\ActiveRecord\Model\Factory;

$daoFactory = new DaoFactory($db); 

$modelFactory = new Factory($daoFactory);
$modelFactory->setFactoryNamespace('App\Model');
$modelFactory->setFactoryPostfix('Model');

// Returns instance of App\Model\UserModel
$user = $modelFactory->create('User'); 

// Throws exception, if not found
$user->find(123); 

if ($user->email == '') {
    // Update email
    $user->update(array('email' => 'user@example.com')); 
}

// Returns instance of App\Model\GroupModel
$group = $user->createModel('Group'); 

在REST控制器上下文中使用

Doctrine ActiveRecord非常适合构建高性能REST服务。

此示例演示了如何在REST控制器上下文中使用EntityModel。注意,如何轻松避免深层嵌套结构。用户模型和表单工厂(由InputValidation包提供)作为依赖项注入。

<?php

namespace App\Controller\Rest;

use Symfony\Component\HttpFoundation\Request;
use App\Exception\FormInvalidException;
use App\Form\FormFactory;
use App\Model\User;

class UsersController
{
    protected $user;
    protected $formFactory;

    public function __construct(User $user, FormFactory $formFactory)
    {
        $this->user = $user;
        $this->formFactory = $formFactory;
    }
    
    public function cgetAction(Request $request)
    {
        $options = array(
            'count' => $request->query->get('count', 50),
            'offset' => $request->query->get('offset', 0)
        );
        
        return $this->user->search(array(), $options);
    }

    public function getAction($id)
    {
        return $this->user->find($id)->getValues();
    }

    public function deleteAction($id)
    {
        return $this->user->find($id)->delete();
    }

    public function putAction($id, Request $request)
    {
        $this->user->find($id);
        
        $form = $this->formFactory->create('User\Edit');
        
        $form
            ->setDefinedWritableValues($request->request->all())
            ->validate();

        if($form->hasErrors()) {
            throw new FormInvalidException($form->getFirstError());
        } 
        
        $this->user->update($form->getValues());

        return $this->user->getValues();
    }

    public function postAction(Request $request)
    {
        $form = $this->formFactory->create('User\Create');
        
        $form
            ->setDefinedWritableValues($request->request->all())
            ->validate();

        if($form->hasErrors()) {
            throw new FormInvalidException($form->getFirstError());
        }
        
        $this->user->save($form->getValues());

        return $this->user->getValues();
    }
}

另请参阅:InputValidation for PHP – 用于任何来源输入数据的简单且安全的白名单验证

数据访问对象(DAO)

DAO直接与数据库表打交道,如果需要,也可以处理原始SQL。 Doctrine\ActiveRecord\Dao\Dao适用于使用原始SQL实现自定义方法。所有DAO默认公开以下公共方法

  • createDao(string $name): 返回一个新的DAO实例
  • beginTransaction(): 开始数据库事务
  • commit(): 提交数据库事务
  • rollBack(): 回滚数据库事务

此外,Doctrine\ActiveRecord\Dao\EntityDao还提供了许多强大的方法,可以轻松处理数据库表行

  • setData(array $data): 设置原始数据(例如在调用更新()时无法检测更改)
  • setValues(array $data): 设置多个值
  • setDefinedValues(array $data): 设置只存在于表模式中的值(比setValues()慢)
  • getValues(): 以数组形式返回所有值
  • find($id): 通过主键查找一行
  • reload(): 从数据库重新加载行值
  • getValues(): 以关联数组形式返回所有值
  • exists($id): 如果存在具有给定主键的行,则返回true
  • save(): 插入新行
  • update(): 更新数据库中的更改值
  • delete(): 从数据库删除实体
  • getId(): 返回当前加载记录的ID(如果为空则抛出异常)
  • hasId(): 如果DAO实例已分配ID(主键),则返回true
  • setId($id): 设置主键
  • findAll(array $cond = array(), $wrapResult = true): 返回匹配$cond的所有实例(如果想要限制或排序结果集,请使用search()或searchAll())
  • search(array $params): 返回一个SearchResult对象(有关支持的参数请参见下文)
  • wrapAll(array $rows): 为每个数组元素创建并返回一个新的DAO
  • updateRelationTable(string $relationTable, string $primaryKeyName, string $foreignKeyName, array $existing, array $updated): 用于更新多对多关系表的辅助函数
  • hasTimestampEnabled(): 如果此DAO在创建和更新行时自动添加时间戳,则返回true
  • findList(string $colName, string $order = '', string $where = '', string $indexName = ''):返回所有匹配行的键/值数组(列表)
  • getTableName():返回底层数据库表名
  • getPrimaryKeyName():返回主键列名(如果主键是数组,则抛出异常)

搜索参数

search()接受以下可选参数以限制、过滤和排序搜索结果

table:表名

table_alias:"table"的别名(用于连接和left_join的表引用)

cond:搜索条件作为数组(键/值或仅对原始SQL的值)

count:最大结果数(整数)

offset:结果偏移量(整数)

join:要连接的表列表,包括连接条件,例如array(array('u', 'phonenumbers', 'p', 'u.id = p.user_id')),参见Doctrine DBAL手册

left_join:见join

columns:列列表(数组)

order:排序顺序(如果不为false)

group:按组(如果不为false)

wrap:如果为false,则返回原始数组而不是DAO实例

ids_only:仅返回主键值

sql_filter:原始SQL过滤器(WHERE)

id_filter:如果不为空,则将结果限制到此主键ID列表

搜索结果

在调用search()EntityDaoEntityModel时,您将获得返回值SearchResult实例。它实现了ArrayAccessSerializableIteratorAggregateCountable,可以作为数组或对象使用,具有以下方法

getAsArray():以数组形式返回搜索结果

getSortOrder():返回排序顺序字符串

getSearchCount():返回搜索计数(限制)整数

getSearchOffset():返回搜索偏移量整数

getResultCount():返回实际查询结果数(<=限制)

getTotalCount():返回总结果计数(在数据库中)

getAllResults():返回所有结果作为EntityDaoEntityModel实例的数组

getAllResultsAsArray():以嵌套数组形式返回所有结果(例如,将其序列化为JSON)

getFirstResult():返回第一个结果EntityDaoEntityModel实例,或抛出异常

实体配置

DAO实体使用受保护的类属性进行配置

<?php

protected $_tableName = ''; // Database table name
protected $_primaryKey = 'id'; // Name or array of primary key(s)
protected $_fieldMap = array(); // 'db_column' => 'object_property'
protected $_hiddenFields = array(); // Fields that should be hidden for getValues(), e.g. 'password'
protected $_formatMap = array(); // 'db_column' => Format::TYPE
protected $_valueMap = array(); // 'object_property' => 'db_column'
protected $_timestampEnabled = false; // Automatically update timestamps?
protected $_timestampCreatedCol = 'created';
protected $_timestampUpdatedCol = 'updated';

<$_formatMap>的可能值在Doctrine\ActiveRecord\Dao\Format中定义为常量

<?php

const NONE = '';
const INT = 'int';
const FLOAT = 'float';
const STRING = 'string';
const ALPHANUMERIC = 'alphanumeric';
const SERIALIZED = 'serialized';
const JSON = 'json';
const CSV = 'csv';
const BOOL = 'bool';
const TIME = 'H:i:s';
const TIMEU = 'H:i:s.u'; // Support for microseconds (up to six digits)
const TIMETZ = 'H:i:sO'; // Support for timezone (e.g. "+0230")
const TIMEUTZ = 'H:i:s.uO'; // Support for microseconds & timezone
const DATE = 'Y-m-d';
const DATETIME = 'Y-m-d H:i:s';
const DATETIMEU = 'Y-m-d H:i:s.u'; // Support for microseconds (up to six digits)
const DATETIMETZ = 'Y-m-d H:i:sO'; // Support for timezone (e.g. "+0230")
const DATETIMEUTZ = 'Y-m-d H:i:s.uO'; // Support for microseconds & timezone
const TIMESTAMP = 'U';

示例

<?php

namespace App\Dao;

use Doctrine\ActiveRecord\Dao\EntityDao;

class UserDao extends EntityDao
{
    protected $_tableName = 'users';
    protected $_primaryKey = 'user_id';
    protected $_timestampEnabled = true;
}

业务模型

业务模型在逻辑上位于控制器数据访问对象(DAO)之间,控制器负责渲染视图和验证用户输入,而DAO是低级接口,用于与存储后端或Web服务进行交互。

模型的公共接口是高级的,应该反映其领域内的所有用例。在基类Doctrine\ActiveRecord\Model\EntityModel中预实现了许多标准用例

createModel(string $name = '', Dao $dao = null):创建新的模型实例

find($id):根据主键查找记录

reload():从数据库重新加载值

findAll(array $cond = array(), $wrapResult = true):查找多个记录;如果$wrapResult为false,则返回原始DAO而不是模型实例

search(array $cond, array $options = array()):返回一个SearchResult对象($options可以包含计数、偏移量、排序顺序等,参见上面DAO文档中的search())

searchAll(array $cond = array(), $order = false):search()的简单版本,类似于findAll()

searchOne(array $cond = array()):搜索单个记录;如果找到0个或多个记录,则抛出异常

searchIds(array $cond, array $options = array()):返回匹配给定搜索条件的匹配主键ID数组

getModelName():返回不带前缀和后缀的模型名称

getId(): 返回当前加载记录的ID(如果为空则抛出异常)

hasId(): 如果模型实例分配了ID(主键),则返回true

getValues(): 返回所有模型属性作为关联数组

getEntityTitle(): 返回此实体的通用名称

isDeletable(): 如果模型实例可以通过delete()方法删除,则返回true

isUpdatable(): 如果模型实例可以通过update($values)方法更新,则返回true

isCreatable(): 如果可以在数据库中通过create($values)方法创建新实体,则返回true

batchEdit(array $ids, array $properties): 更新多个记录的数据

getTableName(): 返回相关的主要数据库表名称

hasTimestampEnabled(): 如果为相关DAO启用了时间戳,则返回true

delete(): 从数据库中永久删除实体记录

save(array $values): 使用提供的值创建新记录

update(array $values): 更新模型实例的数据库记录;在将多个值分配给模型实例之前,应使用表单类验证数据

模型中应实现多少验证? 在无效数据可能导致安全问题或重大不一致的地方,必须在模型层实现一些核心验证规则。模型异常消息通常不需要翻译(在多语言应用程序中),因为无效值应该由表单类预先识别。如果您期望某些异常,应在控制器中捕获和处理它们。

模型通过受保护的类属性与各自的Dao关联

protected $_daoName = ''; // DAO class name without namespace or postfix

示例

<?php

namespace App\Model;

use Doctrine\ActiveRecord\Model\EntityModel;

class User extends EntityModel
{
    protected $_daoName = 'User';

    public function delete() 
    {
        $dao = $this->getEntityDao();
        $dao->is_deleted = 1;
        $dao->update();
    }

    public function undelete() 
    {
        $dao = $this->getEntityDao();
        $dao->is_deleted = 0;
        $dao->update();
    }

    public function search(array $cond, array $options = array()) 
    {
        $cond['is_deleted'] = 0;
        return parent::search($cond, $options);
    }

    public function getValues()
    {
        $result = parent::getValues();
        unset($result['password']);
        return $result;
    }
}

单元测试

此库附带一个docker-compose.yml文件和MySQL数据库测试用例,用于运行单元测试(MySQL将绑定到127.0.0.1:3308)

localhost# docker-compose up -d
localhost# docker-compose exec mysql sh
docker# cd /share/src/Tests/_fixtures
docker# mysql -u root --password=doctrine doctrine-active-record < schema.sql
docker# exit
localhost# bin/phpunit 
PHPUnit 7.3.2 by Sebastian Bergmann and contributors.

................................................................. 65 / 91 ( 71%)
..........................                                        91 / 91 (100%)

Time: 251 ms, Memory: 8.00MB

OK (91 tests, 249 assertions)
localhost# docker-compose down

Composer

要在项目中使用此库,只需运行composer require symlex/doctrine-active-record或将"symlex/doctrine-active-record"添加到您的composer.json文件,然后运行composer update

{
    "require": {
        "php": ">=7.1",
        "symlex/doctrine-active-record": "^4.0"
    }
}

关于

Doctrine ActiveRecord 由Michael Mayer维护。如果您有任何问题、需要商业支持或只是想打个招呼,请随时发送电子邮件至hello@symlex.org。我们欢迎各种形式的贡献。如果您有错误或想法,在提交问题之前,请先阅读我们的指南

注意:此库是Symlex(一个基于Symfony的敏捷Web开发框架堆栈)的一部分,而不是官方的Doctrine项目。