Ajouter un attribut personnalisé à un modèle Laravel / Eloquent en charge?

219

J'aimerais pouvoir ajouter un attribut / propriété personnalisé à un modèle Laravel / Eloquent lorsqu'il est chargé, de la même manière que cela pourrait être réalisé avec la $model->open() méthode de RedBean .

Par exemple, en ce moment, dans mon contrôleur, j'ai:

public function index()
{
    $sessions = EventSession::all();
    foreach ($sessions as $i => $session) {
        $sessions[$i]->available = $session->getAvailability();
    }
    return $sessions;
}

Ce serait bien de pouvoir omettre la boucle et d'avoir l'attribut «disponible» déjà défini et rempli.

J'ai essayé d'utiliser certains des événements de modèle décrits dans la documentation pour attacher cette propriété lors du chargement de l'objet, mais sans succès jusqu'à présent.

Remarques:

  • «disponible» n'est pas un champ de la table sous-jacente.
  • $sessionsest renvoyé en tant qu'objet JSON dans le cadre d'une API, et donc appeler quelque chose comme $session->available()dans un modèle n'est pas une option
coatesap
la source

Réponses:

317

Le problème est causé par le fait que le Modelde toArray()méthode ne tient pas compte des accesseurs qui ne se rapportent pas directement à une colonne dans la table sous - jacente.

Comme Taylor Otwell l'a mentionné ici , "c'est intentionnel et pour des raisons de performances." Cependant, il existe un moyen simple d'y parvenir:

class EventSession extends Eloquent {

    protected $table = 'sessions';
    protected $appends = array('availability');

    public function getAvailabilityAttribute()
    {
        return $this->calculateAvailability();  
    }
}

Tous les attributs répertoriés dans la propriété $ appends seront automatiquement inclus dans le tableau ou la forme JSON du modèle, à condition que vous ayez ajouté l'accesseur approprié.

Ancienne réponse (pour les versions Laravel <4.08):

La meilleure solution que j'ai trouvée consiste à remplacer la toArray()méthode et à définir explicitement l'attribut:

class Book extends Eloquent {

    protected $table = 'books';

    public function toArray()
    {
        $array = parent::toArray();
        $array['upper'] = $this->upper;
        return $array;
    }

    public function getUpperAttribute()
    {
        return strtoupper($this->title);    
    }

}

ou, si vous avez beaucoup d'accesseurs personnalisés, parcourez-les tous et appliquez-les:

class Book extends Eloquent {

    protected $table = 'books';

    public function toArray()
    {
        $array = parent::toArray();
        foreach ($this->getMutatedAttributes() as $key)
        {
            if ( ! array_key_exists($key, $array)) {
                $array[$key] = $this->{$key};   
            }
        }
        return $array;
    }

    public function getUpperAttribute()
    {
        return strtoupper($this->title);    
    }

}
coatesap
la source
Meilleure question et réponse pour aujourd'hui. Je travaillais sur différentes implémentations sur la façon d'y parvenir et juste avant de poster une question sur laravel.io, j'ai trouvé ça! Yay !!!
Gayan Hewa
Et existe-t-il un moyen de ne pas les ajouter à json pour certains cas seulement?
Jordi Puigdellívol
3
Ces attributs douaniers ne semblent pas apparaître lors de l'appel du modèle via une relation. (Ex: Models \ Company :: with ('people')). Une idée?
Andrew
@ JordiPuigdellívol Dans Laravel 5, vous pouvez utiliser le protected $hidden = []pour ajouter des colonnes / accesseurs à avoir exclu.
junkystu
124

La dernière chose sur la page doc Laravel Eloquent est:

protected $appends = array('is_admin');

Cela peut être utilisé automatiquement pour ajouter de nouveaux accesseurs au modèle sans aucun travail supplémentaire comme la modification des méthodes comme ::toArray().

Créez simplement l' getFooBarAttribute(...)accesseur et ajoutez le foo_barà la $appendsmatrice.

trm42
la source
4
Ah intéressant. Cette fonctionnalité a été ajoutée à Laravel depuis que ma question a été publiée ( github.com/laravel/framework/commit/… ). C'est la bonne réponse pour toute personne utilisant la v4.08 ou supérieure.
Coatesap
3
Cela ne sera pas disponible si vous utilisez des relations pour générer le contenu pour vos accesseurs. Dans ce cas, vous devrez peut-être recourir à la substitution de la toArrayméthode.
gpmcadam
2
Il semble que la documentation à laquelle vous avez fait référence ait été déplacée ici: https://laravel.com/docs/5.5/eloquent-serialization .
Mibbler
40

Si vous renommez votre getAvailability()méthode, getAvailableAttribute()votre méthode devient un accesseur et vous pourrez la lire ->availabledirectement sur votre modèle.

Documents: https://laravel.com/docs/5.4/eloquent-mutators#accessors-and-mutators

EDIT: votre attribut étant "virtuel", il n'est pas inclus par défaut dans la représentation JSON de votre objet.

Mais j'ai trouvé ceci: les accesseurs de modèles personnalisés ne sont pas traités lorsque -> toJson () est appelé?

Pour forcer le retour de votre attribut dans le tableau, ajoutez-le comme clé au tableau $ attributes.

class User extends Eloquent {
    protected $attributes = array(
        'ZipCode' => '',
    );

    public function getZipCodeAttribute()
    {
        return ....
    }
}

Je ne l'ai pas testé, mais cela devrait être assez trivial pour vous d'essayer dans votre configuration actuelle.

Alexandre Danault
la source
Cela fonctionne dans la mesure où il est ->availabledisponible sur l' $sessionobjet, mais comme il $sessionsest converti directement en chaîne JSON (il fait partie d'une API), il n'y a aucune chance de l'utiliser.
coatesap
Je ne suis pas sûr de comprendre comment ça marche. Si EventSession::all()renvoie un objet JSON à partir d'une API, vous n'utilisez pas vraiment un modèle Laravel, non? Désolé, je suis confus sur la façon dont vous avez implémenté votre modèle.
Alexandre Danault
EventSession est un objet Eloquent standard ( class EventSession extends Eloquent). ::all()n'est qu'un exemple. EventSession::find(170071)en serait un autre. Celles-ci sont appelées à différents points de SessionController ( SessionController extends \BaseController) qui seraient appelés via des URL telles que «/ sessions / 170071».
coatesap
Je pense que la clé peut résider dans l'objet intégré d'Eloquent à la conversion JSON qui a lieu. Même si vous ajoutez un attribut personnalisé tel que public $availablele modèle, il n'apparaît pas lors de la conversion de l'objet.
coatesap
3
Ceci est disponible depuis la sortie de Laravel 4.0.8 le 8 octobre 2013. Voir les documents officiels: laravel.com/docs/eloquent#converting-to-arrays-or-json (chercher protected $appends = array('is_admin');)
Ronald Hulshof
23

J'avais quelque chose de similaire: j'ai une image d'attribut dans mon modèle, elle contient l'emplacement du fichier dans le dossier Storage. L'image doit être retournée encodée en base64

//Add extra attribute
protected $attributes = ['picture_data'];

//Make it available in the json response
protected $appends = ['picture_data'];

//implement the attribute
public function getPictureDataAttribute()
{
    $file = Storage::get($this->picture);
    $type = Storage::mimeType($this->picture);
    return "data:" . $type . ";base64," . base64_encode($file);
}
Patrique
la source
1
Il doit s'agir de picture_data et non de pictureData dans $ attributes & $ appends. Et vous pouvez également ignorer la variable $ attributes.
Madushan Perera
16

vous pouvez utiliser la setAttributefonction dans le modèle pour ajouter un attribut personnalisé

jianfeng
la source
9

Supposons que vous ayez 2 colonnes nommées first_name et last_name dans votre table d'utilisateurs et que vous souhaitiez récupérer le nom complet. vous pouvez réaliser avec le code suivant:

class User extends Eloquent {


    public function getFullNameAttribute()
    {
        return $this->first_name.' '.$this->last_name;
    }
}

vous pouvez maintenant obtenir le nom complet sous la forme:

$user = User::find(1);
$user->full_name;
ShuBham GuPta
la source
7

Étape 1: définir les attributs à l' $appends
étape 2: définir l'accesseur pour ces attributs.
Exemple:

<?php
...

class Movie extends Model{

    protected $appends = ['cover'];

    //define accessor
    public function getCoverAttribute()
    {
        return json_decode($this->InJson)->cover;
    }
Bedram Tamang
la source
3

Dans mon modèle d'abonnement, je dois savoir que l'abonnement est suspendu ou non. voici comment je l'ai fait

public function getIsPausedAttribute() {
    $isPaused = false;
    if (!$this->is_active) {
        $isPaused = true;
    }
}

puis dans le modèle de vue, je peux utiliser $subscription->is_pausedpour obtenir le résultat.

Le getIsPausedAttributeest le format pour définir un attribut personnalisé,

et utilise is_pausedpour obtenir ou utiliser l'attribut dans votre vue.

li bing zhao
la source
2

dans mon cas, la création d'une colonne vide et la définition de son accesseur ont bien fonctionné. mon accesseur remplissant l'âge de l'utilisateur à partir de la colonne dob. La fonction toArray () a également fonctionné.

public function getAgeAttribute()
{
  return Carbon::createFromFormat('Y-m-d', $this->attributes['dateofbirth'])->age;
}
Hanif Rifa'i
la source