预加载相同的关系,不同的约束
Eager-load Same Relationship, Different Constraints
在 Eloquent 中,我试图以不同的约束对同一关系进行两次延迟加载。这里的目标是了解有关员工时间表的两件事。一个是他们去年工作的所有时间。另一个是他们工作的第一次约会。
第一个关系约束是这样的:
$employeeTimeSheets = app(Timesheet::class)
->with([
'punches' => function (Relation $query) {
$query->where('punch_date', '>=', Carbon::now()->subYear());
}
])
第二个是:
$employeeTimeSheets = app(Timesheet::class)
->with([
'punches' => function (Relation $query) {
$query
->whereNotNull('punch_date')
->orderBy('punch_date')
->limit(1);
}
])
问题当然是'punches'只允许一次。并且将有几千名员工被拉到这里,所以为了性能能够预先加载这些数据对我来说很重要。
这些是一种任意条件,只在整个系统中出现一次。所以我不确定是否有必要向 Timesheet 模型添加一个全新的关系方法。而且我也不想对每个员工施加 所有 的压力,只是为了提取最后一年和事后的最小值,因为那将是一座数据山。
如果不覆盖 Eloquent 处理关系的方式以在名称中添加对 as
关键字的支持,我不确定这样做是否可行。但我真正想要的是这样的感觉:
$employeeTimeSheets = app(Timesheet::class)
->with([
'punches as last_year' => function (Relation $query) {
$query->where('punch_date', '>=', Carbon::now()->subYear());
},
'punches as first_punch' => function (Relation $query) {
$query
->whereNotNull('punch_date')
->orderBy('punch_date')
->limit(1);
}
])
有没有人有更好的方法?
想通了。覆盖模型 class 中的一些方法以解析 as
关键字就可以了。我将所有这些都塞进了一个特征中,但它可以很容易地移动到由所有模型扩展的基础 class,并且它本身扩展模型:
/**
* @uses \Illuminate\Database\Eloquent\Model
* @uses \Illuminate\Database\Eloquent\Concerns\HasAttributes
* @uses \Illuminate\Database\Eloquent\Concerns\HasRelationships
*
* Trait RelationAlias
*/
trait RelationAlias
{
protected $validOperators = [
'as'
];
/**
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if ($key = $this->parseKey($method)) {
$method = $key['concrete'];
if (method_exists($this, $method)) {
return $this->$method(...$parameters);
}
}
return parent::__call($method, $parameters);
}
/**
* @return array
*/
protected function getArrayableRelations()
{
$arrayableRelations = parent::getArrayableRelations();
foreach ($arrayableRelations as $key => $value) {
if ($aliased = $this->parseKey($key)) {
$arrayableRelations[$aliased['alias']] = $value;
unset($arrayableRelations[$key]);
}
}
return $arrayableRelations;
}
/**
* @param $key
* @return mixed
*/
public function getRelationValue($key)
{
if ($found = parent::getRelationValue($key)) {
return $found;
}
$relations = array_keys($this->relations);
foreach ($relations as $relation) {
$aliased = $this->parseKey($relation);
if ($aliased && $aliased['alias'] == $key) {
if ($this->relationLoaded($relation)) {
return $this->relations[$relation];
}
if (method_exists($this, $aliased['concrete'])) {
return $this->getRelationshipFromMethod($key);
}
}
}
}
/**
* @param $key
* @return array|null
*/
protected function parseKey($key)
{
$concrete = $operator = $alias = null;
foreach ($this->validOperators as $operator) {
if (preg_match("/.+ $operator .+/i", $key)) {
list($concrete, $operator, $alias) = explode(' ', $key);
break;
}
}
return $alias ? compact('concrete', 'operator', 'alias') : null;
}
}
在 Eloquent 中,我试图以不同的约束对同一关系进行两次延迟加载。这里的目标是了解有关员工时间表的两件事。一个是他们去年工作的所有时间。另一个是他们工作的第一次约会。
第一个关系约束是这样的:
$employeeTimeSheets = app(Timesheet::class)
->with([
'punches' => function (Relation $query) {
$query->where('punch_date', '>=', Carbon::now()->subYear());
}
])
第二个是:
$employeeTimeSheets = app(Timesheet::class)
->with([
'punches' => function (Relation $query) {
$query
->whereNotNull('punch_date')
->orderBy('punch_date')
->limit(1);
}
])
问题当然是'punches'只允许一次。并且将有几千名员工被拉到这里,所以为了性能能够预先加载这些数据对我来说很重要。
这些是一种任意条件,只在整个系统中出现一次。所以我不确定是否有必要向 Timesheet 模型添加一个全新的关系方法。而且我也不想对每个员工施加 所有 的压力,只是为了提取最后一年和事后的最小值,因为那将是一座数据山。
如果不覆盖 Eloquent 处理关系的方式以在名称中添加对 as
关键字的支持,我不确定这样做是否可行。但我真正想要的是这样的感觉:
$employeeTimeSheets = app(Timesheet::class)
->with([
'punches as last_year' => function (Relation $query) {
$query->where('punch_date', '>=', Carbon::now()->subYear());
},
'punches as first_punch' => function (Relation $query) {
$query
->whereNotNull('punch_date')
->orderBy('punch_date')
->limit(1);
}
])
有没有人有更好的方法?
想通了。覆盖模型 class 中的一些方法以解析 as
关键字就可以了。我将所有这些都塞进了一个特征中,但它可以很容易地移动到由所有模型扩展的基础 class,并且它本身扩展模型:
/**
* @uses \Illuminate\Database\Eloquent\Model
* @uses \Illuminate\Database\Eloquent\Concerns\HasAttributes
* @uses \Illuminate\Database\Eloquent\Concerns\HasRelationships
*
* Trait RelationAlias
*/
trait RelationAlias
{
protected $validOperators = [
'as'
];
/**
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if ($key = $this->parseKey($method)) {
$method = $key['concrete'];
if (method_exists($this, $method)) {
return $this->$method(...$parameters);
}
}
return parent::__call($method, $parameters);
}
/**
* @return array
*/
protected function getArrayableRelations()
{
$arrayableRelations = parent::getArrayableRelations();
foreach ($arrayableRelations as $key => $value) {
if ($aliased = $this->parseKey($key)) {
$arrayableRelations[$aliased['alias']] = $value;
unset($arrayableRelations[$key]);
}
}
return $arrayableRelations;
}
/**
* @param $key
* @return mixed
*/
public function getRelationValue($key)
{
if ($found = parent::getRelationValue($key)) {
return $found;
}
$relations = array_keys($this->relations);
foreach ($relations as $relation) {
$aliased = $this->parseKey($relation);
if ($aliased && $aliased['alias'] == $key) {
if ($this->relationLoaded($relation)) {
return $this->relations[$relation];
}
if (method_exists($this, $aliased['concrete'])) {
return $this->getRelationshipFromMethod($key);
}
}
}
}
/**
* @param $key
* @return array|null
*/
protected function parseKey($key)
{
$concrete = $operator = $alias = null;
foreach ($this->validOperators as $operator) {
if (preg_match("/.+ $operator .+/i", $key)) {
list($concrete, $operator, $alias) = explode(' ', $key);
break;
}
}
return $alias ? compact('concrete', 'operator', 'alias') : null;
}
}