基于Eloquent模型创建后代类


Create descendant class based on Eloquent model

假设我有Vehicle模型(它是Eloquent模型),它存储不同类型的车辆(在vehicles表中)。当然,有许多不同类型的车辆,所以我有例如:

class Car extends Vehicle {
}
class Bicycle extends Vehicle {
}

等等

现在我需要找到基于车辆的对象,这就是问题所在。我在Vehicle模型中增加了如下方法:

public function getClass()
{
    return __NAMESPACE__ . '''' . ucfirst($this->type)
}

这样我可以找到我应该使用的类名。

但是获得有效类的唯一方法是这样的:
$vehicle = Vehicle::findOrFail($vehicleId);
$vehicle = ($vehicle->getClass())::find($vehicleId);

这不是最好的解决方案,因为我需要运行2个完全相同的查询来获得有效的最终类对象。

是否有任何方法来实现相同而不重复查询?

@jedrzej的替代方案。kurylo的方法是只重写Vehicle类中的一个方法:

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;
    });
}

希望这对你有帮助!

为了使Eloquent能够正确返回由类型列确定的类的对象,您需要在Vehicle模型类中重写2个方法:

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;
}

对于看到这个页面的其他人来说,这是对我有效的方法。我从源代码中复制了newInstancenewFromBuilder,并将它们放在我的父类中,在这种情况下,它将是Vehicle

我认为newInstance方法在构建查询构建器实例时运行两次。在newInstance方法中,我将检查是否在属性中设置了type,如果是,则根据类型获得名称空间(我使用PHP enum)。在第二次传递时,$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(),并获得包含CarBicycle类的集合。