如何在 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 子句
谢谢大家!
我正在开发一个项目,它使用 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 子句谢谢大家!