Laravel: Ottieni oggetto dalla raccolta per attributo


92

In Laravel, se eseguo una query:

$foods = Food::where(...)->get();

... allora $foodsè una Collezione Illuminate diFood oggetti modello. (Essenzialmente una serie di modelli.)

Tuttavia, le chiavi di questo array sono semplicemente:

[0, 1, 2, 3, ...]

... quindi se voglio modificare, ad esempio, l' Foodoggetto con un valore iddi 24, non posso farlo:

$desired_object = $foods->get(24);
$desired_object->color = 'Green';
$desired_object->save();

... perché questo altererà semplicemente il 25 ° elemento nell'array, non l'elemento con id24.

Come ottengo un singolo (o più) elemento (i) da una raccolta da QUALSIASI attributo / colonna (come, ma non limitato a, id / colore / età / ecc.)?

Certo, posso farlo:

foreach ($foods as $food) {
    if ($food->id == 24) {
        $desired_object = $food;
        break;
    }
}
$desired_object->color = 'Green';
$desired_object->save();

... ma è solo schifoso.

E, ovviamente, posso farlo:

$desired_object = Food::find(24);
$desired_object->color = 'Green';
$desired_object->save();

... ma è ancora più grossolano , perché esegue una query aggiuntiva non necessaria quando ho già l'oggetto desiderato nella $foodsraccolta.

Grazie in anticipo per qualsiasi consiglio.

MODIFICARE:

Per essere chiari, puoi chiamare ->find()una collezione Illuminate senza generare un'altra query, ma accetta solo un ID principale. Per esempio:

$foods = Food::all();
$desired_food = $foods->find(21);  // Grab the food with an ID of 21

Tuttavia, non esiste ancora un modo pulito (senza loop, senza query) per afferrare uno o più elementi da un attributo da una Collection, come questo:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This won't work.  :(

Risposte:


128

Puoi usare filter, in questo modo:

$desired_object = $food->filter(function($item) {
    return $item->id == 24;
})->first();

filtersarà anche restituire un Collection, ma dal momento che sai che ci sarà una sola, è possibile chiamare firstsu quella Collection.

Non hai più bisogno del filtro (o forse mai, non so che abbia quasi 4 anni). Puoi semplicemente usare first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});

7
Ehi, grazie! Penso di poter vivere con quello. Ancora insolitamente prolisso secondo me per quello che di solito è un framework "eloquente" haha. Ma è ancora molto più pulito delle alternative finora, quindi lo prenderò.
Durata

Come @squaretastic sta sottolineando nell'altra risposta, all'interno della tua chiusura stai facendo un incarico e non un confronto (cioè dovresti == e non =)
ElementalStorm

24
In realtà non è nemmeno necessario chiamare filter()->first(), puoi semplicemente chiamarefirst(function(...))
lukasgeiter

dalla documentazione della Laravel Collection. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro

2
Puoi fare la stessa cosa con la funzione where. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar

111

Laravel fornisce un metodo chiamato keyByche consente di impostare le chiavi per chiave data nel modello.

$collection = $collection->keyBy('id');

restituirà la raccolta ma con le chiavi come valori di idattributo di qualsiasi modello.

Quindi puoi dire:

$desired_food = $foods->get(21); // Grab the food with an ID of 21

e afferrerà l'elemento corretto senza il pasticcio di utilizzare una funzione di filtro.


2
Veramente utile, soprattutto per le prestazioni, -> first () può essere lento se chiamato più volte (foreach in foreach ...) in modo da poter "indicizzare" la tua raccolta come: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;e usare ->get($category->id . ' ' . $manufacturer->id)dopo!
François Breton

Questa chiave continua a essere utilizzata quando vengono aggiunti nuovi elementi alla raccolta? O devo usare keyBy () ogni volta che un nuovo oggetto o array viene inserito nella raccolta?
Jason,

Molto probabilmente dovrai chiamarlo di nuovo poiché keyByrestituisce una nuova collezione da quello che ricordo, non sono sicuro però, puoi controllare Illuminate/Support/Collectionper scoprirlo. (Non lavoro in Laravel da un po 'di tempo quindi qualcuno può correggermi).
Maksym Cierzniak

Questo non ha funzionato per me, ha restituito un altro articolo, l'elemento successivo, se digito get (1) restituirà l'elemento che ha il numero 2 come ID.
Jaqueline Passos

Batch caricamento di un tavolo e ci è voluto un giorno. Ho usato questa soluzione e ci sono voluti pochi minuti.
Jed Lynch

24

A partire da Laravel 5.5 puoi usare firstWhere ()

Nel tuo caso:

$green_foods = $foods->firstWhere('color', 'green');

3
Questa dovrebbe essere la risposta accettata dopo Laravel 5.5
beerwin

7

Dal momento che non ho bisogno di eseguire il loop dell'intera raccolta, penso che sia meglio avere una funzione di supporto come questa

/**
 * Check if there is a item in a collection by given key and value
 * @param Illuminate\Support\Collection $collection collection in which search is to be made
 * @param string $key name of key to be checked
 * @param string $value value of key to be checkied
 * @return boolean|object false if not found, object if it is found
 */
function findInCollection(Illuminate\Support\Collection $collection, $key, $value) {
    foreach ($collection as $item) {
        if (isset($item->$key) && $item->$key == $value) {
            return $item;
        }
    }
    return FALSE;
}

7

Usa i metodi di raccolta integrati contengono e trova , che cercheranno per ID primari (invece che per chiavi di matrice). Esempio:

if ($model->collection->contains($primaryId)) {
    var_dump($model->collection->find($primaryId);
}

contiene () in realtà chiama solo find () e controlla null, quindi potresti abbreviarlo a:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}

Sappiamo che find () accetta un ID principale. Quello che vogliamo è un metodo che accetti qualsiasi attributo, come "colore" o "età". Finora, il metodo di Kalley è l'unico che funziona per qualsiasi attributo.
Durata

5

So che questa domanda è stata originariamente posta prima del rilascio di Laravel 5.0, ma a partire da Laravel 5.0, le raccolte supportano il where()metodo per questo scopo.

Per Laravel 5.0, 5.1 e 5.2, il where()metodo su Collectionfarà solo un confronto uguale. Inoltre, ===per impostazione predefinita esegue un confronto rigoroso di uguale ( ). Per fare un confronto sciolto ( ==), puoi passare falsecome terzo parametro o utilizzare il whereLoose()metodo.

A partire da Laravel 5.3, il where()metodo è stato ampliato per funzionare più come il where()metodo per il generatore di query, che accetta un operatore come secondo parametro. Inoltre, come il generatore di query, l'operatore imposta per impostazione predefinita un confronto uguale se non viene fornito nessuno. Anche il confronto predefinito è stato cambiato da rigoroso per impostazione predefinita a libero per impostazione predefinita. Quindi, se desideri un confronto rigoroso, puoi usare whereStrict(), o semplicemente usare ===come operatore per where().

Pertanto, a partire da Laravel 5.0, l'ultimo esempio di codice nella domanda funzionerà esattamente come previsto:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This will work.  :)

// This will only work in Laravel 5.3+
$cheap_foods = $foods->where('price', '<', 5);

// Assuming "quantity" is an integer...
// This will not match any records in 5.0, 5.1, 5.2 due to the default strict comparison.
// This will match records just fine in 5.3+ due to the default loose comparison.
$dozen_foods = $foods->where('quantity', '12');

3

Devo sottolineare che c'è un piccolo ma assolutamente CRITICO errore nella risposta di Kalley. Ho lottato con questo per diverse ore prima di rendermi conto:

All'interno della funzione, ciò che stai restituendo è un confronto, quindi qualcosa del genere sarebbe più corretto:

$desired_object = $food->filter(function($item) {
    return ($item->id **==** 24);
})->first();

1
Sì, grazie per averlo fatto notare. È anche importante notare che la funzione di filtro non è diversa dal mio foreach()esempio dal punto di vista delle prestazioni, perché fa solo lo stesso tipo di loop ... in effetti, il mio foreach()esempio è più performante perché si interrompe quando trova il modello corretto. Inoltre ... {Collection}->find(24)verrà catturato dalla chiave primaria, il che lo rende l'opzione migliore qui. Il filtro proposto da Kalley è in realtà identico $desired_object = $foods->find(24);.
Durata

1
Mai visto l' **==**operatore, cosa fa?
kiradotee

@kiradotee Penso che l'OP stesse solo tentando di enfatizzare l'operatore di doppio confronto uguale ( == ). La risposta originale utilizzava solo un segno di uguale, quindi stava eseguendo un'assegnazione anziché un confronto. OP stava cercando di sottolineare che dovrebbero esserci due segni di uguale.
patricus


0

Come la domanda precedente, quando si utilizza la clausola where, è necessario utilizzare anche il metodo get Or first per ottenere il risultato.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.