如何将一个 PHP 父对象转换/强制转换为子对象?

3

原因

我有一个包含slug的遗留系统表。

当这些记录匹配时,表示某种具有布局ID的页面。

因为这些页面可能具有不同的资源需求,所以取决于布局ID可以与哪些表连接。

我使用Laravel的Eloquent模型。

我想要的是拥有特定布局关系的子模型。

class Page extends Model {
    // relation 1, 2, 3 that are always present
}

class ArticlePage extends Page {
    // relation 4 and 5, that are only present on an ArticlePage
}

然而在我的控制器中,为了知道我需要哪种布局,我已经必须查询:

网址: /{slug}

$page = Slug::where('slug', $slug)->page;

if ($page->layout_id === 6) {
    //transform $page (Page) to an ArticlePage
}

因此,我得到了一个页面的实例,但我想将其转换或强制转换为ArticlePage的实例,以加载它的其他关系。我该如何实现这一点?

你可能想看一下多态关联:https://laravel.com/docs/5.6/eloquent-relationships#polymorphic-relations - Lars Mertens
2个回答

4
你需要查看 Laravel 中的多态关联来实现这一点。多态关联将允许你根据字段的类型检索不同的模型。在你的 Slug 模型中,你需要像这样做:
public function page()
{
    return $this->morphTo('page', 'layout_id', 'id');
}

在您的某个服务提供者中,例如 AppServiceProvider,您需要提供一个 Morph Map 来告诉 Laravel 将某些 ID 映射到特定的模型类。例如:
Relation::morphMap([
    1 => Page::class,
    // ...
    6 => ArticlePage::class,
]);

现在,每当您使用page关系时,Laravel将检查类型并将正确的模型返回给您。
注意:我对参数等不是100%确定,也没有测试过,但您应该能够从文档中找到答案。
如果您的layout_id位于Page模型上,我唯一看到的解决方案是向您的Page模型添加一个方法,该方法能够根据其layout_id属性将现有页面转换为ArticlePage或其他页面类型。您应该可以尝试类似以下的内容:
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Page extends Model
{
    const LAYOUT_ARTICLE = 6;

    protected $layoutMappings = [
        // Add your other mappings here
        self::LAYOUT_ARTICLE => ArticlePage::class
    ];

    public function toLayoutPage()
    {
        $class = $this->layoutMappings[$this->layout_id];

        if (class_exists($class)) {
            return (new $class())
                ->newInstance([], true)
                ->setRawAttributes($this->getAttributes());
        }

        throw new \Exception('Invalid layout.');
    }
}

这段代码会根据你的 layout_id 属性查找映射,然后创建一个新的正确类型的类,并将其属性填充为您正在创建的页面的属性。如果您查看 Laravel 的 Illuminate\Database\Eloquent::newFromBuilder() 方法,可以了解其运作方式以及我如何获得上面的代码。您应该可以像这样直接使用它:
$page = Slug::where('slug', $slug)
            ->first()
            ->page
            ->toLayoutPage();

那会给你一个 ArticlePage 的实例。

2
这是一个优雅的解决方案。当然,如果你对多态关系感兴趣的话 :) - Mkk
有点晚了,但这个解决方案唯一的缺陷(对我来说)是 layoutID 在页面模型本身上,而不在 Slug 上。 - online Thomas
@ThomasMoors 请查看我的更新答案,希望如果多态关系不适合您的需求,这将对您有所帮助。 - Jonathon

0
据我所知,这方面没有内置函数。 但是你可以像这样做。
  $page = Slug::where('slug', $slug)->page;

  if ($page->layout_id === 6) {
      $page = ArticlePage::fromPage($page);
  }

然后在ArticlePage上创建静态方法
public static function fromPage(Page $page)
{
   $articlePage = new self();

   foreach($page->getAttributes() as $key => $attribute) {
       $articlePage->{$key} = $attribute;
   }

   return $articlePage
}

根据您的使用情况,可能最好创建一个静态方法,在Slug的关系页面上自动执行此操作。

$page->getAttributes() 可以被 $page->toArray() 替代。 这样可以利用访问器和类型转换。例如,如果您的属性被转换为 json,则使用 getAttributes 会将该值作为字符串获取,从而破坏数据类型。 - Hlorofos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接