Отримайте лише конкретні атрибути з колекції Laravel


84

Я переглядав документацію та API для колекцій Laravel, але, здається, не знайшов того, що шукав:

Я хотів би отримати масив із даними моделі з колекції, але отримувати лише вказані атрибути.

Тобто щось на зразок того Users::toArray('id','name','email'), де колекція насправді вміщує всі атрибути для користувачів, оскільки вони використовуються деінде, але в цьому конкретному місці мені потрібен масив з даними користувача та лише зазначені атрибути.

Здається, у Ларавелі немає для цього помічника? - Як я можу це зробити найпростішим способом?


Іншим глядачам може бути цікаво 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 */ ]);
хоп-хоп

це чудово, але мені цікаво, як це може працювати, якщо у мене всередині є вкладений масив
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 змінної, ви не повинні отримувати ще один запит до бази даних.
Давор Мінчоров

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. Реалізуйте вищезазначене за допомогою спеціальної колекції 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()метод доступним.


Ознайомтеся з документацією Laravel щодо розширення колекцій: laravel.com/docs/5.5/collections#extending-collections
Андреас Хакер,

0
  1. Вам потрібно визначити $hiddenта $visibleатрибути. Вони будуть встановлені глобально (це означає, що завжди повертаються всі атрибути з $visibleмасиву).

  2. Використовуючи метод makeVisible($attribute)і makeHidden($attribute)ви можете динамічно змінювати приховані і видимі атрибути. Докладніше: Красномовне: серіалізація -> Тимчасове змінення видимості властивостей


Я не хочу, щоб це було глобально, але динамічно вибираю атрибути для вилучення з колекції.
preyz

0

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

pluck() може бути використаний для цієї мети (якщо потрібен лише 1 ключовий елемент)

Ви також можете використовувати reduce(). Щось на зразок цього зі зменшенням:

$result = $items->reduce(function($carry, $item) {
    return $carry->push($item->getCode());
}, collect());

0

це продовження дії patricus [відповідь] [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();


-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']);
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.