如何在Laravel中加载没有外键的关系?

inkz8wg9  于 2023-06-24  发布在  其他
关注(0)|答案(2)|浏览(138)

我有这个模型:
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”:“调用未定义的方法Illuminate\Database\Eloquent\Builder::load()",
我该怎么办?

mnowg1ta

mnowg1ta1#

你试过的代码

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, suject, body FROM posts WHERE id = ?

但是,由于在第一个查询中没有包含post_id,因此它不能作为第二个查询的参数使用,并且无法加载关系数据。

解决方案1:API资源

使用API resource转换图像,然后再返回。这是最佳实践,因为它允许您将内部数据结构与公共数据结构解耦。它还将防止内部数据结构发生变化时(添加新列、重命名列、删除列等)发生不必要的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,而不是永久设置它们:

$image->load('post');

$image->setVisible(['id', 'type', 'filename']);
$image->post->setVisible(['id', 'status', 'subject', 'body']);

return $image;
cunj1qz1

cunj1qz12#

尝试使用和雄辩的API资源,使和格式和加载字段从关系,检查文档

<?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,
             ]
        ];
    }
}

并在控制器中调用此资源

return new ImageResource($image);

相关问题