【问题标题】:How to use database specific functions in Laravel Eloquent ORM for modular usage如何在 Laravel Eloquent ORM 中使用数据库特定函数进行模块化使用
【发布时间】:2015-01-15 09:57:08
【问题描述】:

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

例如: 我必须像这样对一个查询使用“提取”功能;

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

如果我想使用模型类来实现这一点。迟早我需要以原始格式编写提取函数 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() 函数时

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

【问题讨论】:

    标签: mysql postgresql laravel orm eloquent


    【解决方案1】:

    这可以在各自的驱动程序中使用,但 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'
    

    你的情况有点复杂,但你仍然可以通过一些技巧来实现:

    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 类交换,这相当麻烦。

    【讨论】:

    • 我根据你的逻辑来处理这个问题。你可以看看我的回答。不幸的是,由于我的代表很低,我无法为您的回答 +1 :(
    【解决方案2】:

    从模型中去掉这个逻辑。

    为 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实现时,你所要做的就是修改服务提供者返回MysqlTokenRepository而不是PostgresTokenRepository。或者如果你想一起编写一个新的实现,这一切都是可能的,而无需更改生产代码。如果某些东西不起作用,只需将这一行改回PostgresTokenRepository

    另一个卖给我的好处是,这使您能够保持模型和控制器非常轻巧且非常可测试。

    【讨论】:

      【解决方案3】:

      我最终创建了一个全局范围。创建了一个类似 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;
          }
      }
      

      所以我只需要在模型上使用该特征。我只需要在 whereExpires 范围内添加一个“case”子句,以便将来在我开始使用 mysql 时使用 where 子句支持 mysql

      谢谢大家!

      【讨论】:

        猜你喜欢
        • 2012-11-03
        • 1970-01-01
        • 2015-03-14
        • 1970-01-01
        • 1970-01-01
        • 2015-05-09
        • 2014-08-08
        • 2012-09-24
        • 1970-01-01
        相关资源
        最近更新 更多