Стремительный груз для коллекций на лету - PullRequest
0 голосов
/ 21 мая 2019

У меня есть это, у пользователя есть свойство "многие ко многим" через pivot property_users.

Я делаю как-то повторно используемые классы в моем веб-приложении.

Это модели сих функции загрузки:

//User model
public function properties()
{
    return $this->belongsToMany(Property::class, 'property_users', 'user_id', 'property_id');
}

//Property model
public function property_users()
{
    return $this->hasMany(PropertyUser::class, 'property_id', 'id');
}

//PropertyUser model
public function user()
{
    return $this->belongsTo(User::class);
}

//GetProperties class
public function handle()
{
    return auth()->user()->properties()->get();
}

//somewhere in a feature
$properties = $this->run(GetProperties::class);
//this returns valid properties under the logged in user

Теперь мне нужно получить chat_username в property_users, принадлежащем этому пользователю

Мне удается заставить его работать, если я перебираю свойства и затем делаю этона лету.

$properties = $properties->map(function($property) {
    $propertyUsers = $property->property_users()->get();

    $chatUsername = null;
    foreach($propertyUsers as $propertyUser) {
        if($propertyUser->property_id == $property->id) {
           $chatUsername = $propertyUser->chat_username;
        }
    }

    return [
        'name' => $property->name,
        'id' => $property->id,
        'chat_username' => $chatUsername
    ];
});

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

Другой способ заключается в том, что яможно добавить property_users в загружаемую загрузку в классе GetProperties, обновив его до:

$query = Property::query();

$query->with(['property_users']);

$query->whereHas('property_users', function($qry) {
    $qry->where('user_id', Auth::user()->id);
});

$properties = $query->get();

return $properties;

Но я не хочу полагаться на добавление более загруженной загрузки в исходный класс GetProperties, поскольку GetProperties получитfat и мне эти данные на самом деле не нужны (скажем, добавление property_invoices, property_schedules и т. д., но в какой-то мере они не нужны).

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

Соберите все идентификаторы из свойств, выполните выборку, используя , где , и примените всех пользователей к свойствам в одном запросе.Таким образом, это будет еще красивее.

Может быть, что-то вроде этого: (используя оригинальный класс GetProperties)

$properties = $this->run(GetProperties::class);
//this does not work. The error is: Method Illuminate\Database\Eloquent\Collection::property_users does not exist.
$property->property_users = $properties->property_users()->get();

Было бы замечательно, если бы кто-то мог показать мне, как это сделать.

Ответы [ 2 ]

0 голосов
/ 21 мая 2019

Хорошо, после прочтения здесь и там.

Я нашел эту статью: https://laravel -news.com / eloquent-eager-loading

Решениедействительно мило.Просто:

$properties = $this->run(GetProperties::class);

//lazy load
$properties->load('property_users');

$properties = $properties->map(function($property) {
    $user = $property->property_users->first(function($user) {
        if($user->user_id == Auth::user()->id) {
            return $user;
        }
    })->only('chat_username');

    return [
        'name' => $property->name,
        'id' => $property->id,
        'chat_username' => $user['chat_username']
    ];
});

После проверки журналов запросов:

//auth query done by middleware
[2019-05-21 07:59:11] local.INFO: select * from `users` where `auth_token` = ? or `temporary_auth_token` = ? limit 1 ["token_removed_for_security_purpose","token_removed_for_security_purpose"] 
//These are the queries made:
[2019-05-21 07:59:11] local.INFO: select `properties`.*, `property_users`.`user_id` as `pivot_user_id`, `property_users`.`property_id` as `pivot_property_id` from `properties` inner join `property_users` on `properties`.`id` = `property_users`.`property_id` where `property_users`.`user_id` = ? [8] 
[2019-05-21 07:59:11] local.INFO: select * from `property_users` where `property_users`.`property_id` in (2, 4)  

Таким образом, я могу сохранить свои GetProperties настолько маленькими, насколько это возможно, а затем просто лениво загружать их везде, где они мне нужны.

0 голосов
/ 21 мая 2019

А как насчет загрузки только тех полей, которые вам действительно нужны?

$query->with('property_users:id,user_id');

Модель не станет толстой, и вам не нужно будет делать отдельные запросы в цикле.

Это задокументировано в официальной документации: https://laravel.com/docs/5.8/eloquent-relationships#eager-loading, см. Стремительная загрузка определенных столбцов

Редактировать: если вы хотите выполнить запрос после класса GetProperties, вам необходимо собрать все идентификаторы и выполнить второй запрос. Мне, честно говоря, не нравится этот второй подход, потому что он гораздо медленнее, менее производительный, и я считаю его менее элегантным, чем добавление одной строки в классе GetProperties, но он будет работать:

$properties = $this->run(GetProperties::class);
$ids = $properties->pluck('id'); // Get all the ids as an array
$propertyUsers = propertyUsers::whereIn('property_id', $ids)->get(); // Get the propertyUsers model
foreach($properties as $property) {
    $property->property_users = $propertyUsers->where('property_id', $property->id); // Not even sure you can do that, proerty_users may not been writable
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...