Adicionar um atributo personalizado a um modelo Laravel / Eloquent em carga?

219

Eu gostaria de poder adicionar um atributo / propriedade personalizado a um modelo Laravel / Eloquent quando ele for carregado, semelhante a como isso pode ser alcançado com o $model->open() método do RedBean .

Por exemplo, no momento, no meu controlador, tenho:

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

Seria bom poder omitir o loop e ter o atributo 'disponível' já definido e preenchido.

Tentei usar alguns dos eventos de modelo descritos na documentação para anexar essa propriedade quando o objeto for carregado, mas sem sucesso até o momento.

Notas:

  • 'disponível' não é um campo na tabela subjacente.
  • $sessionsestá sendo retornado como um objeto JSON como parte de uma API e, portanto, chamar algo como $session->available()em um modelo não é uma opção
coatesap
fonte

Respostas:

316

O problema é causado pelo fato de o método Models toArray()ignorar quaisquer acessadores que não estejam diretamente relacionados a uma coluna na tabela subjacente.

Como Taylor Otwell mencionou aqui , "Isso é intencional e por razões de desempenho". No entanto, existe uma maneira fácil de conseguir isso:

class EventSession extends Eloquent {

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

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

Quaisquer atributos listados na propriedade $ appends serão automaticamente incluídos na matriz ou no formato JSON do modelo, desde que você tenha adicionado o acessador apropriado.

Resposta antiga (para versões do Laravel <4.08):

A melhor solução que eu encontrei é substituir o toArray()método e, explicitamente, definir o 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);    
    }

}

ou, se você tiver muitos acessadores personalizados, percorra todos eles e aplique-os:

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
fonte
Melhor pergunta e resposta para hoje. Eu estava trabalhando em diferentes implementações sobre como conseguir isso e pouco antes de postar uma pergunta no laravel.io eu encontrei isso! yay !!!
21714 Gayan Hewa
E existe uma maneira de não adicioná-los ao json apenas em alguns casos?
Jordi Puigdellívol 18/01/2015
3
Esses atributos alfandegários parecem não aparecer ao chamar o modelo por meio de um relacionamento. (Ex: Models \ Company :: with ('people')). Qualquer ideia?
22415 Andrew Andrew
@ JordiPuigdellívol No Laravel 5, você pode usar o protected $hidden = []para adicionar colunas / acessadores que foram excluídos.
junkystu
124

A última coisa na página de documento do Laravel Eloquent é:

protected $appends = array('is_admin');

Isso pode ser usado automaticamente para adicionar novos acessadores ao modelo sem nenhum trabalho adicional, como métodos de modificação ::toArray().

Basta criar getFooBarAttribute(...)acessor e adicione o foo_bara $appendsmatriz.

trm42
fonte
4
Ah interessante. Esse recurso foi adicionado ao Laravel desde que minha pergunta foi publicada ( github.com/laravel/framework/commit/… ). Esta é a resposta certa para quem usa a v4.08 ou superior.
coatesap
3
Isso não estará disponível para você se você estiver usando relacionamentos para gerar o conteúdo para seus acessadores. Nesse caso, talvez seja necessário recorrer à substituição do toArraymétodo.
Gpmcadam
2
Parece que a documentação a que você se referiu foi movida para aqui: https://laravel.com/docs/5.5/eloquent-serialization .
mibbler
40

Se você renomear seu getAvailability()método, ele se getAvailableAttribute()tornará um acessador e poderá lê-lo ->availablediretamente no seu modelo.

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

EDIT: Como seu atributo é "virtual", ele não é incluído por padrão na representação JSON do seu objeto.

Mas achei o seguinte: Acessadores de modelo personalizado não processados ​​quando -> toJson () chamado?

Para forçar o retorno do seu atributo na matriz, adicione-o como uma chave à matriz $ attribute.

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

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

Não testei, mas deve ser bastante trivial para você tentar na sua configuração atual.

Alexandre Danault
fonte
Isso funciona na medida em que fica ->availabledisponível no $sessionobjeto, mas, conforme $sessionsé convertido diretamente em uma string JSON (faz parte de uma API), não há chance de usar isso.
coatesap
Não sei se entendi como suas coisas funcionam. Se EventSession::all()retornar um objeto JSON de uma API, você realmente não está usando um modelo Laravel, certo? Desculpe, estou confuso sobre como você implementou seu modelo.
Alexandre Danault
EventSession é um objeto Eloquent padrão ( class EventSession extends Eloquent). ::all()é apenas um exemplo. EventSession::find(170071)seria outro. Eles são chamados em vários pontos do SessionController ( SessionController extends \BaseController), que seriam chamados por URLs como '/ sessions / 170071'.
coatesap
Acho que a chave pode estar no objeto interno do Eloquent para a conversão JSON que ocorre. Mesmo se você adicionar um atributo personalizado, como public $availableao modelo, isso não será mostrado quando o objeto for convertido.
coatesap
3
Está disponível desde o lançamento do Laravel 4.0.8 em 8 de outubro de 2013. Consulte os documentos oficiais: laravel.com/docs/eloquent#converting-to-arrays-or-json (procure protected $appends = array('is_admin');)
Ronald Hulshof
23

Eu tinha algo semelhante: tenho uma imagem de atributo no meu modelo, que contém a localização do arquivo na pasta Armazenamento. A imagem deve ser retornada codificada em 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
fonte
1
Deverá ser picture_data e não pictureData em $ attribute & $ appends. E você também pode pular a variável $ attribute.
Madushan Perera
16

você pode usar a setAttributefunção no Model para adicionar um atributo customizado

jianfeng
fonte
9

Digamos que você tenha 2 colunas denominadas nome e sobrenome na tabela de usuários e deseja recuperar o nome completo. você pode conseguir com o seguinte código:

class User extends Eloquent {


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

agora você pode obter o nome completo como:

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

Etapa 1: defina atributos na $appends
Etapa 2: defina o acessador para esses atributos.
Exemplo:

<?php
...

class Movie extends Model{

    protected $appends = ['cover'];

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

No meu modelo de assinatura, preciso saber se a assinatura está em pausa ou não. aqui está como eu fiz isso

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

depois, no modelo de exibição, eu posso usar $subscription->is_pausedpara obter o resultado.

O getIsPausedAttributeé o formato para definir um atributo personalizado,

e usa is_pausedpara obter ou usar o atributo em sua exibição.

li bing zhao
fonte
2

no meu caso, criar uma coluna vazia e definir seu acessador funcionou bem. meu acessador preenchendo a idade do usuário da coluna dob. A função toArray () também funcionou.

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