Aller au contenu principal
Making DigitalMaking Digital
Note technique

Laravel pluck() pour remplir un select : pourquoi des options disparaissent

En bref

pluck('nom', 'cle') construit un tableau PHP : une clé décimale y est tronquée en entier et deux lignes avec la même clé n'en font qu'une, d'où des options qui disparaissent d'un select. Un cast en string ne s'applique pas à la clé d'un pluck() fait sur la requête. Pour un select, utilisez toujours pluck('nom', 'id'), et la méthode casts() du modèle pour typer les colonnes.

Une liste déroulante de pays construite avec pluck() affiche deux options au lieu de trois, voire une seule. Le code a l'air juste : Pays::orderBy('nom')->pluck('nom', 'taux'). La cause est la clé choisie : pluck() construit un tableau PHP, et PHP convertit certaines clés. Un nombre décimal devient un entier, et deux lignes qui tombent sur la même clé n'en font plus qu'une. La solution : utiliser l'id comme clé, toujours.

Comment remplir un select avec pluck() ?

Le second argument de pluck() indique la colonne qui sert de clé. Pour un <select>, la clé devient la valeur de l'option et la valeur devient le libellé :

PHP
// Dans le contrôleur
$pays = Pays::orderBy('nom')->pluck('nom', 'id');

return view('pays-select', ['pays' => $pays, 'paysId' => 2]);
Blade
{{-- resources/views/pays-select.blade.php --}}
<select name="pays_id">
    @foreach ($pays as $id => $nom)
        <option value="{{ $id }}" @selected(old('pays_id', $paysId) == $id)>{{ $nom }}</option>
    @endforeach
</select>

Dans notre test, la page rend les trois options 1 Belgique, 2 France (avec selected) et 3 Luxembourg. @selected est une directive Blade : elle ajoute l'attribut selected quand la condition est vraie, et old('pays_id', $paysId) reprend la valeur envoyée avec le formulaire précédent, sinon $paysId.

Pourquoi des options disparaissent ?

Pour le voir, nous avons créé une table pays avec trois lignes : Belgique (taux 1,10), France (1,50) et Luxembourg (1,10). Deux mécanismes font perdre des lignes.

1. PHP convertit les clés décimales en entiers. La documentation de PHP l'écrit : un nombre décimal utilisé comme clé est tronqué, 8.7 devient 8. Avec SQLite, Pays::orderBy('nom')->pluck('nom', 'taux') renvoie les taux en nombres décimaux : 1,10, 1,50 et 1,10 deviennent tous la clé 1, et il ne reste que [1 => 'Luxembourg']. PHP 8.3 émet bien l'avertissement Implicit conversion from float 1.5 to int loses precision, mais Laravel envoie ce type d'avertissement au canal deprecations, réglé sur null dans une application neuve : rien ne s'affiche. Avec LOG_DEPRECATIONS_CHANNEL=single, il apparaît dans storage/logs/laravel.log.

2. Deux lignes avec la même clé n'en font qu'une. La documentation de Laravel le précise : en cas de clés en double, pluck() garde le dernier élément. Même avec des clés en texte ('1.10', '1.50'), Belgique et Luxembourg partagent le taux 1,10 : il reste deux options sur trois.

Requête (3 pays) SQLite MySQL 5.7
pluck('nom', 'taux'), colonne DECIMAL [1 => 'Luxembourg'] ['1.10' => 'Luxembourg', '1.50' => 'France']
pluck('nom', 'taux_float'), colonne FLOAT [1 => 'Luxembourg'] [1 => 'Luxembourg']
pluck('nom', 'id') 3 pays 3 pays

Le résultat dépend donc aussi de la base : MySQL renvoie une colonne DECIMAL en texte et une colonne FLOAT en nombre décimal, SQLite renvoie les deux en nombres décimaux.

Un cast en string suffit-il ?

Pas toujours. Avec 'taux_float' => 'string' déclaré dans le modèle, Pays::orderBy('nom')->pluck('nom', 'taux_float') (directement sur la requête) donnait toujours [1 => 'Luxembourg'], sous SQLite comme sous MySQL : sur une requête, Eloquent n'applique les casts qu'à la colonne des valeurs, pas à celle des clés. Le cast ne s'applique à la clé qu'en passant par les modèles, avec Pays::orderBy('nom')->get()->pluck('nom', 'taux_float'), qui charge toutes les colonnes : on obtient ['1.1' => 'Luxembourg', '1.5' => 'France']. Le doublon du point 2 reste, Belgique manque toujours.

Comment déclarer les casts aujourd'hui ?

Si vous avez besoin d'un taux exact en texte (pour l'afficher, pas comme clé), la documentation actuelle déclare les casts dans une méthode casts() :

PHP
// app/Models/Pays.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Pays extends Model
{
    protected $table = 'pays';

    public $timestamps = false;

    protected function casts(): array
    {
        return [
            'taux' => 'decimal:2',
        ];
    }
}

Avec ce cast, Pays::first()->taux renvoie '1.10', en texte avec deux décimales, quelle que soit la base.

Les clés numériques changent-elles l'ordre des options ?

En PHP, non : l'ordre d'insertion est conservé. Mais une clé texte qui ressemble à un entier ('10', '2') est convertie en entier, contrairement à '007', qui reste du texte. Le piège arrive quand la collection part vers JavaScript (Alpine, Livewire, fetch) : {"10":"Belgique","2":"France","007":"Luxembourg"} devient, en JavaScript, un objet dont les clés sont parcourues dans l'ordre 2, 10, 007 (vérifié avec Node.js). Pour garder l'ordre, envoyez une liste :

PHP
// Pour une réponse JSON
$liste = $pays->map(fn ($nom, $id) => ['id' => $id, 'nom' => $nom])->values();

Le JSON produit est alors un tableau, [{"id":10,"nom":"Belgique"},...], dont l'ordre ne bouge pas.

Ce sont des détails que nous vérifions sur les applications sur mesure et les connecteurs entre logiciels que nous développons. Une liste qui perd des lignes ? Parlons-en.

À retenir
  • Pour un select : pluck('libelle', 'id'), jamais une colonne décimale ou non unique comme clé.
  • PHP tronque les clés décimales en entiers, et pluck() garde la dernière ligne en cas de doublon.
  • Un cast ne s'applique pas à la clé d'un pluck() fait sur la requête.
  • Casts dans la méthode casts() ; listes vers JavaScript avec map() et values().

Testé avec Laravel 13.37.0, PHP 8.3.6, SQLite et MySQL 5.7.

Questions fréquentes

Pourquoi l'avertissement Implicit conversion from float n'apparaît-il nulle part ?

Laravel envoie ces avertissements au canal deprecations, réglé sur null dans une application neuve. Avec LOG_DEPRECATIONS_CHANNEL=single dans le .env, ils s'écrivent dans storage/logs/laravel.log.

La propriété $casts fonctionne-t-elle encore ?

Oui : dans notre test, un modèle avec protected $casts = ['taux' => 'decimal:2'] renvoie bien '1.50'. La documentation actuelle utilise la méthode casts(), qui permet aussi d'écrire des casts avec des appels de méthode.

Peut-on utiliser une colonne texte comme clé, par exemple un code ?

Oui si elle est unique, mais un code qui ressemble à un entier ('10') devient l'entier 10, alors que '007' reste du texte.

Making Digital

Nous connectons les logiciels des PME, automatisons leurs tâches répétitives et créons des assistants IA, depuis 2010. Nos articles partent de nos projets réels.

Notre parcours
Une question sur ce sujet ?

Parlons-en 30 minutes.

Vous saurez si votre problème se règle, comment, et en combien de temps. Sans engagement.

Un échange de 30 minutes, en visio ou par téléphone

Gratuit, sans engagement. On parle de votre problème, pas de technique.