Uzyskaj tylko określone atrybuty z kolekcji Laravel

84

Przeglądałem dokumentację i API dla kolekcji Laravel, ale nie wydaje mi się, aby znaleźć to, czego szukam:

Chciałbym pobrać tablicę z danymi modelu z kolekcji, ale uzyskać tylko określone atrybuty.

To znaczy coś takiego Users::toArray('id','name','email'), gdzie kolekcja faktycznie zawiera wszystkie atrybuty dla użytkowników, ponieważ są one używane gdzie indziej, ale w tym konkretnym miejscu potrzebuję tablicy z danymi użytkownika i tylko określonymi atrybutami.

Wygląda na to, że w Laravel nie ma pomocnika? - Jak mogę to zrobić najłatwiej?

preyz
źródło
Inni widzowie mogą być zainteresowani Collection::implode(). Może pobrać właściwość i wyodrębnić ją ze wszystkich obiektów w kolekcji. To nie odpowiada dokładnie na to pytanie, ale może być przydatne dla innych, którzy przyszli tu z Google, tak jak ja. laravel.com/docs/5.7/collections#method-implode
musicin3d

Odpowiedzi:

181

Możesz to zrobić, korzystając z kombinacji istniejących Collectionmetod. Na początku może to być trochę trudne, ale powinno być wystarczająco łatwe do zerwania.

// 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();
});

Wyjaśnienie

Po pierwsze, map()metoda w zasadzie po prostu wykonuje iterację przez polecenie Collectioni przekazuje każdy element w Collectionprzekazanym wywołaniu zwrotnym. Wartość zwracana z każdego wywołania wywołania zwrotnego buduje nowy Collectionwygenerowany przez map()metodę.

collect($user->toArray())jest po prostu budowaniem nowego, tymczasowego Collectionz Usersatrybutów.

->only(['id', 'name', 'email'])redukuje tymczasowe Collectiontylko do określonych atrybutów.

->all()zamienia tymczasową z Collectionpowrotem w zwykłą tablicę.

Połącz wszystko razem, a otrzymasz „Dla każdego użytkownika w kolekcji użytkowników, zwróć tablicę zawierającą tylko identyfikator, nazwę i atrybuty adresu e-mail”.


Aktualizacja Laravel 5.5

Laravel 5.5 dodał onlymetodę do Modelu, która zasadniczo robi to samo co the collect($user->toArray())->only([...])->all(), więc można to nieco uprościć w 5.5+ do:

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

Jeśli połączysz to z „przesyłaniem komunikatów wyższego rzędu” dla kolekcji wprowadzonym w Laravel 5.4, można to jeszcze bardziej uprościć:

// 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']);
patricus
źródło
Dzięki! Oznaczyłem to jako zaakceptowaną odpowiedź, ponieważ działa jak urok z istniejącą funkcjonalnością Laravel. - Teraz można to udostępnić kolekcji za pośrednictwem niestandardowej klasy kolekcji, tak jak opisałem w moim alternatywnym rozwiązaniu.
preyz
Otóż ​​to. Żałuję jednak, że nie ma mniej szczegółowego sposobu naśladowania ::get([ 'fields', 'array' ])zachowania QueryBuildera . Zasadniczo byłoby to przydatne w przypadku „zrzutów”.
pilat
dla tablic, które możesz zrobić$newArray = Arr::only($initialArray, [ 'id', 'name' /* allowed keys list */ ]);
Hop hop
to jest świetne, ale zastanawiam się, jak to może działać, jeśli mam wewnątrz zagnieżdżoną tablicę
abbood
1
W ostatnich wersjach Laravel można to napisać jeszcze bardziej zwięźle:$users->map->only(['column', ...])
okdewit
17

use User::get(['id', 'name', 'email']), zwróci Ci kolekcję z określonymi kolumnami, a jeśli chcesz uczynić ją tablicą, po prostu użyj toArray()po get()metodzie, jak poniżej:

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

W większości przypadków nie musisz konwertować kolekcji na tablicę, ponieważ kolekcje są w rzeczywistości tablicami na sterydach i masz łatwe w użyciu metody manipulowania kolekcją.

Davor Minchorov
źródło
1
Kolekcja została już utworzona przez poprzednie zapytanie, gdzie potrzeba więcej atrybutów. Dlatego muszę wygenerować tablicę danych, w której mam tylko określone atrybuty.
preyz
Możesz albo uczynić zapytanie dynamicznym, przekazując tablicę kolumn do zwrócenia, albo możesz zrobić inną, ->get(['id', 'email', 'name'])która nie będzie ponownie sprawdzać bazy danych, ale zwróci nową kolekcję zawierającą tylko wybrane wartości. Metody takie jak pluck / lists, get, first itp. Mają takie same nazwy w obu klasach: w klasie konstruktora zapytań iw klasie kolekcji.
Davor Minchorov
Ruffles, który nie działa w Laravel 5.3. - Metoda "get" konstruktora zapytań w rzeczywistości pobiera kolumny, ale wykonuje również inne zapytanie. Metoda „get” kolekcji może wyodrębnić określony model za pomocą klucza z kolekcji, co nie jest tym, o co mi tutaj chodzi.
preyz
get () zawsze zwraca kolekcję, nie ma znaczenia, czy jest to DB :: table () -> get () czy Model :: get (), więc jeśli zapiszesz zapytanie w zmiennej i uruchomisz na niej metodę get $ query zmienna, nie powinieneś otrzymywać kolejnego zapytania do bazy danych.
Davor Minchorov
1
Kiedy przechowuję zapytanie w zmiennej, takiej jak $ query = Model :: where (...), to uruchamia zapytanie do bazy danych za każdym razem, gdy wykonuję zapytanie $ query-> get (). - Również twoja sugestia imho nie jest pożądanym rozwiązaniem problemu. Rozwiązałem to za pomocą nowych funkcji tablicowych, które zaimplementowałem za pomocą niestandardowej kolekcji (patrz moja własna odpowiedź).
preyz
3

Poniższa metoda również działa.

$users = User::all()->map(function ($user) {
  return collect($user)->only(['id', 'name', 'email']);
});
hawx
źródło
1

Mam teraz własne rozwiązanie tego problemu:

1. Utworzono ogólną funkcję wyodrębniania określonych atrybutów z tablic

Poniższa funkcja wyodrębnia tylko określone atrybuty z tablicy asocjacyjnej lub tablicy tablic asocjacyjnych (ostatnia jest tym, co otrzymujesz wykonując $ collection-> toArray () w Laravel).

Można go używać w następujący sposób:

$data = array_extract( $collection->toArray(), ['id','url'] );

Używam następujących funkcji:

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;   
}

To rozwiązanie nie koncentruje się na wpływie na wydajność podczas przeglądania kolekcji w dużych zestawach danych.

2. Zaimplementuj powyższe poprzez niestandardową kolekcję w Laravel

Ponieważ chciałbym móc po prostu to zrobić $collection->extract('id','url'); na dowolnym obiekcie kolekcji, zaimplementowałem niestandardową klasę kolekcji.

Najpierw stworzyłem ogólny Model, który rozszerza model Eloquent, ale używa innej klasy kolekcji. Wszystkie modele, których potrzebujesz, aby rozszerzyć ten niestandardowy model, a nie model elokwentny wtedy.

<?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 );
    }    
}
?>

Po drugie stworzyłem następującą klasę kolekcji niestandardowej:

<?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 );
    }
}   
?>

Na koniec wszystkie modele powinny zamiast tego rozszerzyć model niestandardowy, na przykład:

<?php
namespace App\Models;
class Article extends Model
{
...

Teraz funkcje z nr. 1 powyżej są starannie używane przez kolekcję, aby udostępnić $collection->extract()metodę.

preyz
źródło
Zapoznaj się z dokumentacją Laravel dotyczącą rozszerzania kolekcji: laravel.com/docs/5.5/collections#extending-collections
Andreas Hacker
0
  1. Musisz zdefiniować $hiddeni określić $visibleatrybuty. Zostaną ustawione jako globalne (co oznacza, że ​​zawsze zwracają wszystkie atrybuty z $visibletablicy).

  2. Stosując metodę makeVisible($attribute)i makeHidden($attribute)można dynamicznie zmieniać ukrytych i widocznych atrybutów. Więcej: Eloquent: Serialization -> Tymczasowa modyfikacja widoczności właściwości

Grzegorz Gajda
źródło
Nie chcę, aby było to globalne, ale wybieram atrybuty do dynamicznego wyodrębniania z kolekcji.
preyz
0

Miałem podobny problem, gdy musiałem wybrać wartości z dużej tablicy, ale chciałem, aby wynikowa kolekcja zawierała tylko wartości jednej wartości.

pluck() może być użyty do tego celu (jeśli wymagany jest tylko 1 kluczowy element)

możesz również użyć reduce(). Coś takiego z redukuj:

$result = $items->reduce(function($carry, $item) {
    return $carry->push($item->getCode());
}, collect());
przewodowy00
źródło
0

to jest kontynuacja patricus [odpowiedź] [1] powyżej, ale dla zagnieżdżonych tablic:

$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
źródło
-1

wydaje się, że działa, ale nie jestem pewien, czy jest zoptymalizowany pod kątem wydajności, czy nie.

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

wynik:

array:2 [
  0 => 4
  1 => 1
]
rozpoznać
źródło
-3
$users = Users::get();

$subset = $users->map(function ($user) {
    return array_only(user, ['id', 'name', 'email']);
});
NEM
źródło