Загрузить все отношения для модели

Обычно для загрузки отношений я делал что-то вроде этого:

Model::with('foo', 'bar', 'baz')...

Решение может заключаться в том, чтобы установить $with = ['foo','bar','baz'], однако всегда будет загружать эти три отношения, когда я вызываю Model

Можно ли сделать что-то вроде этого: Model::with('*')?

Ответ 1

Нет, нет, по крайней мере, без дополнительной работы, потому что ваша модель не знает, какие отношения она поддерживает, пока они на самом деле не загружены.

У меня была эта проблема в одном из моих собственных пакетов Laravel. Нет способа получить список отношений модели с Laravel. Это довольно очевидно, хотя, если вы посмотрите на то, как они определены. Простые функции, которые возвращают объект Relation. Вы даже не можете получить возвращаемый тип функции с классами отражения php, поэтому нет никакого способа отличить функцию отношения от любой другой функции.

Чтобы облегчить задачу, вы можете определить функцию, которая добавляет все отношения. Для этого вы можете воспользоваться областями запросов eloquents (спасибо Jarek Tkaczyk за упоминание об этом в комментариях).

public function scopeWithAll($query) 
{
    $query->with('foo', 'bar', 'baz');
}

Использование областей видимости вместо статических функций позволяет вам использовать не только вашу функцию непосредственно в модели, но также, например, при объединении методов построителя запросов, таких как where, в любом порядке:

Model::where('something', 'Lorem ipsum dolor')->withAll()->where('somethingelse', '>', 10)->get();

Альтернативы для получения поддерживаемых отношений

Хотя Laravel не поддерживает что-то подобное из коробки, вы всегда можете добавить его самостоятельно.

Аннотации

Я использовал аннотации, чтобы определить, является ли функция отношением или нет в моем пакете, упомянутом выше. Аннотации официально не являются частью php, но многие люди используют doc-блоки для их моделирования. Laravel 5 также собирается использовать аннотации в своих определениях маршрутов, поэтому я решил, что это не будет плохой практикой в этом случае. Преимущество заключается в том, что вам не нужно вести отдельный список поддерживаемых отношений.

Добавьте аннотацию к каждому из ваших отношений:

/**
 * @Relation
 */
public function foo() 
{
    return $this->belongsTo('Foo');
}

И напишите функцию, которая анализирует блоки документов всех методов в модели и возвращает имя. Вы можете сделать это в модели или в родительском классе:

public static function getSupportedRelations() 
{
    $relations = [];
    $reflextionClass = new ReflectionClass(get_called_class());

    foreach($reflextionClass->getMethods() as $method) 
    {
        $doc = $method->getDocComment();

        if($doc && strpos($doc, '@Relation') !== false) 
        {
            $relations[] = $method->getName();
        }
    }

    return $relations;
}

А затем просто используйте их в своей функции withAll:

public function scopeWithAll($query) 
{
    $query->with($this->getSupportedRelations());
}

Некоторые любят аннотации в php, а некоторые нет. Мне нравится этот простой пример использования.

Массив поддерживаемых отношений

Вы также можете поддерживать массив всех поддерживаемых отношений. Однако для этого необходимо, чтобы вы всегда синхронизировали его с доступными отношениями, что не всегда так просто, особенно если в нем участвует несколько разработчиков.

protected $supportedRelations = ['foo','bar', 'baz'];

А затем просто используйте их в своей функции withAll:

public function scopeWithAll($query) 
{
    return $query->with($this->supportedRelations);
}

Конечно, вы также можете переопределить with, как Лукасгейтер, упомянутый в его ответе. Это кажется чище, чем при использовании withAll. Если вы используете аннотации или массив конфигурации, однако, это вопрос мнения.

Ответ 2

Я бы не использовал методы static, как предлагалось, так как... это Красноречиво;) Просто используйте то, что он уже предлагает - область.

Конечно, он не сделает этого для вас (главный вопрос), однако это определенно путь:

// SomeModel
public function scopeWithAll($query)
{
    $query->with([ ... all relations here ... ]); 
    // or store them in protected variable - whatever you prefer
    // the latter would be the way if you want to have the method
    // in your BaseModel. Then simply define it as [] there and use:
    // $query->with($this->allRelations); 
}

Таким образом, вы можете использовать это как хотите:

// static-like
SomeModel::withAll()->get();

// dynamically on the eloquent Builder
SomeModel::query()->withAll()->get();
SomeModel::where('something', 'some value')->withAll()->get();

Кроме того, на самом деле вы можете разрешить Eloquent делать это за вас, как это делает Doctrine - используя doctrine/annotations и DocBlocks. Вы можете сделать что-то вроде этого:

// SomeModel

/**
 * @Eloquent\Relation
 */
public function someRelation()
{
  return $this->hasMany(..);
}

Это слишком длинная история, чтобы включить его здесь, поэтому узнайте, как это работает: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html

Ответ 3

Так как я столкнулся с аналогичной проблемой и нашел хорошее решение, которое здесь не описано, и не требует заполнения некоторых настраиваемых массивов или что-то еще, я отправлю его на будущее.

Что я делаю, сначала создайте trait, называемый RelationsManager:

trait RelationsManager
{
    protected static $relationsList = [];

    protected static $relationsInitialized = false;

    protected static $relationClasses = [
        HasOne::class,
        HasMany::class,
        BelongsTo::class,
        BelongsToMany::class
    ];

    public static function getAllRelations($type = null) : array
    {
        if (!self::$relationsInitialized) {
            self::initAllRelations();
        }

        return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
    }

    protected static function initAllRelations()
    {
        self::$relationsInitialized = true;

        $reflect = new ReflectionClass(static::class);

        foreach($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            /** @var ReflectionMethod $method */
            if ($method->hasReturnType() && in_array((string)$method->getReturnType(), self::$relationClasses)) {
                self::$relationsList[(string)$method->getReturnType()][] = $method->getName();
            }
        }
    }

    public static function withAll() : Builder
    {
        $relations = array_flatten(static::getAllRelations());

        return $relations ? self::with($relations) : self::query();
    }
}

Теперь вы можете использовать его с любым классом, например -

class Project extends Model
{
    use RelationsManager;

    //... some relations

}

а затем, когда вам нужно извлечь их из базы данных:

$projects = Project::withAll()->get();

Некоторые примечания. В моем списке классов отношений примеров не содержится морфовых отношений, поэтому, если вы хотите их получить, вам нужно добавить их в переменную $relationClasses. Кроме того, это решение работает только с PHP 7.

Ответ 4

Невозможно узнать, каковы все отношения, не указав их сами. Как другие ответы размещены хорошо, но я хотел добавить несколько вещей.

Базовая модель

Мне кажется, что вы хотите сделать это в нескольких моделях, поэтому сначала я создал бы BaseModel, если вы еще этого не сделали.

class BaseModel extends Eloquent {
    public $allRelations = array();
}

"Config" массив

Вместо жесткого кодирования отношений в метод я предлагаю вам использовать переменную-член. Как вы можете видеть выше, я уже добавил $allRelations. Имейте в виду, что вы не можете назвать его $relations, поскольку Laravel уже использует это внутри.

Переопределить with()

Поскольку вы хотели with(*), вы тоже можете это сделать. Добавьте это в BaseModel

public static function with($relations){
    $instance = new static;
    if($relations == '*'){
        $relations = $instance->allRelations;
    }
    else if(is_string($relations)){
        $relations = func_get_args();
    }
    return $instance->newQuery()->with($relations);
}

(Кстати, некоторые части этой функции исходят из исходного класса модели)

Использование

class MyModel extends BaseModel {
    public $allRelations = array('foo', 'bar');
}

MyModel::with('*')->get();

Ответ 5

Вы можете попытаться определить методы, специфичные для вашей модели, используя отражение, например:

$base_methods = get_class_methods('Illuminate\Database\Eloquent\Model');
$model_methods = get_class_methods(get_class($entry));
$maybe_relations = array_diff($model_methods, $base_methods);

dd($maybe_relations);

Затем попытайтесь загрузить каждого в хорошо контролируемый try/catch. Модель класса Laravel имеет load и loadMissing методы для быстрой загрузки.

Смотрите ссылку на API.

Ответ 6

Вы можете создать метод в своей модели

public static function withAllRelations() {
   return static::with('foo', 'bar', 'baz');
}

И вызовите Model::withAllRelations()

или

$instance->withAllRelations()->first(); // or ->get()

Ответ 7

У вас не может быть динамическая загрузка отношений для определенной модели. вам нужно указать модель, отношения которой поддерживаются.