Получайте только определенные атрибуты из коллекции Laravel
Я просматривал документацию и API для коллекций Laravel, но, похоже, не нашел того, что ищу:
Я хотел бы получить массив с данными модели из коллекции, но получить только указанные атрибуты.
Т.Е. что-то вроде Users::toArray('id','name','email')
, где коллекция фактически содержит все атрибуты для пользователей, потому что они используются в другом месте, но в этом конкретном месте мне нужен массив с данными пользователя и только указанные атрибуты.
Там есть не похоже, чтобы это было помощником в Ларавеле? - Как я могу сделать это самым простым способом?
7 answers
Вы можете сделать это, используя комбинацию существующих методов Collection
. Поначалу это может быть немного сложно понять, но должно быть достаточно легко сломаться.
// get your main collection with all the attributes...
$users = Users::get();
// build your second collection with a subset of attributes. this new
// collection will be a collection of plain arrays, not Users models.
$subset = $users->map(function ($user) {
return collect($user->toArray())
->only(['id', 'name', 'email'])
->all();
});
Объяснение
Во-первых, метод map()
в основном просто повторяет Collection
и передает каждый элемент в Collection
переданному обратному вызову. Значение, возвращаемое при каждом вызове обратного вызова, создает новый Collection
, сгенерированный методом map()
.
collect($user->toArray())
просто строит новый, временный Collection
из атрибутов Users
.
->only(['id', 'name', 'email'])
уменьшает временное значение Collection
только до указанных атрибутов.
->all()
превращает временный Collection
обратно в простой массив.
Сложите все это вместе, и вы получите "Для каждого пользователя в коллекции пользователей возвращайте массив только атрибутов идентификатора, имени и электронной почты".
Обновление Laravel 5.5
Laravel 5.5 добавил в модель метод only
, который в основном делает то же самое, что и collect($user->toArray())->only([...])->all()
, так что это можно немного упростить в 5.5+ до:
// get your main collection with all the attributes...
$users = Users::get();
// build your second collection with a subset of attributes. this new
// collection will be a collection of plain arrays, not Users models.
$subset = $users->map(function ($user) {
return $user->only(['id', 'name', 'email']);
});
Используйте User::get(['id', 'name', 'email'])
, он вернет вам коллекцию с указанными столбцами, и если вы хотите сделать ее массивом, просто используйте toArray()
после метода get()
примерно так:
User::get(['id', 'name', 'email'])->toArray()
В большинстве случаев вам не нужно будет преобразовывать коллекцию в массив, потому что коллекции на самом деле являются массивами на стероидах, и у вас есть простые в использовании методы для управления коллекцией.
Теперь я придумал собственное решение этой проблемы:
1. Создана общая функция для извлечения определенных атрибутов из массивов
Приведенная ниже функция извлекает только определенные атрибуты из ассоциативного массива или массива ассоциативных массивов (последнее вы получаете, когда выполняете $collection->toArray()в Laravel).
Его можно использовать следующим образом:
$data = array_extract( $collection->toArray(), ['id','url'] );
Я использую следующие функции:
function array_is_assoc( $array )
{
return is_array( $array ) && array_diff_key( $array, array_keys(array_keys($array)) );
}
function array_extract( $array, $attributes )
{
$data = [];
if ( array_is_assoc( $array ) )
{
foreach ( $attributes as $attribute )
{
$data[ $attribute ] = $array[ $attribute ];
}
}
else
{
foreach ( $array as $key => $values )
{
$data[ $key ] = [];
foreach ( $attributes as $attribute )
{
$data[ $key ][ $attribute ] = $values[ $attribute ];
}
}
}
return $data;
}
Это решение не фокусируется о влиянии производительности на циклический просмотр коллекций в больших наборах данных.
2. Реализуйте вышеизложенное с помощью пользовательской коллекции i Laravel
Поскольку я хотел бы иметь возможность просто выполнять $collection->extract('id','url');
для любого объекта коллекции, я реализовал пользовательский класс коллекции.
Сначала я создал общую модель, которая расширяет модель Eloquent, но использует другой класс коллекции. Все ваши модели должны расширять эту пользовательскую модель, а не красноречивую модель тогда.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Lib\Collection;
class Model extends EloquentModel
{
public function newCollection(array $models = [])
{
return new Collection( $models );
}
}
?>
Во-вторых, я создал следующий пользовательский класс коллекции:
<?php
namespace Lib;
use Illuminate\Support\Collection as EloquentCollection;
class Collection extends EloquentCollection
{
public function extract()
{
$attributes = func_get_args();
return array_extract( $this->toArray(), $attributes );
}
}
?>
Наконец, все модели должны затем расширить вашу пользовательскую модель, например:
<?php
namespace App\Models;
class Article extends Model
{
...
Теперь функции из пункта 1 выше аккуратно используются коллекцией, чтобы сделать доступным метод $collection->extract()
.
Вам необходимо определить атрибуты
$hidden
и$visible
. Они будут установлены глобальными (это означает, что всегда возвращайте все атрибуты из массива$visible
).Используя методы
makeVisible($attribute)
иmakeHidden($attribute)
, вы можете динамически изменять скрытые и видимые атрибуты. Подробнее: Красноречиво: Сериализация ->Временное изменение видимости свойств
Похоже, это работает, но не уверен, оптимизировано ли это для производительности или нет.
$request->user()->get(['id'])->groupBy('id')->keys()->all();
Вывод:
array:2 [
0 => 4
1 => 1
]
У меня была аналогичная проблема, когда мне нужно было выбрать значения из большого массива, но я хотел, чтобы результирующая коллекция содержала только значения одного значения.
pluck()
может быть использован для этой цели (если требуется только 1 ключевой элемент)
Вы также можете использовать reduce()
. Что-то вроде этого с уменьшением:
$result = $items->reduce(function($carry, $item) {
return $carry->push($item->getCode());
}, collect());
Это продолжение ответа Патрика [1] выше, но для вложенных массивов:
$topLevelFields = ['id','status'];
$userFields = ['first_name','last_name','email','phone_number','op_city_id'];
return $onlineShoppers->map(function ($user) {
return collect($user)->only($topLevelFields)
->merge(collect($user['user'])->only($userFields))->all();
})->all();