Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

davepgreene's avatar

Get all model relationships

Hi.

Is there a way to retrieve all of a model's relationships short of parsing columns in every table to see if _id exists?

Ideally I'd like an array with the model name (with namespace) and the type of relationship.

Possible?

0 likes
7 replies
phildawson's avatar
Level 26

Possible?

Challenge accepted.

@davepgreene Here's something I've just cooked up.

<?php

namespace App;

use ErrorException;
use Illuminate\Database\Eloquent\Relations\Relation;
use ReflectionClass;
use ReflectionMethod;

trait RelationshipsTrait
{
    public function relationships() {

        $model = new static;

        $relationships = [];

        foreach((new ReflectionClass($model))->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
        {
            if ($method->class != get_class($model) ||
                !empty($method->getParameters()) ||
                $method->getName() == __FUNCTION__) {
                continue;
            }

            try {
                $return = $method->invoke($model);

                if ($return instanceof Relation) {
                    $relationships[$method->getName()] = [
                        'type' => (new ReflectionClass($return))->getShortName(),
                        'model' => (new ReflectionClass($return->getRelated()))->getName()
                    ];
                }
            } catch(ErrorException $e) {}
        }

        return $relationships;
    }
}

You should get an array of arrays, just add the trait to any models.

class Article extends Model
{
    use RelationshipsTrait;

    ...
}

$article = new Article;
dd($article->relationships());
  "example" => array:2 [▼
    "type" => "BelongsTo"
    "model" => "App\Example"
  ],
  "gallery" => array:2 [▼
    "type" => "MorphMany"
    "model" => "App\Gallery"
  ]
17 likes
hxss's avatar

@phildawson good solution...BUT! You invoke every method in model. I think this is can be dangerous(some methods can modify data) and slowly.

You need to narrow your search. I see two ways:

  1. Using template in naming of relations(like lnkPosts, but it's ugly)
  2. Using final prefix with all relations methods(ONLY with relations). This will limit the inheritance of these methods, but is hardly ever useful inheritance of relations.

So there is code:

<?php

namespace App\Models;

use ErrorException;
use Illuminate\Database\Eloquent\Relations\Relation;
use ReflectionClass;
use ReflectionMethod;
use Illuminate\Support\Collection;

class Relationship
{
    public $name;
    public $type;
    public $model;
    public $foreignKey;
    public $ownerKey;

    public function __construct($relationship = [])
    {
        if ($relationship)
        {
            $this->name = $relationship['name'];
            $this->type = $relationship['type'];
            $this->model = $relationship['model'];
            $this->foreignKey = $relationship['foreignKey'];
            $this->ownerKey = $relationship['ownerKey'];
        }
    }
}

class Relationships
{
    private $model;
    private $relationships;

    public function __construct($model) {
        $this->model = $model;
        $this->relationships = null;
    }

    public function __invoke($key = false)
    {
        if (!$this->relationships)
            $this->all();

        if ($key)
            return $this->byKey($key);
        else
            return $this->relationships;
    }

    public function all() {

        $this->relationships = new Collection;

        foreach((new ReflectionClass($this->model))->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
        {
            if ($method->class == get_class($this->model)
                && empty($method->getParameters())
                && $method->getName() !== __FUNCTION__
                && $method->isFinal())
            {
                try {
                    $return = $method->invoke($this->model);

                    if ($return instanceof Relation)
                    {
                        $ownerKey = null;
                        if ((new ReflectionClass($return))->hasMethod('getOwnerKey'))
                            $ownerKey = $return->getOwnerKey();
                        else
                        {
                            $segments = explode('.', $return->getQualifiedParentKeyName());
                            $ownerKey = $segments[count($segments) - 1];
                        }

                        $rel = new Relationship([
                            'name' => $method->getName(),
                            'type' => (new ReflectionClass($return))->getShortName(),
                            'model' => (new ReflectionClass($return->getRelated()))->getName(),
                            'foreignKey' => (new ReflectionClass($return))->hasMethod('getForeignKey')
                                ? $return->getForeignKey()
                                : $return->getForeignKeyName(),
                            'ownerKey' => $ownerKey,
                        ]);

                        $this->relationships[$rel->name] = $rel;
                    }
                } catch(ErrorException $e) {}
            }
        }

        return $this->relationships;
    }

    public function byKey($key)
    {
        $relationships = new Collection;

        foreach ($this->relationships as $name => $relationship)
            if ($relationship->type == 'BelongsTo'
                && $relationship->foreignKey == $key)
                $relationships[$name] = $relationship;

        return $relationships;
    }
}
2 likes
tombenevides's avatar

Hi.. These solutions give an array with the information of all relations, right?

And the opposite? Can I define an array of information and use to define the relation functions that Eloquent uses?

Ex: based on that array of information, I want use the following code (or something like that):

Controller

$data = ModelUse::where('id',$id)
    ->with('Relation01')
    ->get();

Model

//USE THIS ARRAY
$relationships = [
    'Relation01' => [
        'type' => 'hasOne',
        'fkey' => 'foreign_key',
        'lkey' => 'local_key',
    ],
    'Relation02' => [
        'type' => 'hasMany',
        'fkey' => 'foreign_key',
        'lkey' => 'local_key',
    ]
];


//INSTEAD OF USING THIS:
public function Relation01(){
    return $this->hasOne('Relation01::class, 'foreign_key', 'local_key');
};
damms005's avatar

@HXSS - You said ".BUT! You invoke every method in model..."; and I guess you were referring to the line where he did $return = $method->invoke(model). However, in your code, you also did $return = $method->invoke($this->model);.

So how does your own code now prevent "invoking every method"?

1 like
timucinbahsi@gmail.com's avatar

I think they meant invoking not only relationship methods. Their solution is to mark relationships as final so that they can invoke only them.

Please or to participate in this conversation.