英文:
How to load a relationship in Laravel without foreign key?
问题
我有这个模型:
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");
它运行正常,返回如下:
{
"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);
}
}
这里的问题是我不想返回"post_id"字段,我想要得到以下内容:
{
"id": "pre_1BzVrTK8dviqA9FGxSoSnU",
"type": "document",
"filename": "v1BzVc3jJPp64e7bQo1drmL.jpeg",
"post": {
"id": "post_1BzVc3jJPp64e7bQo1drmL",
"status": "active"
....
}
....
}
我尝试了这个:
return $image->select(["id", "type", "filename"])->load("post:id,status,subject,body");
它返回一个错误:
"message": "Call to undefined method Illuminate\Database\Eloquent\Builder::load()",
我该怎么办?
英文:
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?
答案1
得分: 2
尝试使用Eloquent API资源来创建、格式化并加载与关系相关的字段,请查阅文档。
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ImageResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
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,
]
];
}
}
然后在您的控制器中调用此资源:
return new ImageResource($image);
英文:
Try use and eloquent API Resources to make and format and load fields from to relationships, check documentation
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ImageResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
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,
]
];
}
}
And call this resource in your controller
return new ImageResource($image);
答案2
得分: 2
你尝试的代码
return $image->select(["id", "type", "filename"])
->load("post:id,status,subject,body");
实际上存在多个问题,并混合了一些内容。
第一个问题是,你已经有一个图像模型,并尝试仅选择少数列,这实际上会为另一个图像创建一个新的查询。你可以在加载图像之前仅选择所需的列,例如:
$image = Image::query()->select(['id', 'type', 'filename'])->find($id);
但是,这个解决方案的问题导致了第二个问题:
当你不选择 post_id
时,由于缺少外键,Laravel 将无法加载关系。
如果查询语句是正确的,实际上会执行两个查询:
SELECT id, type, filename FROM images
SELECT id, status, subject, body FROM posts WHERE id = ?
但是由于你没有在第一个查询中包含 post_id
,它在第二个查询的参数中不可用,因此无法加载关系数据。
可能的解决方案 #1: API 资源
使用 API 资源 在返回之前转换图像。这是最佳实践,因为它允许你将内部数据结构与公共数据结构解耦。它还可以防止在内部数据结构发生更改时出现不希望的 API 更改(例如添加新列、重命名列、删除列等)。
在你的情况下,解决方案可以很简单:
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);
可能的解决方案 #2: 隐藏字段
你可以在模型中使用 $hidden
属性定义隐藏字段。这些字段不会被序列化为 JSON:
class Post extends Model
{
protected $hidden = ['private_field'];
}
或者,你可以使用 $visible
来定义一个白名单,而不是黑名单。只有 $visible
数组中的属性将被序列化:
class Post extends Model
{
protected $visible = ['id', 'status', 'subject', 'body'];
}
这个解决方案显然会影响整个应用程序,因此可能不是理想的解决方案。
可能的解决方案 #3: 临时设置 hidden/visible 属性
而不是永久性地设置 $hidden
或 $visible
,你还可以在控制器中临时设置它们:
$image->load('post');
$image->setVisible(['id', 'type', 'filename']);
$image->post->setVisible(['id', 'status', 'subject', 'body']);
return $image;
请注意,这些解决方案只是其中一些可能的方法,具体选择取决于你的需求和项目的结构。
英文:
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.
Possible solution #1: API resources
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);
Possible solution #2: Hide fields
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.
Possible solution #3: Temporarily set hidden/visible properties
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;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论