alex-unruh/repository

基于Doctrine DBAL查询构建器的抽象层

v1.1.0 2022-04-13 13:23 UTC

This package is auto-updated.

Last update: 2024-09-13 19:07:32 UTC


README

这是一个扩展Doctrine DBAL查询构建器的抽象层,提供了帮助减少绑定值代码量的方法,例如。

安装

composer require alex-unruh/repository

用法

下面是一个使用Doctrine DBAL QueryBuilder进行插入查询的场景示例

// index.php
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Query\QueryBuilder;

$conn = DriverManager::getConnection($connection_params);
$query_builder = $conn->createQueryBuilder();

$query_builder->insert('users')
    ->setValue('Name', '?')
    ->setValue('CountryCode', '?')
    ->setValue('District', '?')
    ->setValue('Population', '?')
    ->setParameter(0, 'Osasco')
    ->setParameter(1, 'BRA')
    ->setParameter(2, 'São Paulo')
    ->setParameter(3, '800000')
    ->executeStatement();

现在让我们看看使用Repository::class进行相同查询的抽象化示例

// index.php
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->insert('city')
    ->addvalues(['Name' => 'Osasco', 'CountryCode' => 'BRA', 'District' => 'São Paulo', 'Population' => '800000'])
    ->execute();

在幕后,仓库类执行的是完全相同的程序,安全地进行绑定并返回相同的结果。如果你有一个大表,这将非常有用。实际上,Doctrine DBAL Query Builder中所有的方法都存在于Repository::class中,因为,正如我们已经说过的,它扩展了DBAL QB。你可以自由地使用它们。

父类(Doctrine DBAL QueryBuilder)中不存在的方法

setConnection(array $connection_params): Repository

如果你需要在多个查询中使用相同的连接,如事务,这非常有用

//index.php

use AlexUnruh\Repository;

$conn = DriverManager::getConnection($connection_params);
$conn->transactional({
  $repo = new Repository();
  $repo->setConnection($conn);
  
  $repo->select('author_id')->from('posts')->where('slug = :slug')->setParameter('slug', 'my-post');
  $result = $repo->getFirst();
  $id = $result['author_id'];

  $repo->resetQueryParts();
  $repo->update('users')->setValues(['best_post' => true])->where("id = {$id}")->execute();
});

get()

用于select语句的末尾,返回多维数组

// index.php
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->select('*')->from('users')->get();

// Returns
/*
 [
  [
    'id' => '1',
    'name' => 'Foo',
    'username' => 'Bar,
    'password' => 'foobar'
  ],
  [
    'id' => '2',
    'name' => 'Bar',
    'username' => 'Foo,
    'password' => 'barfoo'
  ],
 ]
*/

getFirst()

用于select语句的末尾,只返回查询的第一个结果

// index.php
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->select('*')->from('users')->getFirst();

// Returns
/*
  [
    'id' => '1',
    'name' => 'Foo',
    'username' => 'Bar,
    'password' => 'foobar'
  ]
*/

addValues(array $array_data)

  • @param array $array_data = 包含要插入到表中的键 => 值对的数组
// index.php
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->insert('users')->addValues(['name' => 'Foo', 'email' => 'foo@bar.com', 'pass' => $encripted_pass])->execute();

setValues(array $array_data)

  • @param array $array_data = 包含要更新到表中的键 => 值对的数组
// index.php
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->update('users')->setValues(['name' => 'Foo', 'email' => 'foo@bar.com', 'pass' => $encripted_pass])->execute();

execute()

execute方法负责在插入、更新和删除语句中安全地绑定参数,并返回受影响的行数。它通过调用Doctrine DBAL父类的setParameters()和executeStatement()方法来实现。

// index.php 
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->update('users')->setvalues(['name' => 'New Name'])->execute();

尽管不可见,但在setValues和execute方法之间将存在一个bindParam

setTypes(array $types): Repository

  • @param array $types = 包含要用于需要指定要插入或更新的数据类型的查询的键 => 值对的数组。请参阅Doctrine DBAL文档中的更多内容

在某些情况下,当你在Doctrine DBAL或其他查询构建器中工作时,需要设置特定列数据的类型。例如,如果你在一个表中存储加密数据,可能需要告诉查询构建器这种数据的类型,因为每种类型的数据库以不同的方式存储这种数据。

在其他情况下,出于安全考虑,在存储之前需要设置值的类型,在setParameter方法中。如果类型与定义的不同,将抛出异常。

让我们看看使用Doctrine DBAL QueryBuilder的示例

// index.php 
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Query\QueryBuilder;

$conn = DriverManager::getConnection($connection_params);
$query_builder = $conn->createQueryBuilder();

$query_builder->insert('posts')
    ->setValue('name', ?)
    ->setValue('slug', ?)
    ->setValue('author_id', ?)
    ->setValue('image', ?)
    ->setParameter(0, $post_name, 'string')
    ->setparameter(1, $slug, 'string')
    ->setparameter(2, $author_id, 'integer')
    ->setparameter(3, $encrypted_data, 'blob')
    ->executeSatetment();

现在,让我们看看使用Repository::class的示例

// index.php 
use AlexUnruh\Repository;

$repo = new Repository($connection_params);
$repo->setTypes(['name' => 'string', 'slug' => 'string', 'author_id' => 'string', 'image' => 'blob']);
$repo->insert('posts')
  ->addValues(['name' => $post_name, 'slug' => $slug, 'author_id' => $author_id. 'image' => $encripted_data])
  ->execute();

扩展Repository::class

大多数查询可以使用父仓库类中的方法执行。但在某些情况下,你可能会有更复杂的查询(例如包含子查询的查询),这会使你的控制器“膨胀”,并且你希望将这些查询放在单独的类中,使用仓库模式。为此,你可以创建自己的类并扩展父仓库类。

所有继承自Repositor::class的类都需要至少有一个受保护的$table_name属性,才能使用以下将要描述的特殊CRUD方法。

继承类可以实现的另一个参数是$data_types,它包含上面setTypes方法中描述的数据类型。

不要浪费时间试图理解下面的方法。虽然它可行,但这里只是为了演示Repository模式的用法。

// MyRepo.php
use AlexUnruh\Repository;

class MyRepo extends Repository
{
  protected $table_name = 'images';
  protected $data_types = [];

  /**
  * Remember, we are extending the Query Builder class, so we use "$this" here
  */
  public function lockRecord(int $status)
  {
    $id = uniqid(rand(), true);
    $now = date('Y-m-d H:i:s');
    $max_time = date('Y-m-d H:i:s', strtotime('+5 minutes', strtotime($now)));

    $subquery = $this->select('uuid')
      ->distinct()
      ->from('another_table')
      ->where('status = ?')
      ->setMaxResults(1)
      ->getSQL();

    $this->resetQueryParts();
    $this->modify(['time_lock' => "'$max_time'", 'id_lock' => $id])
      ->where("id_lock = 0 OR time_lock < '{$now}'")
      ->andwhere('cancel = 0')
      ->andWhere('status = ?')
      ->andWhere("uuid IN ($subquery)")
      ->setParameter(0, $status)
      ->setParameter(1, $status);

    return $this->execute() ? true : false;
  }
}

// index.php
$repo = new MyRepo($connection_params);
$repo->lockRecord(1);

仅适用于扩展类的可用方法:读取、创建、修改和删除。

以下所有方法都需要在继承自Repositor::class的类中使用,因为它们使用了这些类中定义的参数,例如$table_name或$data_types。

read(array $data, string $table_alias = null): Repository

  • @param array $array_data = 包含要从表中选择的数据的数组
  • @param string $table_alias = 在连接语句中使用的表别名(可选)
// index.php

// example 1
$user = new UserRepo($connection_params);
$result = $user->read(['name', 'username'])->where('id' => 5)->getFirst();

// example 2 (With table alias an join)
$user = new UserRepo($connection_params);

// second parameter "a" is the table alias to users table
$user->read(['a.name as author', 'b.*'], 'a')
    ->join('a', 'posts', 'b', 'b.author_id' = 'a.id')
    ->where('a.id = :id')
    ->setparameter('id', $id);

$result = $user->get();

create(array $data): Repository

  • @param array $array_data = 包含要插入到表中的键 => 值对的数组

在幕后,repository::class将会使用$data_types数组安全地绑定值到每个列,如果这些值存在于类属性中。

// index.php
$user = new UserRepo($connection_params);
$user->create(['name' => 'Foo', 'email' => 'foo@bar.com', 'pass' => $encripted_pass])->execute();

modify(array $data, string $table_alias = null): Repository

  • @param array $array_data = 包含要更新到表中的键 => 值对的数组
  • @param string $table_alias = 在连接语句中使用的表别名(可选)

在幕后,repository::class将会使用$data_types数组安全地绑定值到每个列,如果这些值存在于类属性中。始终与WHERE子句一起使用。

//index.php 
$user = new UserRepo($connection_params);
$user->modify(['name' => 'Foo', 'email' => 'foo@bar.com', 'pass' => $encripted_pass])->where('id = :id')->addParameter('id', $id)->execute();

destroy(string $table_alias = null): Repository

  • @param string $table_alias = 在连接语句中使用的表别名(可选)
// index.php
$user = new UserRepo($connection_params);
$user->destroy()->where('id = :id')->setParameter('id', $id, 'integer')->execute();

addParameter(string $key, string $val, string $type = null): Repository

  • @param string $key = 参数键
  • @param string $val = 参数值
  • @param string $type = 参数类型

此方法建议在您使用modify方法并需要在其他子句(如WHERE子句)中绑定新参数时使用。

// index.php
$user = new UserRepo($connection_params);
$user->modify(['name' => 'Foo', 'email' => 'foo@bar.com', 'pass' => $encripted_pass])->where('id = :id')->addParameter('id', $id)->execute();

享受吧。