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

huangapple go评论110阅读模式
英文:

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

&lt;?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&lt;string, mixed&gt;
     */
    public function toArray(Request $request): array
    {
        return [
            &#39;id&#39; =&gt; $this-&gt;id,
            &#39;type&#39; =&gt; $this-&gt;type,
            &#39;filename&#39; =&gt; $this-&gt;filename,
            &#39;post&#39; =&gt; [
                 &#39;id&#39; =&gt; $this-&gt;post-&gt;id,
                 &#39;status&#39; =&gt; $this-&gt;post-&gt;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-&gt;select([&quot;id&quot;, &quot;type&quot;, &quot;filename&quot;])
    -&gt;load(&quot;post:id,status,subject,body&quot;);

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()-&gt;select([&#39;id&#39;, &#39;type&#39;, &#39;filename&#39;])-&gt;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 [
            &#39;id&#39; =&gt; $this-&gt;id,
            &#39;type&#39; =&gt; $this-&gt;type,
            &#39;filename&#39; =&gt; $this-&gt;filename,
            &#39;post&#39; =&gt; [
                &#39;id&#39; =&gt; $this-&gt;post-&gt;id,
                &#39;status&#39; =&gt; $this-&gt;post-&gt;status,
                &#39;subject&#39; =&gt; $this-&gt;post-&gt;subject,
                &#39;body&#39; =&gt; $this-&gt;post-&gt;body,
            ]
        ];
    }
}
$image-&gt;load(&#39;post&#39;);

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 = [&#39;private_field&#39;];
}

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 = [&#39;id&#39;, &#39;status&#39;, &#39;subject&#39;, &#39;body&#39;];
}

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-&gt;load(&#39;post&#39;);

$image-&gt;setVisible([&#39;id&#39;, &#39;type&#39;, &#39;filename&#39;]);
$image-&gt;post-&gt;setVisible([&#39;id&#39;, &#39;status&#39;, &#39;subject&#39;, &#39;body&#39;]);

return $image;

huangapple
  • 本文由 发表于 2023年6月22日 02:42:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76526241.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定