¿Agregar un atributo personalizado a un modelo Laravel / Eloquent en carga?

219

Me gustaría poder agregar un atributo / propiedad personalizado a un modelo Laravel / Eloquent cuando se carga, de forma similar a cómo se podría lograr con el $model->open() método de RedBean .

Por ejemplo, en este momento, en mi controlador tengo:

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

Sería bueno poder omitir el ciclo y tener el atributo 'disponible' ya establecido y poblado.

Intenté usar algunos de los eventos del modelo descritos en la documentación para adjuntar esta propiedad cuando se carga el objeto, pero hasta ahora no he tenido éxito.

Notas:

  • 'disponible' no es un campo en la tabla subyacente.
  • $sessionsse devuelve como un objeto JSON como parte de una API y, por lo tanto, llamar a algo como $session->available()en una plantilla no es una opción
coatesap
fuente

Respuestas:

316

El problema es causado por el hecho de que el método Model's toArray()ignora cualquier acceso que no se relacione directamente con una columna en la tabla subyacente.

Como Taylor Otwell mencionó aquí , "Esto es intencional y por razones de rendimiento". Sin embargo, hay una manera fácil de lograr esto:

class EventSession extends Eloquent {

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

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

Todos los atributos enumerados en la propiedad $ appends se incluirán automáticamente en la matriz o en la forma JSON del modelo, siempre que haya agregado el descriptor de acceso apropiado.

Respuesta anterior (para versiones de Laravel <4.08):

La mejor solución que he encontrado es anular el toArray()método y establecer explícitamente el atributo:

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

}

o, si tiene muchos accesos personalizados, recorra todos y aplíquelos:

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
fuente
La mejor pregunta y respuesta para hoy. Estaba trabajando en diferentes implementaciones sobre cómo lograr esto y justo antes de publicar una pregunta en laravel.io ¡encontré esto! Hurra !!!
Gayan Hewa
¿Y hay una manera de no agregarlos a json solo por algunos casos?
Jordi Puigdellívol
3
Estos atributos aduaneros no parecen aparecer cuando se llama al modelo a través de una relación. (Ej: Modelos \ Compañía :: con ('personas')). ¿Alguna idea?
Andrew
@ JordiPuigdellívol En Laravel 5, puede usar el protected $hidden = []para agregar columnas / accesores que hayan excluido.
junkystu
124

Lo último en la página de documentos de Laravel Eloquent es:

protected $appends = array('is_admin');

Eso se puede usar automáticamente para agregar nuevos accesos al modelo sin ningún trabajo adicional, como modificar métodos como ::toArray().

Basta con crear getFooBarAttribute(...)descriptor de acceso y añadir la foo_barde $appendsmatriz.

trm42
fuente
44
Ah interesante. Esta característica se ha agregado a Laravel desde que se publicó mi pregunta ( github.com/laravel/framework/commit/… ). Esta es la respuesta correcta para cualquier persona que use v4.08 o superior.
coatesap
3
Esto no estará disponible para usted si está utilizando relaciones para generar el contenido para sus accesores. En este caso, puede que tenga que recurrir a anular el toArraymétodo.
gpmcadam
2
Parece que la documentación a la que se refirió se ha movido aquí: https://laravel.com/docs/5.5/eloquent-serialization .
mibbler
40

Si cambia el nombre de su getAvailability()método a getAvailableAttribute()su método, se convierte en un descriptor de acceso y podrá leerlo ->availabledirectamente en su modelo.

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

EDITAR: Dado que su atributo es "virtual", no está incluido por defecto en la representación JSON de su objeto.

Pero encontré esto: ¿ Los accesos de modelos personalizados no se procesan cuando -> toJson () llama?

Para forzar que su atributo sea devuelto en la matriz, agréguelo como una clave a la matriz $ atributos.

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

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

No lo probé, pero debería ser bastante trivial para que lo intentes en tu configuración actual.

Alexandre Danault
fuente
Esto funciona tanto como está ->availabledisponible en el $sessionobjeto, pero a medida que $sessionsse convierte directamente en una cadena JSON (es parte de una API), no hay posibilidad de usar esto.
coatesap
No estoy seguro de entender cómo funcionan tus cosas. Si EventSession::all()devuelve un objeto JSON de una API, realmente no está utilizando un modelo de Laravel, ¿verdad? Lo siento, estoy confundido sobre cómo implementó su modelo.
Alexandre Danault
EventSession es un objeto Eloquent estándar ( class EventSession extends Eloquent). ::all()Es solo un ejemplo. EventSession::find(170071)Sería otro. Se llaman en varios puntos a lo largo de SessionController ( SessionController extends \BaseController) que se llamarían a través de URL como '/ sessions / 170071'.
coatesap
Creo que la clave puede estar en el objeto incorporado de Eloquent a la conversión JSON que tiene lugar. Incluso si agrega un atributo personalizado, como public $availableal modelo, esto no se muestra cuando se convierte el objeto.
coatesap
3
Está disponible desde el lanzamiento de Laravel 4.0.8 el 8 de octubre de 2013. Vea los documentos oficiales: laravel.com/docs/eloquent#converting-to-arrays-or-json (busque protected $appends = array('is_admin');)
Ronald Hulshof
23

Tenía algo simular: tengo una imagen de atributo en mi modelo, que contiene la ubicación del archivo en la carpeta Almacenamiento. La imagen debe devolverse codificada 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
fuente
1
Debe ser picture_data, no pictureData en $ atributos y $ appends. Y también puede omitir la variable de atributos $.
Madushan Perera
16

puede usar la setAttributefunción en Modelo para agregar un atributo personalizado

jianfeng
fuente
9

Digamos que tiene 2 columnas llamadas first_name y last_name en su tabla de usuarios y desea recuperar el nombre completo. puedes lograrlo con el siguiente código:

class User extends Eloquent {


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

ahora puedes obtener el nombre completo como:

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

Paso 1: Definir atributos en $appends
Paso 2: Definir el descriptor de acceso para esos atributos.
Ejemplo:

<?php
...

class Movie extends Model{

    protected $appends = ['cover'];

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

En mi modelo de suscripción, necesito saber si la suscripción está pausada o no. así es como lo hice

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

luego en la plantilla de vista, puedo usar $subscription->is_pausedpara obtener el resultado.

El getIsPausedAttributees el formato para establecer un atributo personalizado,

y usa is_pausedpara obtener o usar el atributo en su vista.

li bing zhao
fuente
2

en mi caso, crear una columna vacía y configurar su accesor funcionó bien. mi descriptor de acceso llenando la edad del usuario desde la columna dob. La función toArray () también funcionó.

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