基于 Eloquent 模型创建后代 class
Create descendant class based on Eloquent model
假设我有 Vehicle
模型(它是 Eloquent 模型)存储不同类型的车辆(在 vehicles
table 中)。当然,有很多不同类型的车辆,所以我举个例子:
class Car extends Vehicle {
}
class Bicycle extends Vehicle {
}
等等。
现在我需要根据车辆查找对象,这就是问题所在。我在 Vehicle
模型中添加了以下方法:
public function getClass()
{
return __NAMESPACE__ . '\' . ucfirst($this->type)
}
所以我可以找到我应该使用的 class 名称。
但是获得有效 class 的唯一方法是这样的:
$vehicle = Vehicle::findOrFail($vehicleId);
$vehicle = ($vehicle->getClass())::find($vehicleId);
这不是最佳解决方案,因为我需要 运行 2 个完全相同的查询才能获得有效的最终 class 对象。
有没有什么方法可以在不重复查询的情况下实现同样的效果?
为了 Eloquent 正确 return 由 type 列确定的 class 对象,您需要覆盖Vehicle 模型中的 2 种方法 class:
public function newInstance($attributes = array(), $exists = false)
{
if (!isset($attributes['type'])) {
return parent::newInstance($attributes, $exists);
}
$class = __NAMESPACE__ . '\' . ucfirst($attributes['type']);
$model = new $class((array)$attributes);
$model->exists = $exists;
return $model;
}
public function newFromBuilder($attributes = array(), $connection = null)
{
if (!isset($attributes->type)) {
return parent::newFromBuilder($attributes, $connection);
}
$instance = $this->newInstance(array_only((array)$attributes, ['type']), true);
$instance->setRawAttributes((array)$attributes, true);
return $instance;
}
@jedrzej.kurylo 方法的替代方法是只覆盖 Vehicle
class:
中的一个方法
public static function hydrate(array $items, $connection = null)
{
$models = parent::hydrate($items, $connection);
return $models->map(function ($model) {
$class = $model->getClass();
$new = (new $class())->setRawAttributes($model->getOriginal(), true);
$new->exists = true;
return $new;
});
}
希望对您有所帮助!
对于浏览此页面的任何其他人,这对我来说是有用的。我从源代码中复制了 newInstance
和 newFromBuilder
,并将它们放在我的父级 class 中,在这种情况下它将是 Vehicle
.
我认为 newInstance
方法在构建查询构建器实例时是 运行 两次。在 newInstance
方法中,我将检查属性中是否设置了 type
,如果是,则根据类型获取命名空间(我使用 PHP 枚举)。在第二遍 $attributes 被转换为一个对象而不是数组,不知道为什么但是不用担心你的 IDE 抱怨。
在 newFromBuilder
方法中,我必须将 $attributes
传递给 newInstance
方法,因为它只是传递一个空数组。
$model = $this->newInstance([], true);
至:
$model = $this->newInstance($attributes, true);
Vehicle.php
/**
* Create a new instance of the given model.
*
* @param array $attributes
* @param bool $exists
* @return static
*/
public function newInstance($attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
// hydration of new objects via the Eloquent query builder instances.
$model = new static;
if (isset($attributes->type)) {
$class = // Logic for getting namespace
$model = new $class;
}
$model->exists = $exists;
$model->setConnection(
$this->getConnectionName()
);
$model->setTable($this->getTable());
$model->mergeCasts($this->casts);
$model->fill((array) $attributes);
return $model;
}
/**
* Create a new model instance that is existing.
*
* @param array $attributes
* @param string|null $connection
* @return static
*/
public function newFromBuilder($attributes = [], $connection = null)
{
// I had to pass $attributes in to newInstance
$model = $this->newInstance($attributes, true);
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
通过进行这些更改,我可以 Vehicle::all()
并获得包含 Car
和 Bicycle
classes.
的集合
假设我有 Vehicle
模型(它是 Eloquent 模型)存储不同类型的车辆(在 vehicles
table 中)。当然,有很多不同类型的车辆,所以我举个例子:
class Car extends Vehicle {
}
class Bicycle extends Vehicle {
}
等等。
现在我需要根据车辆查找对象,这就是问题所在。我在 Vehicle
模型中添加了以下方法:
public function getClass()
{
return __NAMESPACE__ . '\' . ucfirst($this->type)
}
所以我可以找到我应该使用的 class 名称。
但是获得有效 class 的唯一方法是这样的:
$vehicle = Vehicle::findOrFail($vehicleId);
$vehicle = ($vehicle->getClass())::find($vehicleId);
这不是最佳解决方案,因为我需要 运行 2 个完全相同的查询才能获得有效的最终 class 对象。
有没有什么方法可以在不重复查询的情况下实现同样的效果?
为了 Eloquent 正确 return 由 type 列确定的 class 对象,您需要覆盖Vehicle 模型中的 2 种方法 class:
public function newInstance($attributes = array(), $exists = false)
{
if (!isset($attributes['type'])) {
return parent::newInstance($attributes, $exists);
}
$class = __NAMESPACE__ . '\' . ucfirst($attributes['type']);
$model = new $class((array)$attributes);
$model->exists = $exists;
return $model;
}
public function newFromBuilder($attributes = array(), $connection = null)
{
if (!isset($attributes->type)) {
return parent::newFromBuilder($attributes, $connection);
}
$instance = $this->newInstance(array_only((array)$attributes, ['type']), true);
$instance->setRawAttributes((array)$attributes, true);
return $instance;
}
@jedrzej.kurylo 方法的替代方法是只覆盖 Vehicle
class:
public static function hydrate(array $items, $connection = null)
{
$models = parent::hydrate($items, $connection);
return $models->map(function ($model) {
$class = $model->getClass();
$new = (new $class())->setRawAttributes($model->getOriginal(), true);
$new->exists = true;
return $new;
});
}
希望对您有所帮助!
对于浏览此页面的任何其他人,这对我来说是有用的。我从源代码中复制了 newInstance
和 newFromBuilder
,并将它们放在我的父级 class 中,在这种情况下它将是 Vehicle
.
我认为 newInstance
方法在构建查询构建器实例时是 运行 两次。在 newInstance
方法中,我将检查属性中是否设置了 type
,如果是,则根据类型获取命名空间(我使用 PHP 枚举)。在第二遍 $attributes 被转换为一个对象而不是数组,不知道为什么但是不用担心你的 IDE 抱怨。
在 newFromBuilder
方法中,我必须将 $attributes
传递给 newInstance
方法,因为它只是传递一个空数组。
$model = $this->newInstance([], true);
至:
$model = $this->newInstance($attributes, true);
Vehicle.php
/**
* Create a new instance of the given model.
*
* @param array $attributes
* @param bool $exists
* @return static
*/
public function newInstance($attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
// hydration of new objects via the Eloquent query builder instances.
$model = new static;
if (isset($attributes->type)) {
$class = // Logic for getting namespace
$model = new $class;
}
$model->exists = $exists;
$model->setConnection(
$this->getConnectionName()
);
$model->setTable($this->getTable());
$model->mergeCasts($this->casts);
$model->fill((array) $attributes);
return $model;
}
/**
* Create a new model instance that is existing.
*
* @param array $attributes
* @param string|null $connection
* @return static
*/
public function newFromBuilder($attributes = [], $connection = null)
{
// I had to pass $attributes in to newInstance
$model = $this->newInstance($attributes, true);
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
通过进行这些更改,我可以 Vehicle::all()
并获得包含 Car
和 Bicycle
classes.