Verificação do Laravel se existe um modelo relacionado

151

Eu tenho um modelo eloquente que tem um modelo relacionado:

public function option() {
    return $this->hasOne('RepairOption', 'repair_item_id');
}

public function setOptionArrayAttribute($values)
{
    $this->option->update($values);
}

Quando crio o modelo, ele não tem necessariamente um modelo relacionado. Quando atualizá-lo, posso adicionar uma opção ou não.

Portanto, preciso verificar se o modelo relacionado existe, para atualizá-lo ou criá-lo, respectivamente:

$model = RepairItem::find($id);
if (Input::has('option')) {
    if (<related_model_exists>) {
        $option = new RepairOption(Input::get('option'));
        $option->repairItem()->associate($model);
        $option->save();
        $model->fill(Input::except('option');
    } else {
       $model->update(Input::all());
    }
};

Onde <related_model_exists>está o código que estou procurando?

Tom Macdonald
fonte
3
Impressionante pergunta obrigado! E ótimas respostas para os caras abaixo. Economizou tempo no meu projeto.
Rafael

Respostas:

197

No php 7.2+ você não pode usar counto objeto de relação, portanto, não existe um método único para todas as relações. Use o método de consulta como @tremby fornecido abaixo:

$model->relation()->exists()

solução genérica trabalhando em todos os tipos de relação ( pré php 7.2 ):

if (count($model->relation))
{
  // exists
}

Isso funcionará para todas as relações, pois as propriedades dinâmicas retornam Modelou Collection. Ambos implementam ArrayAccess.

Então é assim:

relações únicas: hasOne / belongsTo/ morphTo/morphOne

// no related model
$model->relation; // null
count($model->relation); // 0 evaluates to false

// there is one
$model->relation; // Eloquent Model
count($model->relation); // 1 evaluates to true

relações com muitos: hasMany / belongsToMany/ morphMany/ morphToMany/morphedByMany

// no related collection
$model->relation; // Collection with 0 items evaluates to true
count($model->relation); // 0 evaluates to false

// there are related models
$model->relation; // Collection with 1 or more items, evaluates to true as well
count($model->relation); // int > 0 that evaluates to true
Jarek Tkaczyk
fonte
1
Leia tudo. count($relation)é uma solução geral para todas as relações. Ele funcionará para Modele Collection, enquanto Modelnão tiver ->count()método.
Jarek Tkaczyk
7
@CurvianVynes Não, não. Collectionpossui seu próprio método isEmpty, mas a emptyfunção genérica retorna false para um objeto (portanto, não funciona para a coleção vazia).
Jarek Tkaczyk
1
count($model->relation)não funcionou morphToquando o relacionamento ainda não tinha uma associação definida. ID e tipo externos são nulos e a consulta db criada pelo Laravel é falsa e gera exceção. Eu usei $model->relation()->getOtherKey()como solução alternativa.
Jocelyn
1
@Jocelyn Sim, é um bug eloquente. Infelizmente, existem pelo menos alguns deles para relações polimórficas, portanto, obviamente, você não pode confiar neles de forma alguma.
Jarek Tkaczyk
2
Ele irá quebrar no PHP 7.2, retornando:count(): Parameter must be an array or an object that implements Countable
CodeGodie 26/06
81

Um objeto Relation transmite chamadas de método desconhecidas para um Eloquent query Builder , configurado para selecionar apenas os objetos relacionados. Esse construtor, por sua vez, transmite chamadas de método desconhecido para o construtor de consultas subjacente .

Isso significa que você pode usar os métodos exists()ou count()diretamente de um objeto de relação:

$model->relation()->exists(); // bool: true if there is at least one row
$model->relation()->count(); // int: number of related rows

Observe os parênteses depois de relation: ->relation()é uma chamada de função (obtendo o objeto de relação), em vez de ->relationum getter de propriedades mágicas configurado para você pelo Laravel (obtendo o objeto / objetos relacionados).

Usar o countmétodo no objeto de relação (ou seja, usar parênteses) será muito mais rápido do que fazer $model->relation->count()ou count($model->relation)(a menos que a relação já tenha sido carregada com antecedência), pois executa uma consulta de contagem em vez de extrair todos os dados de quaisquer objetos relacionados do banco de dados, apenas para contá-los. Da mesma forma, o uso existstambém não precisa extrair dados do modelo.

Ambos exists()e count()trabalho em todos os tipos de relação que eu tentei, por isso, pelo menos belongsTo, hasOne, hasMany, e belongsToMany.

tremer
fonte
existe não está disponível no lúmen, não sei por quê.
Briankip
@briankip - deveria. Tem certeza de que está obtendo o objeto de relação (chamando o método) em vez da coleção (usando a propriedade mágica)?
tremby
18

Eu prefiro usar o existsmétodo:

RepairItem::find($id)->option()->exists()

para verificar se o modelo relacionado existe ou não. Está funcionando bem no Laravel 5.2

Hafez Divandari
fonte
1
+1; count ($ model-> relacionamento) estava retornando verdadeiro para mim no Laravel 5.2, mesmo que não houvesse nenhum item na tabela de relações. -> existe () faz o truque.
Ben Wilson
9

Após o Php 7.1 , a resposta aceita não funcionará para todos os tipos de relacionamentos.

Porque, dependendo do tipo do relacionamento, o Eloquent retornará a Collection, a Modelou Null. E no Php 7.1 count(null) lançará um error.

Portanto, para verificar se a relação existe, você pode usar:

Para relacionamentos únicos: por exemplo hasOneebelongsTo

if(!is_null($model->relation)) {
   ....
}

Para relacionamentos múltiplos: por exemplo: hasManyebelongsToMany

if ($model->relation->isNotEmpty()) {
   ....
}
Hemerson Varela
fonte
4

Não tenho certeza se isso mudou no Laravel 5, mas a resposta aceita usando count($data->$relation)não funcionou para mim, pois o próprio ato de acessar a propriedade de relação fez com que ela fosse carregada.

No final, um simples isset($data->$relation)fez o truque para mim.

Dave Stewart
fonte
Eu acredito que é $data->relationsem $(não pode editar, porque de 6 caracteres limite)
Zanshin13
2
Ah, esse $relationseria o nome da sua relação, assim $data->postsou não. Desculpe se isso foi confuso, eu queria deixar claro que relationnão era uma propriedade modelo concreto: P
Dave Stewart
Isso funcionou por um tempo, mas parou de funcionar depois que eu atualizei o Laravel de 5.2.29 para 5.2.45. Alguma idéia de por que ou como corrigi-lo? Agora, está causando o carregamento dos dados relacionais por algum motivo.
26616 Anthony
Eu adicionei uma resposta que tem uma correção para isso.
Anthony
3

Você pode usar o método relacionLoaded no objeto de modelo. Isso salvou meu bacon e espero que ajude outra pessoa. Recebi essa sugestão quando fiz a mesma pergunta em Laracasts.

Anthony
fonte
2

Como Hemerson Varela já disse no Php 7.1 count(null), lançará um errore hasOneretornará nullse não houver linha. Como você tem uma hasOnerelação, eu usaria o emptymétodo para verificar:

$model = RepairItem::find($id);
if (!empty($temp = $request->input('option'))) {
   $option = $model->option;

   if(empty($option)){
      $option = $model->option()->create();
   }

   $option->someAttribute = temp;
   $option->save();
};

Mas isso é supérfluo. Não há necessidade de verificar se a relação existe, para determinar se você deve fazer uma updateou uma createchamada. Basta usar o método updateOrCreate . Isso é equivalente ao acima:

$model = RepairItem::find($id);
if (!empty($temp = $request->input('option'))) {  
   $model->option()
         ->updateOrCreate(['repair_item_id' => $model->id],
                          ['option' => $temp]);
}
Adão
fonte
0

Eu tive que refatorar completamente meu código quando atualizei minha versão do PHP para 7.2+ devido ao mau uso da função count ($ x). Isso é uma verdadeira dor e também é extremamente assustador, pois existem centenas de usos, em diferentes cenários e não há regras para todos.

Regras que segui para refatorar tudo, exemplos:

$ x = Auth :: user () -> posts-> find (6); (verifique se o usuário tem um ID de postagem = 6 usando -> find ())

[FAILS] if(count($x)) { return 'Found'; } 
[GOOD] if($x) { return 'Found'; }

$ x = Auth :: user () -> perfil-> departamentos; (verifique se o perfil tem alguns departamentos, pode haver muitos departamentos)

[FAILS] if(count($x)) { return 'Found'; }
[GOOD] if($x->count()) { return 'Found'; }

$ x = Auth :: user () -> perfil-> get (); (verifique se o usuário tem um perfil depois de usar um -> get ())

[FAILS] if(count($x)) { return 'Found'; }
[GOOD] if($x->count()) { return 'Found'; }

Espero que isso possa ajudar, mesmo 5 anos após a pergunta, este post do stackoverflow me ajudou muito!

raphjutras
fonte