我在潜在客户和状态之间有一个很好的关系。潜在客户是潜在客户。状态可以是活动的,保留的,取消订阅的等等。我有以下表格:
| Leads | Lead_Status | Statuses |
|---------|--------------|----------|
| - id | - lead_id | - id |
| - name | - status_id | - name |
我们为每个潜在客户提供多种状态,因为我们需要跟踪分析的状态历史记录。这一切都很有效。如果在大多数查询中使用范围,则通常将global scope而不是query scope应用于模型。我的范围做了一些丑陋的事情来检索潜在客户的当前状态,并使用$lead->status
之类的内容轻松访问它,就像我们得到$lead->name
一样。以下是使用newQuery在获取Lead::
时向初始查询添加查询信息的范围确定过程:
public function newQuery() {
return parent::newQuery()->select(DB::raw('leads.*, status.status_id AS status'))->from(DB::raw(' leads
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
SELECT lead_id, max(created_at) AS max_created_at
FROM lead_status GROUP BY lead_id
) AS ls2 INNER JOIN lead_status AS ls ON ls.lead_id = ls2.lead_id AND ls.created_at = ls2.max_created_at
) AS status ON leads.id = status.lead_id
'));
}
这非常有效。它返回带有id,名称和状态的潜在客户。
现在问题:
当我尝试创建新的潜在客户时,我收到错误:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near
'
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
' at line 2
(SQL:
insert into leads
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
SELECT lead_id, max(created_at) AS max_created_at
FROM lead_status GROUP BY lead_id
) AS ls2
INNER JOIN lead_status AS ls ON ls.lead_id = ls2.lead_id
AND ls.created_at = ls2.max_created_at
) AS status ON leads.id = status.lead_id
(`id`, `name`, `updated_at`, `created_at`) values (test5, Dustin Griffith, 2014-12-03 18:24:06, 2014-12-03 18:24:06)
)
很明显,潜在客户的目标是Lead::create()
。这不是我想要的。我希望它只在我们从潜在客户表中选择数据时确定范围。所以我的主要问题是,当它是一个select语句时,我如何告诉它只有范围?
我还使用了global scope下的laravel文档中看到的ScopeInterface
方法,这种方法更复杂但在某些情况下更受青睐。我可以补充一点,如果我们应该建立它而不是旧的newQuery
方法。我用这种方法得到了相同的结果。
我不是在寻找一个hacky解决方案。任何输入都将非常感激,但我正在寻找处理这些情况的正确方法,因为我将来会一次又一次地这样做。
答案 0 :(得分:2)
newQuery
不是正确的方法。我们想要使用的是问题中提到的Global Scope方法。这允许我们定义每次调用查询时使用的范围,以及如何在不需要时删除范围。 Laravel非常聪明,可以在需要时删除范围(创建,更新,删除)。文件读到:
如果Eloquent模型使用具有与bootNameOfTrait命名约定匹配的方法的特征,则在引导Eloquent模型时将调用该特征方法,从而为您提供注册全局范围或执行任何其他操作的机会。范围必须实现ScopeInterface,它指定两个方法:apply和remove。
我跳过了特征部分并将以下内容放在模型本身中,但是对于组织来说,将其分解为自己的特征是明智的。我在模型中添加了以下内容:
// Import the classes
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ScopeInterface;
// Add the global scope
public static function boot() {
parent::boot();
static::addGlobalScope(new StatusColumnScope);
}
// Create the scope
class StatusColumnScope implements ScopeInterface {
// Create our apply method that will be called for every query on this model
public function apply(Builder $builder) {
$builder->select(DB::raw('leads.*, status.status_id AS status'))->from(DB::raw(' leads
LEFT OUTER JOIN (
SELECT ls.status_id, ls.lead_id
FROM (
SELECT lead_id, max(created_at) AS max_created_at
FROM lead_status GROUP BY lead_id
) AS ls2 INNER JOIN lead_status AS ls ON ls.lead_id = ls2.lead_id AND ls.created_at = ls2.max_created_at
) AS status ON leads.id = status.lead_id
'));
}
// Create the remove method that is called every time we need to unscope
public function remove(Builder $builder) {
$query = $builder->getQuery();
$query->selectRaw('select *')->from('leads');
}
}
当我们需要取消范围时,会调用上面的remove方法。起初这让我感到困惑,因为我认为如果我们不需要范围,它就不应该是范围。这不是Laravel的工作方式。这只是在真正深入挖掘后才变得更具吸引力。我会告诉你我所看到的。让我们在Laravel的引擎盖下看一下Eloquent/Builder.php
中的一些方法:
创建:
public static function create(array $attributes) {
$model = new static($attributes);
$model->save();
return $model;
}
这称为保存:
public function save(array $options = array()) {
$query = $this->newQueryWithoutScopes();
.......
}
并调用newQueryWithoutScope:
public function newQueryWithoutScopes() {
return $this->removeGlobalScopes($this->newQuery());
}
当然是removeGlobalScopes:
public function removeGlobalScopes($builder) {
foreach ($this->getGlobalScopes() as $scope) {
$scope->remove($builder, $this);
}
return $builder;
}
$scope->remove()
是我们在remove
课程中设置的StatusColumnScope
方法。这被称为反转范围,但在我们的情况下,我只是完全覆盖查询,将其设置回原始查询。