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


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

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

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

Там есть не похоже, чтобы это было помощником в Ларавеле? - Как я могу сделать это самым простым способом?

 31
Author: preyz, 2016-07-16

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']);
});
 63
Author: patricus, 2018-08-02 17:21:38

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

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

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

 8
Author: Ruffles, 2016-07-16 15:46:27

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

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().

 2
Author: preyz, 2016-07-17 16:08:27
  1. Вам необходимо определить атрибуты $hidden и $visible. Они будут установлены глобальными (это означает, что всегда возвращайте все атрибуты из массива $visible).

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

 1
Author: Grzegorz Gajda, 2016-07-16 14:27:12

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

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

Вывод:

array:2 [
  0 => 4
  1 => 1
]
 0
Author: ken, 2017-09-15 03:20:43

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

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

Вы также можете использовать reduce(). Что-то вроде этого с уменьшением:

$result = $items->reduce(function($carry, $item) {
    return $carry->push($item->getCode());
}, collect());
 0
Author: wired00, 2018-02-28 23:01:55

Это продолжение ответа Патрика [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();
 0
Author: abbood, 2018-07-23 06:39:12