Laravel 5.7. I have a model Audio, with the fields id and title. An Audio can have many AudioVersions, where AudioVersion has id, audio_id (referring to Audio) and url.
Now, I have two parent models, Foo and Bar, which can have many Audio models.
Audio:
class Audio extends Model
{
public function versions()
{
return $this->hasMany('App\AudioVersion', 'audio_id');
}
}
AudioVersion:
class AudioVersion extends Model
{
public function audio()
{
return $this->belongsTo('App\AudioContent');
}
}
Foo:
class Foo extends Model
{
public function audioContents()
{
return $this->morphToMany('App\Audio', 'audio_contentable', 'audio_contentable');
}
}
I have an Eloquent API resource, FooResource, which returns its Audio objects:
FooResource:
class FooResource extends JsonResource
{
public function toArray($request)
{
return [
'audio' => AudioResource::collection($this->audioContents),
];
}
}
AudioResource:
class AudioResource extends JsonResource
{
public function toArray($request)
{
return [
'urls' => $this->versions,
];
}
}
My problem is that in the audio key of my FooResource I only want to return Audios which have AudioVersions related to them. I.e. if I have an Audio with no AudioVersions, I do not want that Audio included in the Foo's audio key. I can't find a way to do this deep conditional logic in Eloquent / Resources.
In the FooResource class, you could filter() the collection before passing it to the collection method.
class FooResource extends JsonResource
{
public function toArray($request)
{
$audioContents = $this->audioContents()->filter(function($audio, $key) {
return $audio->versions->count();
}
return [
'audio' => AudioResource::collection($audioContents),
];
}
}
You need to do something like this. This is the example, you only need to add join inside with so that only those audios will be fetched which have audio versions.
$audioContents = AudioContents::with([
'audio' => function ($query) use ($SpecificID) {
return $query->join("audio_versions")
->on("audio_versions.audio_id", "=", "audios.id");
}
])->get();
Try this and let me know if you are facing any issue.
In the end I added a scope to Audio:
public function scopeHasVersions($query)
{
return $query->whereHas('versions');
}
Then in FooResource:
return [
'audio' => AudioResource::collection($this->audioContents()->hasVersions()->get()),
];
Related
We are currently developing a feature in codotto.com where a user can comment on an IT meetup. Each comment can have an answer to it. We are only allowing for one-level deep answers, so something like:
- Comment 1
- Answer to comment 1
- Answer to comment 1
- Comment 2
- Answer to comment 2
- Answer to comment 2
I have the following database structure:
// meetup_messages
- id
- user_id
- meetup_id
- meetup_message_id (nullable) -> comments that do not answer will have this set to nullable
In my model I define the answers as a HasMany relationship:
class MeetupMessage extends Model
{
// ...
public function answers(): HasMany
{
return $this->hasMany(self::class, 'meetup_message_id');
}
}
Then on my controller, I get all comments that do not have answers:
public function index(
IndexMeetupMessageRequest $request,
Meetup $meetup,
MeetupMessageService $meetupMessageService
): MeetupMessageCollection
{
$meetupMessages = MeetupMessage::with([
'user',
// 'answers' => function ($query) {
// $query->limit(3);
// }
'answers'
])
->whereNull('meetup_message_id')
->whereMeetupId($meetup->id)
->paginate();
return new MeetupMessageCollection($meetupMessages);
}
Then on my MeetupMessageCollection:
class MeetupMessageCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
}
Then on my MeetupMessageResource:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;
class MeetupMessageResource extends JsonResource
{
public function toArray($request)
{
return collect([
// 'answers' => new MeetupMessageCollection($this->whenLoaded('answers')),
])
->when(
is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
function (Collection $collection) {
$collection->put('answers', MeetupMessageCollection::collection($this->answers));
}
);
}
}
But I get the following error: Call to undefined method App\\Models\\Meetup\\MeetupMessage::mapInto(). How can I still use MeetupMessageCollection by passing the answers to it?
As #matialauriti pointed out, you cant use resource collections inside collections in Laravel
class MeetupMessageResource extends JsonResource
{
public function toArray()
{
return [
'answers' => new MeetupMessageCollction($this->answers) // ❌ You can't do this
]
}
}
My solution was to pull my resource formation to a private method and re-use it if answers is present:
class MeetupMessageResource extends JsonResource
{
public function toArray($request)
{
return collect($this->messageToArray($this->resource))
->when($this->relationLoaded('user'), function (Collection $collection) {
$collection->put('user', $this->userToArray($this->user));
})
// ✅ Now I don't need to use Resources inside my API Resource class
->when(
is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
function (Collection $collection) {
$answers = $this
->answers
->map(function (MeetupMessage $answer) {
return array_merge(
$this->messageToArray($answer),
['user' => $this->userToArray($answer->user)]
);
});
$collection->put('answers', $answers);
}
);
}
private function messageToArray(MeetupMessage $meetupMessage): array
{
return [
'id' => $meetupMessage->id,
'message' => Purify::config(MeetupMessageService::CONFIG_PURIFY)->clean($meetupMessage->message),
'answersCount' => $this->whenCounted('answers'),
'createdAt' => $meetupMessage->created_at,
];
}
}
I want to show pivot data into json format
I have 2 relationship many to many mode: SubmitForm and Fisherman.
// submitform model
public function fisherman()
{
return $this->belongsToMany(Fisherman::class, 'submitform_fisherman','submitform_id', 'fisherman_id');
}
// fisherman model
public function submitForm()
{
return $this->belongsToMany(SubmitForm::class,'submitform_fisherman','fisherman_id', 'submitform_id');
}
this is my submitformcontroller to show the data
use App\Http\Resources\SubmitformResource;
use App\SubmitForm;
class SubmitFormController extends Controller
{
public function index()
{
return SubmitformResource::collection(SubmitForm::with('fisherman')->get());
}
....
}
this is my SubmitformResource
class SubmitformResource extends JsonResource
{
public function toArray($request){
return [
"id" => $this->id,
"product_form" => $this->product_form,
"fisherman" => $this->whenPivotLoaded('submitform_fisherman', function(){
return new FishermanResource($this->pivot->fisherman);
})
}
}
when I tried i dont get fisherman data.
Can someone help me?
I'm trying to make an api that have lists and inside each list there is anther list inside of it called cards and the cards list is the cards of this list.
I tried to show it in index function and didn't work it was like this:
public function index()
{
// $list = List -> cards();
$list = List::cards();
return response( $list );
}
Card Model:
public function list()
{
return $this->belongsTo( List::class() );
}
Card Model:
public function cards()
{
return $this->hasMany( Card::class() );
}
What i want to output is json data like this:
"lists":[
'name':listname
'cards':[
'card one': card name,
]
]
If you use Laravel framework use Resource for response, in Resource of laravel you can load cards. For example in ListController :
public function index()
{
return ListResource::collection(List::all()->paginate());
}
And in ListResource :
public function toArray($request)
{
'cards' => CardResource::collection('cards');
}
belongsTo or hasMany accepts model name as a first argument. In your case you need to pass your model class name in your relations methods.
public function list()
{
return $this->belongsTo(List::class);
}
and
public function cards()
{
return $this->hasMany(Card::class);
}
So if you want to receive models including relations you can use with method.
return response(List::query()->with('cards'));
You can use resources.
Http\Resources\List:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class List extends JsonResource
{
public function toArray($request)
{
$cards = [];
foreach ($this->cards as $card) {
$cards[] = $card->name;
}
return [
'name' => $this->name,
'cards' => $cards,
];
}
}
Http\Controllers\ListController:
namespacce App\Http\Controllers;
use App\Http\Resources\List as ListResource;
use App\Components\List;
class ListController extends Controller
{
$lists = List::query()->get();
return ListResource::collection($lists)->response();
}
I'm trying to pass my article data to the single page article named article.blade.php although all the data are recorded into the database but when I tried to return them in my view, nothing showed and the [ ] was empty. Nothing returned.
this is my articleController.php
<?php
namespace App\Http\Controllers;
use App\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
public function single(Article $article)
{
return $article;
}
}
this is my model:
<?php
namespace App;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use Sluggable;
protected $guarded = [];
protected $casts = [
'images' => 'array'
];
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
public function path()
{
return "/articles/$this->slug";
}
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
and this is my Route
Route::get('/articles/{articleSlug}' , 'ArticleController#single');
Change your code to
class ArticleController extends Controller
{
public function single(Article $article)
{
return view('article', compact('article'));
}
}
change route to
Route::get('/articles/{article}' , 'ArticleController#single');
And model
public function getRouteKeyName()
{
return 'slug';
}
See docs https://laravel.com/docs/5.7/routing#route-model-binding
You might not be getting any data because you have not specified that you're using title_slug as the route key for model binding in your model.
Add this to your model class and it should give you the data
public function getRouteKeyName()
{
return 'slug';
}
Then you can return the data in json, view or other format.
Depending on what you try to archive, you need to either ...
return $article->toJson(); // or ->toArray();
.. for json response or ..
return view(..., ['article' => $article])
for passing a the article to a certain view
How can I eager load a resource collection relationship? I've made a resource which calls gravel_pits relationship
class GravelTypeResource extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'deleted_at' => $this->deleted_at,
'gravel_pits' => $this->gravel_pits,
];
}
}
On the model M:M relationship:
public function gravel_pits()
{
return $this->belongsToMany('App\GravelPit');
}
And from the API I am getting it back like this:
public function index()
{
return GravelTypeResource::collection(GravelType::all());
}
I can eager load it by doing
public function index()
{
return GravelTypeResource::collection(GravelType::with('gravel_pits'));
}
which works...but I can't control then what properties of gravel pits I actually want back, instead, eager load fetches them all. Is there a simple workaround to this?
you can use Resource Collections
GravelTypeResourceCollection::make($collection);
and since you can use load and loadMissing on eloquent collections you can do this
class GravelTypeResourceCollection extends ResourceCollection
{
$collects = GravelTypeResource::class;
public function __construct($resource){
$resource->loadMissing(['gravel_pits']);
parent::__construct($resource);
}
}
You can pass in a select to get just the fields you want. Just make sure you get the fields that the relationship is based on:
return GravelTypeResource::collection(GravelType::with('gravel_pits'=>function($query) {
$query->select(['id', 'gravel_type_id', 'column3', 'column4']);
});