rsferreira11's avatar

Is there a way to list all relationships of a model?

For example if I have

Model Post - belongsTo an User - hasMany Posts

Can I use any method to list values? Example: array [ 'belongsTo' => ['User'], 'hasMany' => ['Posts'], ]

0 likes
21 replies
pisio's avatar

$user = User::find(1);

$user->posts()->orderBy("id","desc")->get(); // it will order it by id and get new collection of all items for this user

thomaskim's avatar

I'm not getting the question. Are you trying to get all the relationships of a specific model, all relationships of all models, or something else?

Francismori7's avatar

@mikemand's solution cannot be used as the ReflectionClass cannot see what's inside the method - it will not detect "hasOne" or similar callbacks, you're better of using a regular expression on the file's content and filter it all out (complex IMHO).

You would have to list all the methods yourself in another method, not so bad if you ask me, because you always know what's in the code, nothing is "assumed", it's all there.

    public function getRelationships() {
        return [
            'belongsTo' => [
                'User',
            ],
            'hasMany' => [
                'Post',
            ],
        ];
    }
iamjaax's avatar

I'm a little late, but if you're using PHP 7, and leveraging return types, you could do this:

$user = \App\User::class;
    $reflector = new ReflectionClass($user);
    $relations = [];
    foreach ($reflector->getMethods() as $reflectionMethod) {
        $returnType = $reflectionMethod->getReturnType();
        if ($returnType) {
            if (in_array(class_basename($returnType->getName()), ['HasOne', 'HasMany', 'BelongsTo', 'BelongsToMany', 'MorphToMany', 'MorphTo'])) {
                $relations[] = $reflectionMethod;
            }
        }
    }

    dd($relations);
13 likes
MThomas's avatar

@sonoma Why are you responding to posts that have been inactive for years...

iamjaax's avatar

@MTHOMAS - My web search throw up this thread as the first result, I figured I'd provide a solution. Got a problem with that?

14 likes
Digitalized's avatar

@derp - I came to the same conclusion. Including a return type on relationships seems to be the only conceivable way of doing this.

I've seen other solutions which involve actually invoking every method on a class and checking the return type - which is a truly terrible idea!

half2me's avatar

Thanks @derp, thats actually quite a smart solution. I was using doc annotations for this so far, but realized this is a much nicer approach.

Pocciox's avatar

Thanks @derp , it worked. But i had to add the return type specification for my model relationships like this:

before (not working):

public function users()
{
    return $this->belongsToMany('App\User');
}

after(working):

public function users(): BelongsToMany
{
    return $this->belongsToMany('App\User');
}
InfoRR's avatar

@Pocciox I can't find the comment of @derp in which he explains his solution. Can you please share what actually worked for you?

1 like
neeravp's avatar

@infoRR Make sure to add a return type to the methods. Then you can use a bit of reflection magic along with Collection.

Say you define the following method in a trait or an abstract base model

public static function definedRelations(): array
{
      $reflector = new \ReflectionClass(get_called_class());

      return collect($reflector->getMethods())
          ->filter(
              fn($method) => !empty($method->getReturnType()) &&
                  str_contains(
                      $method->getReturnType(), 
                      'Illuminate\Database\Eloquent\Relations'
                 )
          )
          ->pluck('name')
          ->all();
 }

Then you can get the defined method names as an array for eg: \App\User::definedRelations() will give the names of all relations defined for the App\User model

6 likes
secondman's avatar

@neeravp

This is LEGEND! One of my client's apps does a lot of copying of models and one of the main ones has 17 relationships, and then all those have relationships, etc.

Thanks so much for this, very elegant.

neeravp's avatar

@secondman Glad that it was helpful to you. I personally use this in a few projects. PHP Reflection is awesome. And Laravel Collection is way ahead of times & can't thank Taylor Otwell enough for this.

saulens22's avatar

@neeravp I needed to get actual classes, so I added additional map to your great solution. Here it is in case someone needs to get all relationship classes:

public static function listAllRelatedClasses(): array
    {
        $reflector = new \ReflectionClass(get_called_class());

        return collect($reflector->getMethods())
            ->filter(
                fn ($method) => !empty($method->getReturnType()) &&
                    str_contains(
                        $method->getReturnType(),
                        'Illuminate\Database\Eloquent\Relations'
                    )
            )
            ->map(
                function ($method) {
                    return (with(new (get_called_class()))->{$method->name}()->getRelated())::class;
                }
            )
            ->unique()->values()->all();
    }
2 likes
timkerr222's avatar

Here is my method. It depends on the models properly including an Illuminate return type for the relationship method:

protected function hasRelationships($model): array
    {
        $reflectionClass = new ReflectionClass($model);
        $reflectionMethods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
        $relationshipMethods = [];

        foreach ($reflectionMethods as $reflectionMethod) {
            $methodName = $reflectionMethod->getName();
            $methodReturnType = $reflectionMethod->getReturnType();

            if ($methodReturnType !== null) {
                $methodReturnTypeString = $methodReturnType->getName();
                
                $shortname = explode("\", $methodReturnTypeString);
                $relationshipClass=strtolower(end($shortname));
                $relationshipHelpers = [
                    'belongsto', 'hasone', 'hasmany', 'belongstomany',
                    'hasmanythrough', 'morphone', 'morphone', 'morphmany',
                    'morphto', 'morphtomany', 'through', 'embedsone',
                    'embedsmany', 'embedsmany', 'embedsmany'
                ];
    
                    if(in_array($relationshipClass, $relationshipHelpers))
                        $relationshipMethods[] = $methodName;

                 
            }
        }

        return $relationshipMethods;
    }
2 likes

Please or to participate in this conversation.