I have this model:
App\Controllers\ImageController.php
.....
$image = new Image;
$image->id = "pre_" . (string) Str::ulid()->toBase58();
$image->filename = $filename
$image->post_id= $post_id;
$image->save();
return $image->load("post:id,status,subject,body");
it is working fine, it returns the following:
{
"id": "pre_1BzVrTK8dviqA9FGxSoSnU",
"type": "document",
"filename": "v1BzVc3jJPp64e7bQo1drmL.jpeg",
"post_id": "post_1BzVc3jJPp64e7bQo1drmL",
"post": {
"id": "post_1BzVc3jJPp64e7bQo1drmL",
"status": "active"
....
}
....
}
App\Models\Image.php
class Image extends Model
{
use HasFactory;
public $incrementing = false;
public function post()
{
return $this->belongsTo(Post::class);
}
}
The issue here is that I don't want it returns the "post_id" field, I would like to get the following:
{
"id": "pre_1BzVrTK8dviqA9FGxSoSnU",
"type": "document",
"filename": "v1BzVc3jJPp64e7bQo1drmL.jpeg",
"post": {
"id": "post_1BzVc3jJPp64e7bQo1drmL",
"status": "active"
....
}
....
}
I tried this:
return $image->select(["id", "type", "filename"])->load("post:id,status,subject,body");
and it returns an error:
"message": "Call to undefined method Illuminate\Database\Eloquent\Builder::load()",
What can I do?
The code you've tried
return $image->select(["id", "type", "filename"])
->load("post:id,status,subject,body");
has actually multiple issues and is mixing a few things up.
The first issue is that you are already have an image model and try to select only a few columns, which will actually create a new query for another image. What you can do instead is select only the required columns before loading the image, e.g.:
$image = Image::query()->select(['id', 'type', 'filename'])->find($id);
The problem with this solution leads us to the second problem though:
When you don't select the post_id
, Laravel won't be able to load the relationship due to the missing foreign key.
If the query statement was correct, it would actually execute two queries:
SELECT id, type, filename FROM images
SELECT id, status, suject, body FROM posts WHERE id = ?
But since you've not included the post_id
in the first query, it is not available as parameter for the second query and it fails to load the relationship data.
Use an API resource to transform the image before returning it. This is best practice as it allows you to decouple the internal data structure from the public data structure. It will also prevent unwanted API changes when the internal data structure changes (new column is added, a column is renamed, a column is deleted, ...).
In your case, the solution could be as simple as:
class ImageResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'type' => $this->type,
'filename' => $this->filename,
'post' => [
'id' => $this->post->id,
'status' => $this->post->status,
'subject' => $this->post->subject,
'body' => $this->post->body,
]
];
}
}
$image->load('post');
return new ImageResource($image);
You can define hidden fields in your models using the $hidden
property. These fields will not be serialized to JSON:
class Post extends Model
{
protected $hidden = ['private_field'];
}
Alternatively, you can use $visible
to define a whitelist instead of a blacklist. Only the properties in the $visible
array will be serialized:
class Post extends Model
{
protected $visible = ['id', 'status', 'subject', 'body'];
}
This solution will obviously affect the entire application and may therefore be not ideal.
Instead of permanently setting $hidden
or $visible
, you can also temporarily set them in your controller:
$image->load('post');
$image->setVisible(['id', 'type', 'filename']);
$image->post->setVisible(['id', 'status', 'subject', 'body']);
return $image;