Obtenga solo atributos específicos con de Laravel Collection

83

He estado revisando la documentación y la API de Laravel Collections, pero parece que no encuentro lo que estoy buscando:

Me gustaría recuperar una matriz con datos de modelo de una colección, pero solo obtener atributos especificados.

Es decir, algo así como Users::toArray('id','name','email'), donde la colección de hecho contiene todos los atributos para los usuarios, porque se usan en otros lugares, pero en este lugar específico necesito una matriz con datos de usuario y solo los atributos especificados.

¿No parece haber un ayudante para esto en Laravel? - ¿Cómo puedo hacer esto de la manera más fácil?

presa
fuente
Otros espectadores podrían estar interesados Collection::implode(). Puede tomar una propiedad y extraerla de todos los objetos de la colección. Esto no responde exactamente a esta pregunta, pero podría ser útil para otros que han venido aquí desde Google, como lo hice yo. laravel.com/docs/5.7/collections#method-implode
musicin3d

Respuestas:

174

Puede hacer esto usando una combinación de Collectionmétodos existentes . Puede ser un poco difícil de seguir al principio, pero debería ser bastante fácil de romper.

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

Explicación

Primero, el map()método básicamente solo itera a través del Collection, y pasa cada elemento del Collectiona la devolución de llamada pasada. El valor devuelto por cada llamada de la devolución de llamada crea el nuevo Collectiongenerado por el map()método.

collect($user->toArray())es simplemente construir un nuevo, temporal a Collectionpartir de los Usersatributos.

->only(['id', 'name', 'email'])reduce el temporal Collectiona solo los atributos especificados.

->all()convierte la Collectioncopia temporal en una matriz simple.

Póngalo todo junto y obtendrá "Para cada usuario de la colección de usuarios, devuelva una matriz de solo los atributos de identificación, nombre y correo electrónico".


Actualización de Laravel 5.5

Laravel 5.5 agregó un onlymétodo en el Modelo, que básicamente hace lo mismo que el collect($user->toArray())->only([...])->all(), por lo que esto se puede simplificar ligeramente en 5.5+ para:

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

Si combina esto con la "mensajería de orden superior" para las colecciones introducidas en Laravel 5.4, se puede simplificar aún más:

// 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
fuente
¡Gracias! He marcado esto como la respuesta aceptada, porque funciona como un encanto con la funcionalidad existente de Laravel. - Ahora, esto podría estar disponible para la colección a través de una clase de colección personalizada, como describí en mi solución alternativa.
Preyz
Eso es. Pero me gustaría que hubiera una forma menos detallada de emular el ::get([ 'fields', 'array' ])comportamiento de QueryBuilder . Básicamente, sería útil para "vertederos".
Pilat
para arreglos que puede hacer$newArray = Arr::only($initialArray, [ 'id', 'name' /* allowed keys list */ ]);
Hop hop
esto es genial, pero me pregunto cómo puede funcionar si tengo una matriz anidada dentro
abbood
1
En las versiones recientes de laravel esto puede escribirse aún más concisa:$users->map->only(['column', ...])
okdewit
17

use User::get(['id', 'name', 'email']), le devolverá una colección con las columnas especificadas y si desea convertirlo en una matriz, solo use toArray()después del get()método así:

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

La mayoría de las veces, no necesitará convertir la colección en una matriz porque las colecciones son en realidad matrices con esteroides y tiene métodos fáciles de usar para manipular la colección.

Volantes
fuente
1
La colección ya está creada por una consulta anterior, donde se necesitan más atributos. Por lo tanto, necesito generar una matriz de datos donde solo tenga atributos específicos.
Preyz
Puede hacer que su consulta sea dinámica pasando una matriz de columnas para devolver o puede hacer otra ->get(['id', 'email', 'name'])que no volverá a consultar la base de datos, pero devolverá una nueva colección con solo los valores seleccionados. Métodos como pluck / list, get, first, etc.tienen los mismos nombres tanto en la clase del generador de consultas como en la clase de colección.
Ruffles
Ruffles, eso no funciona en Laravel 5.3. - El método "get" del generador de consultas de hecho toma columnas, pero también realiza otra consulta. El método "get" de la colección puede extraer un modelo específico por clave de la colección, que no es lo que busco aquí.
Preyz
get () siempre devuelve una colección, no importa si es DB :: table () -> get () o Model :: get (), así que si guarda la consulta en una variable y ejecuta el método get en esa $ variable de consulta, no debería obtener otra consulta en la base de datos.
Volantes
1
Cuando almaceno la consulta en una variable como $ query = Model :: where (...), luego ejecuta una consulta en la base de datos cada vez que hago una $ query-> get (). - Además, su sugerencia en mi humilde opinión no es una solución deseada para el problema. Lo resolví con algunas funciones de matriz nuevas que implementé a través de una colección personalizada (vea mi propia respuesta).
Preyz
3

El método siguiente también funciona.

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

Ahora he encontrado una solución propia para esto:

1. Creó una función general para extraer atributos específicos de matrices

La función a continuación extrae solo atributos específicos de una matriz asociativa o una matriz de matrices asociativas (la última es lo que obtiene al hacer $ collection-> toArray () en Laravel).

Se puede usar así:

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

Estoy usando las siguientes funciones:

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

Esta solución no se centra en las implicaciones de rendimiento al recorrer las colecciones en grandes conjuntos de datos.

2. Implementar lo anterior a través de una colección personalizada en Laravel

Como me gustaría poder hacer simplemente $collection->extract('id','url');en cualquier objeto de colección, he implementado una clase de colección personalizada.

Primero creé un modelo general, que extiende el modelo Eloquent, pero usa una clase de colección diferente. Todos sus modelos necesitan ampliar este modelo personalizado, y no el modelo 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 );
    }    
}
?>

En segundo lugar, creé la siguiente clase de colección personalizada:

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

Por último, todos los modelos deberían extender su modelo personalizado en su lugar, como tal:

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

Ahora las funciones del no. 1 de arriba son utilizados cuidadosamente por la colección para que el $collection->extract()método esté disponible.

presa
fuente
Consulte la documentación de Laravel sobre la extensión de colecciones: laravel.com/docs/5.5/collections#extending-collections
Andreas Hacker
0
  1. Necesita definir $hiddeny $visibleatributos. Se establecerán de forma global (eso significa que siempre devolverán todos los atributos de la $visiblematriz).

  2. Usando el método makeVisible($attribute)y makeHidden($attribute)puede cambiar dinámicamente los atributos ocultos y visibles. Más: Eloquent: Serialización -> Modificar temporalmente la visibilidad de la propiedad

Grzegorz Gajda
fuente
No quiero que esto sea global, pero selecciono los atributos para extraer de la colección de forma dinámica.
Preyz
0

Tuve un problema similar en el que necesitaba seleccionar valores de una matriz grande, pero quería que la colección resultante solo contuviera valores de un solo valor.

pluck() podría usarse para este propósito (si solo se requiere 1 elemento clave)

también podrías usar reduce(). Algo como esto con reducir:

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

este es un seguimiento del patricus [respuesta] [1] anterior, pero para matrices anidadas :

$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
fuente
-1

esto parece funcionar, pero no estoy seguro de si está optimizado para el rendimiento o no.

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

salida:

array:2 [
  0 => 4
  1 => 1
]
conocido
fuente
-3
$users = Users::get();

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