peak/database

数据库通用工具 + Laravel 数据库包装 + 使用 Phinx 的数据库迁移

1.0.2 2019-08-27 17:38 UTC

This package is auto-updated.

Last update: 2024-09-28 05:49:22 UTC


README

此包的目的包括

  • 为 DDD 和 Clean 架构提供通用的数据库工具
  • 提供带有 Phinx 迁移的数据库迁移
  • 便于在非 Laravel 项目中集成 Laravel 数据库

安装

 composer require peak/database

Laravel 数据库使用

use Peak\Database\Laravel\DatabaseService;

$config = [
    'driver' => 'mysql',
    'host' => 'localhost',
    'port' => '3306',
    'database' => 'database',
    'username' => 'root',
    'password' => 'root',
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
];
    
$db = (new DatabaseService())->createConnection($config, 'connectionName');

就是这样!查看 Laravel 查询构建器 了解如何进行查询的更多信息。

数据库迁移使用

对于数据库迁移,在你的项目根目录创建一个名为 phinx.php 的文件。该文件应返回一个 Phinx 配置数组。

<?php

namespace {

    use Peak\Database\Common\LaravelPhinxMigration;
    use Peak\Database\Laravel\LaravelDatabaseService;
    use Peak\Database\Laravel\LaravelConnectionManager;
    use Peak\Database\Phinx\PhinxConfigService;
    use Peak\Database\Phinx\PhinxEnvConfig;

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

    try {
        $env = getenv();
        $db = (new LaravelDatabaseService())->createConnection([
           'driver' => $env['DB_DRIVER'],
           'host' => $env['DB_HOST'],
           'port' => $env['DB_PORT'],
           'database' => $env['DB_DATABASE'],
           'username' => $env['DB_USERNAME'],
           'password' => $env['DB_PASSWORD'],
           'charset' => $env['DB_CHARSET'],
           'collation' => $env['DB_COLLATION'],
           'prefix' => $env['DB_PREFIX'],
       ], 'connectionName');
        LaravelConnectionManager::setConnection($db, 'prod');

        return (new PhinxConfigService())
            ->create(
                'migrations',
                LaravelPhinxMigration::class,
                'migrations',
                'prod',
                [
                    new PhinxEnvConfig('prod', [
                        'name' => $db->getDatabaseName(),
                        'connection' => $db->getPdo(),
                    ])
                ]
            );

    } catch(\Exception $e) {
        die($e->getMessage());
    }
}

上面的 phinx.php 允许在迁移中使用 Laravel 数据库,借助 LaravelPhinxMigration

<?php

use Peak\Database\Laravel\LaravelPhinxMigration;
use Illuminate\Database\Schema\Blueprint;

class Users extends LaravelPhinxMigration
{
    public function up()
    {
        $this->db->getSchemaBuilder()->create('users', function(Blueprint $table){
            $table->increments('id');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->string('password');
            $this->tsColumns($table);
            $table->timestamp('lastSeen')->nullable()->default(null);
        });
    }

    public function down()
    {
        $this->db->getSchemaBuilder()->drop('users');
    }
}

通用工具

通用工具的目的是帮助您表达查询,而不依赖于特定的数据库框架或任何数据库。

  • 使用 QueryFilters 来表达 "where" 语句。
  • 使用 QueryPagination 来表达分页,如排序和限制/偏移量语句。

需要注意的是,这些通用工具本身并不做任何事情。它们主要作为领域或用例与实际数据库或存储实现之间的 "边界" 接口。在您的最终实现中,您将需要一个构建器/辅助工具将通用表达式转换为实际的数据库框架/orm。

创建通用查询 "where" 过滤器和通用查询 "分页" 的示例

$queryFilters = new QueryFilters();
$queryFilters
    ->setColumns(['id', 'title'])
    ->where('level', '6', '>')
    ->orWhere('level', '2', '<')
    ->orWhereArray((new QueryFilters())
        ->where('status', 'online')
        ->where('type', '2')
        ->whereNull('ban')
        ->whereNotNull('deletedAt')
    );

$queryPagination = new QueryPagination(
    $column, 
    $direction, 
    $pageNumber, 
    $itemsPerPages
);

$queryFilters$queryPagination 传递给一个用例。这将帮助在用例和存储库之间创建边界,因为用例不必了解您实现(数据库框架/orm 等)的详细信息。

<?php

namespace Domain\UseCase;

use Peak\Database\Generic\QueryFiltersInterface;
use Peak\Database\Generic\QueryPaginationInterface;

class MyUseCase 
{
    // ...
    public function execute(
        QueryFiltersInterface $queryFilters,
        QueryPaginationInterface $queryPagination
    ) {
        // do things
        // ...
       
        return $this->repository->getMany($queryFilters, $queryPagination);
    }
}

最后,我们在存储库实现中使用 LaravelGenericHelper 将通用 QueryFiltersInterface 转换为实际的 Laravel 查询构建器 "where" 表达式;

<?php

use Domain\Repository\MyRepositoryInterface;
use Peak\Database\Generic\QueryFiltersInterface;
use Peak\Database\Generic\QueryPaginationInterface;
use Peak\Database\Common\LaravelGenericHelper;

class MyRepository implements MyRepositoryInterface
{
    // ...
    
   public function getMany(
       QueryFiltersInterface $queryFilters,
       QueryPaginationInterface $queryPagination
   ) {
       $qb = $this->table('tusers');
       $qb = LaravelGenericHelper::filterQuery($qb, $queryFilters);
       $qb = LaravelGenericHelper::paginateQuery($qb, $queryPagination);
       return $qb->get();
   }
}

我们可以在用例中直接使用 Laravel 查询构建器,但这可能会使代码过于依赖特定的数据库库(此处为 Laravel 数据库)。通过使用通用查询过滤器和分页,可以非常容易地测试存储库和用例,而无需真实数据库连接。

有关 Laravel 数据库分页和过滤器的安全重要信息

来自 Laravel 数据库文档

Laravel 查询构建器使用 PDO 参数绑定来保护您的应用程序免受 SQL 注入攻击。不需要清理作为绑定传递的字符串。但是

"PDO 不支持绑定列名。因此,您绝对不应该允许用户输入决定查询中引用的列名,包括 "order by" 列等。如果您必须允许用户选择要查询的列,请始终将列名与允许的列的白名单进行验证。"

如果您允许用户选择列名,应创建一个扩展 AbstractRestrictedQueryPagination 的类来防止不想要的列名。

class UserPagination extends AbstractRestrictedQueryPagination
{
    protected $allowedColumns = [
        'username', 'email', 'createdAt', 'updatedAt', 'deletedAt'
    ];
    
    protected $allowedDirections = [
        'asc', 'desc'
    ];
}


// and use it like this:

$queryPagination = new UserPagination(
    $column, 
    $direction, 
    $pageNumber, 
    $itemsPerPages
);

相同的做法可以应用于查询过滤器的列和运算符,使用 AbstractRestrictedQueryFilters

class UserFilters extends AbstractRestrictedQueryFilters
{
    protected $allowedColumns = [
        'username', 'email', 'createdAt', 'updatedAt', 'deletedAt'
    ];
    
    protected $allowedOperators = [
        '=', '>', '<', 'like'
    ];
}


// and use it like this:

$queryFilters = new UserFilters();
$queryFilters
    ->where('username', 'bob', '=')
    //...