user7554217
user7554217

Reputation:

Laravel 5.6 API resource collection - Conditional relation not fetched

I'm experiencing my first Laravel project and I implemented a resource collection API, where I fetch data via passport. Data seems to be retrieved correctly from model, except for relations. Here's the situation:

item.php (Model)

<?php

// Definizione Namespace
namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Classe Item
 */
class Item extends Model
{
    use SoftDeletes;

    // Dichiarazione Proprietà
    protected $table = 'item';
    protected $dateformat = 'Y-m-d';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'data_acquisto',
        'labeled',
        'estensione_garanzia',
        'stato',
        'data_dismissione',
        'note'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'codice',
        'serial',
        'componente_id',
        'tipologia_id',
        'condizione_id',
        'locazione_id',
        'fornitore_id',
        'parent_id'
    ];

    /**
     * The attributes that should be mutated to dates.
     *
     * @var array
     */
    protected $dates = [
        'data_acquisto',
        'data_dismissione',
        'deleted_at'
    ];

    /**
     * All of the relationships to be touched.
     *
     * @var array
     */
    protected $touches = [
        'componenti',
        'condizioni',
        'fornitori',
        'locazioni',
        'tipologie'
    ];

    /**
     * Scope query item figli
     * Getter
     * @param array $query Query
     * @return array Query
     */
    public function scopeFigli($query)
    {
        return $query->where('parent_id', '!=', null);
    }

    /**
     * Componenti Correlati
     * Getter
     * @return object Componenti
     */
    public function componenti()
    {
        // Definizione relazione
        return $this->belongsTo('App\Componente');
    }

    /**
     * Condizioni Correlate
     * Getter
     * @return object Condizioni
     */
    public function condizioni()
    {
        // Definizione relazione
        return $this->belongsTo('App\Condizione');
    }

    /**
     * Fornitori Correlati
     * Getter
     * @return object Fornitori
     */
    public function fornitori()
    {
        // Definizione relazione
        return $this->belongsTo('App\Fornitore');
    }

    /**
     * Locazioni Correlate
     * Getter
     * @return object Locazioni
     */
    public function locazioni()
    {
        // Definizione relazione
        return $this->belongsTo('App\Locazione');
    }

    /**
     * Tipologie Correlate
     * Getter
     * @return object Tipologie
     */
    public function tipologie()
    {
        // Definizione relazione
        return $this->belongsTo('App\Tipologia');
    }
}

item.php (Resource)

<?php

// Definizione Namespace
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Componente as ComponenteResource;
use App\Http\Resources\Condizione as CondizioneResource;
use App\Http\Resources\Fornitore as FornitoreResource;
use App\Http\Resources\Locazione as LocazioneResource;
use App\Http\Resources\Tipologia as TipologiaResource;

class Item extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        parent::toArray($request);

        return [
            'id' => $this->id,
            'codice' => $this->codice,
            'data_acquisto' => $this->data_acqisto,
            'serial' => $this->serial,
            'labeled' => $this->labeled,
            'estensione_garanzia' => $this->estensione_garanzia,
            'stato' => $this->stato,
            'data_dismissione' => $this->data_dismissione,
            'note' => $this->note,
            'parent_id' => $this->parent_id,
            // Includi associazioni se caricate
            'componenti' => ComponenteResource::collection($this->whenLoaded('componenti')),
            'condizioni' => CondizioneResource::collection($this->whenLoaded('condizioni')),
            'fornitori' => FornitoreResource::collection($this->whenLoaded('fornitori')),
            'locazioni' => LocazioneResource::collection($this->whenLoaded('locazioni')),
            'tipologie' => TipologiaResource::collection($this->whenLoaded('tipologie'))
        ];
    }
}

This is the screen about an example of data fetched:

enter image description here

As showed above there's no trace of relations. By googling around and changing code as suggested like this:

// Resoruce - Straight including relations instead of lazy load
[...]
'componenti' => ComponenteResource::collection($this->componenti),
[...]

or by expliciting the foreign key in model:

/**
 * Componenti Correlati
 * Getter
 * @return object Componenti
 */
public function componenti()
{
    // Definizione relazione
    return $this->belongsTo('App\Componente', 'componente_id');
}

I'm still not retrieving relations. Could anyone give me a little help/tip to solve this problem?

Thanks in advance for help.

Upvotes: 2

Views: 4978

Answers (2)

Vinicius Louren&#231;o
Vinicius Louren&#231;o

Reputation: 140

The code below will only show Tipologie when it is explicitly loaded to avoid N+1 query problems.

'tipologie' => TipologiaResource::collection($this->whenLoaded('tipologia'))

To load Tipologie for Resource to show it, you need to explicitly load it as:

$itemResource = new ItemResource($item->load('tipologia', ... other relationships...);

See Eager Loading for more information about this.

Edit

Sorry for not understanding the type of relationship, just like @luca-cattide said, collection should not be used for belongsTo, and the correct one is to use:

TipologiaResource::make($this->tipologia);

Or also:

new TipologiaResource($this->topologia);

But I advise you to use "load" method to load the information before, otherwise you perform a search in the database for "item", another by "typologie" and so on until loading all your relationships.

There's another way you load information without having to load the item, see below:

new ItemResource(App\Item::find(1)->with(['tipologie', ... other relationships ... ])->get());

See more about N+1 query problems here.

Upvotes: 3

user7554217
user7554217

Reputation:

Thanks @vinicius, but googling around a bit more, as suggested from this post by @CamiloManrique, I noticed that in these relations, I'm trying to fetch data from belongs_to side (so actually from Item and not from Componente, Tipologia and so on). As is ::collection simply doesn't work except if called by hasMany relation side

So, instead using ::collection in conjunction with whenLoaded I refactored like this:

    // Includi associazioni se caricate
    'componente' => ComponenteResource::make($this->componente),
    'condizione' => CondizioneResource::make($this->condizione),
    'fornitore' => FornitoreResource::make($this->fornitore),
    'locazione' => LocazioneResource::make($this->locazione),
    'tipologia' => TipologiaResource::make($this->tipologia)

In this way data being fetched with no error.

Thanks again for your tips.

Upvotes: 2

Related Questions