web-dev-qa-db-fra.com

Obtenez uniquement des attributs spécifiques avec Laravel Collection

J'ai examiné la documentation et l'API pour Laravel Collections, mais je ne trouve pas ce que je recherche:

Je voudrais récupérer un tableau avec les données de modèle d'une collection, mais seulement obtenir les attributs spécifiés.

C'est à dire. quelque chose comme Users::toArray('id','name','email'), où la collection contient en fait tous les attributs des utilisateurs, car ils sont utilisés ailleurs, mais à cet endroit spécifique, j'ai besoin d'un tableau avec userdata, et uniquement des attributs spécifiés.

Il ne semble pas y avoir d'aide pour cela à Laravel? - Comment puis-je faire cela le plus facilement possible?

38
preyz

Vous pouvez le faire en utilisant une combinaison de méthodes Collection existantes. Cela peut sembler un peu difficile à suivre au début, mais cela devrait être assez facile à décomposer.

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

Explication

Tout d’abord, la méthode map() parcourt essentiellement la Collection et transmet chaque élément de la Collection au rappel transmis. La valeur renvoyée par chaque appel du rappel génère la nouvelle Collection générée par la méthode map().

collect($user->toArray()) est en train de créer une nouvelle Collection temporaire à partir des attributs Users.

->only(['id', 'name', 'email']) réduit la variable Collection temporaire aux seuls attributs spécifiés.

->all() transforme la variable temporaire Collection dans un tableau simple.

Rassemblez le tout et vous obtenez "Pour chaque utilisateur de la collection users, renvoyez un tableau contenant uniquement les attributs id, name et email."


Laravel 5.5 mise à jour

Laravel 5.5 a ajouté une méthode only sur le modèle, qui fait essentiellement la même chose que collect($user->toArray())->only([...])->all(). Cela peut donc être légèrement simplifié dans 5.5+ pour:

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

Je viens maintenant de proposer une solution à ce problème:

1. Création d'une fonction générale permettant d'extraire des attributs spécifiques de tableaux

La fonction ci-dessous extrait uniquement des attributs spécifiques d'un tableau associatif ou d'un tableau de tableaux associatifs (le dernier correspond à ce que vous obtenez lorsque vous faites $ collection-> toArray () dans Laravel).

Il peut être utilisé comme ceci:

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

J'utilise les fonctions suivantes:

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

Cette solution ne se concentre pas sur les conséquences en termes de performances pour la lecture en boucle des collections dans de grands ensembles de données.

2. Mettez en œuvre ce qui précède via une collection personnalisée i Laravel

Etant donné que j'aimerais pouvoir simplement faire $collection->extract('id','url'); sur n'importe quel objet de collection, j'ai implémenté une classe de collection personnalisée.

J'ai d'abord créé un modèle général, qui étend le modèle Eloquent, mais utilise une classe de collection différente. Tous vos modèles doivent étendre ce modèle personnalisé, et non le modèle Eloquent alors.

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

Deuxièmement, j'ai créé la classe de collection personnalisée suivante:

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

Enfin, tous les modèles devraient alors étendre votre modèle personnalisé, comme ceci:

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

Maintenant les fonctions de no. 1 ci-dessus sont parfaitement utilisés par la collection pour rendre la méthode $collection->extract() disponible.

1
preyz
  1. Vous devez définir les attributs $hidden et $visible. Ils seront définis globalement (cela signifie que tous les attributs du tableau $visible seront toujours renvoyés).

  2. En utilisant les méthodes makeVisible($attribute) et makeHidden($attribute), vous pouvez modifier dynamiquement les attributs masqués et visibles. Plus: Eloquent: Sérialisation -> Modification temporaire de la visibilité des propriétés

1
Grzegorz Gajda
$users = Users::get();

$subset = $users->map(function ($user) {
    return array_only(user, ['id', 'name', 'email']);
});
0
NEM

ceci est un suivi du patricus [réponse] [1] ci-dessus mais pour imbriqué tableaux:

$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
abbood

J'ai eu un problème similaire où je devais sélectionner les valeurs d'un grand tableau, mais je voulais que la collection résultante ne contienne que les valeurs d'une seule valeur.

pluck() pourrait être utilisé à cette fin (si un seul élément clé est requis)

vous pouvez aussi utiliser reduce(). Quelque chose comme ça avec réduire:

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