Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I transform / cast a PHP parent object to a child object?

Reason

I got a legacy system with a table containing slugs.

When those records match, it represents some kind of page with a layout ID.

Because these pages can have different resource needs it depends on the layout ID which tables can be joined with.

I use Laravel's Eloquent models.

What I would like is to have a child model that holds the layout specific relations.

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
}

However in my controller, in order to know which layout I need, I already have to query:

url: /{slug}

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

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

Because of this I get an instance of Page, but I would like to transform or cast it to an instance of ArticlePage to load it's additional relations. How can I achieve this?

like image 356
online Thomas Avatar asked Nov 29 '25 15:11

online Thomas


1 Answers

You'll need to look into Polymorphic relations in Laravel to achieve this. A Polymorphic Relation would allow you to retrieve a different model based on the type of field it is. In your Slug model you would need something like this:

public function page()
{
    return $this->morphTo('page', 'layout_id', 'id');
}

In one of your service providers, e.g. AppServiceProvider you would need to provide a Morph Map to tell Laravel to map certain IDs to certain model classes. For example:

Relation::morphMap([
    1 => Page::class,
    // ...
    6 => ArticlePage::class,
]);

Now, whenever you use the page relation, Laravel will check the type and give you the correct model back.

Note: I'm not 100% sure on the parameters etc. and I haven't tested but you should be able to work it out from the docs.


If your layout_id is on the Page model, the only solution I see is to add a method to your Page model that is able to convert your existing page into an ArticlePage, or other page type, based on its layout_id property. You should be able to try something like this:

<?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.');
    }
}

What this does is look for a mapping based on your layout_id property, and then it creates a new class of the correct type, filling its attributes with those from the page you're creating from. This should be all you need, if you take a look at Laravel's Illuminate\Database\Eloquent::newFromBuilder() method, which Laravel calls when it creates new model instances, you can see what's going on and how I've gotten the code above. You should be able to just use it like this:

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

That will give you an instance of ArticlePage

like image 166
Jonathon Avatar answered Dec 02 '25 04:12

Jonathon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!