Наследование модели в Laravel
В настоящее время я работаю с Laravel, и я борюсь с тем, что каждая модель должна простираться от Eloquent, и я не уверен, как реализовать иерархию модели (с разными таблицами).
Пример:
Скажем, у меня есть абстрактная модель Tool
, а затем модель Hammer
и модель Screwdriver
, которые простираются от инструмента.
Теперь инструмент будет расширять Eloquent... НО, нет таблицы для инструментов, есть таблица для Hammers и другая таблица для отверток, потому что они имеют разные атрибуты.
Как указать, что у Hammer есть таблица, и Screwdriver имеет таблицу, когда они оба расширяют Tool? И как я могу использовать Eloquent для вызова, например, для всех инструментов?
Вроде:
Tools::all()
Это должно привести все молотки и отвертки, потому что они все Инструменты
Возможно ли использование Eloquent?
Ответы
Ответ 1
Несмотря на то, что мне кажется, что подход "Полиморфные отношения" (PR) немного лучше, чем "Единственное наследование таблицы" (STI), он по-прежнему не чувствует ничего похожего на подход, основанный на истинном наследовании, Из точки зрения домена (например, диаграммы класса UML) это похоже на попытку использования отношения композиции вместо наследования.
С точки зрения согласованности базы данных полиморфное отношение само по себе уже является очень странным решением в Laravel (IMHO). Поскольку не может быть ни одного поля, являющегося внешним ключом для нескольких таблиц, это может привести к объединению идентификатора, который не должен быть соединен. И обратные отношения, которые не соблюдаются.
Хотя я на самом деле не описываю решение проблемы, я бы предложил другой подход, чем PR и STI. Это решение будет похоже на подход Hibernate table-per-subclass. Я думаю, что Dauce extension to Eloquent идет в том же направлении, за исключением, кажется, еще нескольких проблем с реализацией.
С точки зрения базы данных таблица для каждого подкласса также означает, что суперкласс содержит один столбец для каждого прямого подкласса. Затем вы можете установить ограничения внешнего ключа на (не нулевой) id и правильно использовать отношения. Однако в классе Laravel magic Eloquent все еще должна быть какая-то дополнительная логика, которая превращает запрошенный объект в правильный тип.
Итак, для меня функционально модели Eloquent должны правильно наследоваться на стороне PHP, в то время как база данных все еще может использовать ограничения внешнего ключа.
Ответ 2
Примечание. Вы не можете использовать абстрактный класс напрямую, он должен быть расширен дочерним классом
Если ваша модель Tool
(abstract
) не имеет сопоставленного с ней table
, вам не нужно использовать Tool::all
, и вы не можете напрямую использовать/создавать экземпляр модели abstract
но вы можете использовать эту модель abstract
в качестве базового класса следующим образом:
abstract class Tool extends Eloquent {
protected $validator = null;
protected $errors = null;
protected $rules = array();
// Declare common methods here that
// will be used by both child models, for example:
public static function boot()
{
parent::boot();
static::saving(function($model)
{
if(!$this->isvalid($model->toArray(), $this->rules) return false;
});
}
protected function isvalid($inputs, $rules)
{
// return true/false
$this->validator = Validator::make($inputs, $rules);
if($this->validator->passes()) return true;
else {
$this->errors = $this->validator->errors();
return false;
}
}
public function getErrors()
{
return $this->errors;
}
public function hasErrors()
{
return count($this->errors);
}
// declare any abstract method (method header only)
// that every child model needs to implement individually
}
class Hammer extends Tool {
protected $table = 'hammers';
protected $fillable = array(...);
protected $rules = array(...); // Declare own rules
}
class Screwdriver extends Tool {
protected $table = 'screwdrivers';
protected $fillable = array(...);
protected $rules = array(...); // Declare own rules
}
Используйте Hammer
и Screwdriver
напрямую, но никогда не Tool
model/class, потому что это класс abstract
, например:
$hammers = Hammer:all();
Или может быть что-то вроде этого:
$screwdriver = Screwdriver:create(Input::all());
if($screwdrivers->hasErrors()) {
return Redirect::back()->withInput()->withErrors($screwdriver->getErrors());
}
return Redirect::route('Screwdriver.index');
Ответ 3
Я нашел ошибку (функция?), где Laravel 5 просматривает неправильный класс, если ParentClass и ChildClass имеют одну и ту же строку $table. Поэтому, если вы вызываете ParentClass:: all() в определенных ситуациях, он может возвращать экземпляры ChildClass в коллекции!!
Альфа-ответ привел меня к обходному пути, когда вы создаете следующую структуру классов:
abstract class BaseClass extends BaseModel
{
// ParentClass member variables and functions go here, to be shared between parent and child classes
}
class ParentClass extends BaseClass
{
protected $table = 'your_table';
// place no other member variables or functions in this class
}
class ChildClass extends BaseClass
{
protected $table = 'your_table';
// ChildClass member variables and functions go here
}
Это заставляет Laravel делать правильный поиск, когда вы вызываете ParentClass:: all() или ChildClass:: all(), поскольку их общий предок не имеет объявленной таблицы. Просто рассматривайте BaseObject как ParentObject и таким образом вам не нужно мутить концепцию наследования с данными базы данных, которые не должны быть релевантными. Я до сих пор не испытывал стресс-тестирование, поэтому не забудьте указать причину существования этого класса в вашем коде для будущих патронов для отладки.
Ответ 4
Единственная ORM в PHP, которую я знаю, которая делает одну таблицу для иерархии классов, - это Doctrine. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html. И я считаю, что он может отобразить такую иерархию, которую вы планируете использовать, абстрактный суперкласс, который может получить доступ ко всем подклассам.
Чтобы интегрировать Doctrine в Laravel, я предлагаю использовать пакет laravel-doctrine, который вам нужен всю информацию на этом веб-сайте http://www.laraveldoctrine.org/.