如何在 Laravel Eloquent ORM 中使用数据库特定函数进行模块化使用

How to use database specific functions in Laravel Eloquent ORM for modular usage

我正在开发一个项目,它使用 ORM 尽可能地在每个数据库系统上制作项目 运行。 项目现在使用 postgresql。我想知道如何在不丢失 ORM 模块化的情况下使用数据库特定功能。

例如: 我必须像这样对一个查询使用 "extract" 函数;

DELETE FROM tokens AS t WHERE (extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())

如果我想使用模型 class 来实现这个。迟早我需要以原始格式

编写提取函数 where 子句
Tokens::whereRaw('(extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())')->get();

如果我使用查询生成器

DB::table('tokens')->whereRaw('(extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())')->select()->get();

同样的事情发生

我需要像当我使用 postgresql ORM 时需要使用 EXTRACT() 函数或当我使用 mysql ORM 时需要使用 UNIX_TIMESTAMP() 函数

我可以使用哪些方法来实现这一点?

从模型中去掉这个逻辑。

为 Postgres 创建一个存储库,我们称之为 PostgresTokenRepository。这个存储库的构造函数应该看起来像...

<?php

class PostgresTokenRepository implements TokenRepositoryInterface
{
    protected $token;

    public function __construct(Token $token)
    {
        $this->token = $token;
    }

    public function getTokens()
    {
        return $this->token->whereRaw('(extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())')->get();
    }
}

并且您将需要一个界面...TokenRepositoryInterface

interface TokenRepositoryInterface
{
    public function getTokens();
}

现在你应该就存储库进行了所有设置。如果您需要执行 MySQL 实现,只需创建一个 MysqlTokenRepository,除了 getTokens() 函数将使用 UNIX_TIMESTAMP() 外,它看起来很相似。

现在您需要告诉 Laravel,当您寻找 TokenRepositoryInterface 的实现时,它应该 return PostgresTokenRepository。为此,我们需要创建一个服务提供者。

<?php

class UserServiceProvider extends \Illuminate\Support\ServiceProvider
{
    public function register()
    {
        $this->app->bind('TokenRepositoryInterface', 'PostgresTokenRepository');
    }
}

现在唯一要做的就是将此服务提供商添加到 config/app.php 中的服务提供商数组中。

现在,只要您在控制器中需要此存储库,就可以自动注入它们。这是一个例子...

class TokenController extends BaseController
{

    protected $token;

    public function __construct(TokenRepositoryInterface $token)
    {
        $this->token = $token;
    }

    public function index()
    {
        $tokens = $this->token->getTokens();

        return View::make('token.index')->with('tokens', $tokens);
    }
}

这样做的目的是当你想开始使用MySQL实现时,你所要做的就是将服务提供者修改为return MysqlTokenRepository而不是PostgresTokenRepository。或者,如果您想一起编写一个新的实现,则无需更改生产代码即可实现。如果某事不起作用,只需将该行改回 PostgresTokenRepository.

另一个让我信服的好处是,这让您能够让您的模型和控制器保持非常轻便且非常可测试。

这可以放在各自的驱动程序中,但 Taylor Otwell 对驱动程序特定功能的看法是,您应该像您一样使用 raw 语句。

但是在 Eloquent 中你可以很容易地自己做:

// BaseModel / trait / builder macro or whatever you like
public function scopeWhereUnix($query, $col, $operator = null, $value = null)
{
    if (func_num_args() == 3)
    {
        list($value, $operator) = array($operator, '=');
    }

    switch (class_basename($query->getQuery()->getConnection()))
    {
        case 'MySqlConnection':
            $col = DB::raw("unix_timestamp({$col})");
            break;
        case 'PostgresConnection':
            $col = DB::raw("extract(epoch from {$col})");
            break;
    }

    $query->where($col, $operator, $value);
}

现在您可以这样做了:

Tokens::whereUnix('created_at', 'value')->toSql();
// select * from tokens where unix_timestamp(created_at) = 'value'
// or
// select * from tokens where extract(epoch from created_at) = 'value'

你的条件有点复杂,但你仍然可以通过一些 hack 来实现:

Tokens::whereUnix(DB::raw('created_at) + (expires', '<', time())->toSql();
// select * from tokens where unix_timestamp(created_at) + (expires) < 12345678
// or
// select * from tokens where extract(epoch from created_at) + (expires) < 12345678

不幸的是 Query\Builder (DB::table(..)) 不是那么容易扩展 - 事实上它根本不可扩展,所以你需要用你自己的 Builder 交换它 class ,比较麻烦。

我最终创建了一个全局范围。创建了一个像 ExpiresWithTimestampsTrait 这样的特征,其中包含 whereExpires 范围的逻辑。范围确实添加了特定于数据库驱动程序的 where 子句。

public function scopeWhereExpired($query)
{
    // Eloquent class is my helper for getting connection type based on the @jarek's answer
    $type = Eloquent::getConnectionType($this);
    switch ($type) {
        case Eloquent::CONNECTION_POSTGRESS:
            return $query->whereRaw("(round(extract(epoch from (created_at)) + expires)) < round(extract(epoch from LOCALTIMESTAMP))");
            break;
        default:
            break;
    }
}

所以我只需要在模型上使用那个特征。当我将来开始使用 mysql

时,我只需要向 whereExpires 范围添加一个 "case" 子句以支持 mysql 和 where 子句

谢谢大家!