Получите только определенные атрибуты из коллекции Laravel

84

Я просматривал документацию и API для Laravel Collections, но, похоже, не нашел то, что ищу:

Я хотел бы получить массив с данными модели из коллекции, но получить только указанные атрибуты.

То есть что-то вроде того Users::toArray('id','name','email'), где коллекция фактически содержит все атрибуты для пользователей, потому что они используются в другом месте, но в этом конкретном месте мне нужен массив с пользовательскими данными и только указанные атрибуты.

Кажется, в Laravel нет помощника для этого? - Как мне это сделать проще всего?

Preyz
источник
Другие зрители могут быть заинтересованы Collection::implode(). Он может взять свойство и извлечь его из всех объектов в коллекции. Это не дает точного ответа на этот вопрос, но может быть полезно другим, пришедшим сюда из Google, как это сделал я. laravel.com/docs/5.7/collections#method-implode
musicin3d

Ответы:

181

Вы можете сделать это, используя комбинацию существующих 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обратно в простой массив.

Сложите все это вместе, и вы получите «Для каждого пользователя в коллекции пользователей вернуть массив, состоящий только из атрибутов id, name и email».


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

Если вы объедините это с «обменом сообщениями более высокого порядка» для коллекций, представленных в Laravel 5.4, его можно упростить еще больше:

// 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->only(['id', 'name', 'email']);
Патрик
источник
Благодаря! Я отметил это как принятый ответ, потому что он прекрасно работает с существующими функциями Laravel. - Теперь это можно сделать доступным для коллекции через специальный класс коллекции, как я описал в своем альтернативном решении.
preyz
Вот и все. Но я бы хотел, чтобы был менее подробный способ подражать ::get([ 'fields', 'array' ])поведению QueryBuilder . В принципе, для «свалок» пригодится.
пилат
для массивов вы можете сделать$newArray = Arr::only($initialArray, [ 'id', 'name' /* allowed keys list */ ]);
Hop hop
это здорово, но мне интересно, как это может работать, если у меня внутри есть вложенный массив
abbood
1
В последних версиях Laravel это можно было бы написать еще более кратко:$users->map->only(['column', ...])
okdewit
17

use User::get(['id', 'name', 'email']), он вернет вам коллекцию с указанными столбцами, и если вы хотите сделать ее массивом, просто используйте toArray()после get()метода, например:

User::get(['id', 'name', 'email'])->toArray()

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

Давор Минчоров
источник
1
Коллекция уже создана предыдущим запросом, где требуются дополнительные атрибуты. Поэтому мне нужно создать массив данных, в котором у меня есть только определенные атрибуты.
preyz
Вы можете сделать свой запрос динамическим, передав массив столбцов для возврата, или сделать другой, ->get(['id', 'email', 'name'])который не будет запрашивать базу данных, но вернет новую коллекцию только с выбранными значениями. Такие методы, как pluck / lists, get, first и т. Д., Имеют одинаковые имена как в классе построителя запросов, так и в классе коллекции.
Давор Минчоров
Оборки, которые не работают в Laravel 5.3. - Метод "get" построителя запросов фактически принимает столбцы, но также выполняет другой запрос. Метод "get" коллекции может извлекать из коллекции конкретную модель по ключу, чего я здесь не хочу.
preyz
get () всегда возвращает коллекцию, не имеет значения, DB :: table () -> get () или Model :: get (), поэтому, если вы сохраните запрос в переменной и запустите метод get для этого $ query variable, вы не должны получать другого запроса к базе данных.
Давор Минчоров
1
Когда я сохраняю запрос в переменной типа $ query = Model :: where (...), он запускает запрос к базе данных всякий раз, когда я выполняю $ query-> get (). - Кроме того, ваше предложение imho не является желаемым решением проблемы. Я решил это с помощью некоторых новых функций массива, которые я реализовал через настраиваемую коллекцию (см. Мой собственный ответ).
preyz
3

Метод ниже также работает.

$users = User::all()->map(function ($user) {
  return collect($user)->only(['id', 'name', 'email']);
});
ястреб
источник
1

Теперь я придумал собственное решение этой проблемы:

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. Реализуйте вышесказанное с помощью специальной коллекции в Laravel.

Поскольку я хотел бы иметь возможность просто работать $collection->extract('id','url');с любым объектом коллекции, я реализовал собственный класс коллекции.

Сначала я создал общую модель, которая расширяет модель Eloquent, но использует другой класс коллекции. Все ваши модели должны расширять эту пользовательскую модель, а не модель 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()метод доступным.

Preyz
источник
Ознакомьтесь с документацией Laravel по расширению коллекций: laravel.com/docs/5.5/collections#exnding-collections
Андреас Хакер
0
  1. Вам нужно определить $hiddenи $visibleатрибуты. Они будут установлены глобально (это означает, что всегда будут возвращаться все атрибуты из $visibleмассива).

  2. Используя метод makeVisible($attribute)и makeHidden($attribute)вы можете динамически изменять скрытые и видимые атрибуты. Подробнее: Eloquent: сериализация -> Временное изменение видимости свойств

Гжегож Гайда
источник
Я не хочу, чтобы это было глобальным, но выбираю атрибуты для динамического извлечения из коллекции.
preyz
0

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

pluck() можно использовать для этой цели (если требуется только 1 ключевой элемент)

вы также можете использовать reduce(). Примерно так с уменьшением:

$result = $items->reduce(function($carry, $item) {
    return $carry->push($item->getCode());
}, collect());
проводной00
источник
0

это продолжение вышеупомянутого patricus [answer] [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();
Abbood
источник
-1

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

$request->user()->get(['id'])->groupBy('id')->keys()->all();

вывод:

array:2 [
  0 => 4
  1 => 1
]
Кен
источник
-3
$users = Users::get();

$subset = $users->map(function ($user) {
    return array_only(user, ['id', 'name', 'email']);
});
NEM
источник