38

I'm trying to get an array of all of my model's associations. I have the following model:

class Article extends Eloquent 
{
    protected $guarded = array();

    public static $rules = array();

    public function author() 
    {
        return $this->belongsTo('Author');
    }

    public function category() 
    {
        return $this->belongsTo('Category');
    }
}

From this model, I'm trying to get the following array of its relations:

array(
    'author',
    'category'
)

I'm looking for a way to pull this array out from the model automatically.

I've found this definition of a relationsToArray method on an Eloquent model, which appears to return an array of the model's relations. It seems to use the $this->relations attribute of the Eloquent model. However, this method returns an empty array, and the relations attribute is an empty array, despite having my relations set up correctly.

What is $this->relations used for if not to store model relations? Is there any way that I can get an array of my model's relations automatically?

Davit Zeynalyan
  • 8,418
  • 5
  • 30
  • 55
tprsn
  • 757
  • 2
  • 7
  • 18
  • I found more helpful discussions here: https://laracasts.com/discuss/channels/eloquent/is-there-a-way-to-list-all-relationships-of-a-model?page=1#reply=113736 and here: https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships – Ryan Nov 09 '18 at 23:14
  • This approach using phpDoc seems interesting: https://stackoverflow.com/a/27742504/470749 – Ryan Nov 13 '18 at 20:03

4 Answers4

59

It's not possible because relationships are loaded only when requested either by using with (for eager loading) or using relationship public method defined in the model, for example, if a Author model is created with following relationship

public function articles() {
    return $this->hasMany('Article');
}

When you call this method like:

$author = Author::find(1);
$author->articles; // <-- this will load related article models as a collection

Also, as I said with, when you use something like this:

$article = Article::with('author')->get(1);

In this case, the first article (with id 1) will be loaded with it's related model Author and you can use

$article->author->name; // to access the name field from related/loaded author model

So, it's not possible to get the relations magically without using appropriate method for loading of relationships but once you load the relationship (related models) then you may use something like this to get the relations:

$article = Article::with(['category', 'author'])->first();
$article->getRelations(); // get all the related models
$article->getRelation('author'); // to get only related author model

To convert them to an array you may use toArray() method like:

dd($article->getRelations()->toArray()); // dump and die as array

The relationsToArray() method works on a model which is loaded with it's related models. This method converts related models to array form where toArray() method converts all the data of a model (with relationship) to array, here is the source code:

public function toArray()
{
     $attributes = $this->attributesToArray();

     return array_merge($attributes, $this->relationsToArray());
}

It merges model attributes and it's related model's attributes after converting to array then returns it.

The Alpha
  • 143,660
  • 29
  • 287
  • 307
  • I NEED SOME HELP. ``` Word::where("word", "love") ->with("senses") ->get() ->pluck('senses') ->toArray(); ``` In this example above, provided that each Word belongToMany Sense and that each Sense belongToMany synonyms. How can I retrieve the synonyms with the senses? – Monero Jeanniton Aug 12 '17 at 23:52
  • 1
    `Word::where("word", "love") ->with("senses. synonyms") ->get() ->pluck('senses. synonyms') ->toArray()` – The Alpha Aug 13 '17 at 15:48
  • Wow i never knew about this call `getRelations()` – Shreyansh Panchal Jan 07 '20 at 11:32
9

use this:

class Article extends Eloquent 
{
    protected $guarded = array();

    public static $rules = array();

    public $relationships = array('Author', 'Category');

    public function author() {
        return $this->belongsTo('Author');
    }

    public function category() {
        return $this->belongsTo('Category');
    }
}

So outside the class you can do something like this:

public function articleWithAllRelationships()
{
    $article = new Article;
    $relationships = $article->relationships;
    $article = $article->with($relationships)->first();
}
  • 4
    I know this is an old question, but it's still unresolved. The accepted answer doesn't really solve the problem. This one does, only thing I would change is make it a private variable and use getters and setters. It's a good example as well where you are trying to solve something within a framework where plain old PHP just does the trick. – Kerel Apr 25 '18 at 06:56
  • 2
    The down side of this approach is that it requires the `$relationships` to be kept in sync with the relationship definitions themselves. It is definitely a good and simple approach though. – GuruBob Jan 29 '21 at 22:51
1

GruBhub, thank you very much for your comments. I have corrected the typo that you mentioned.

You are right, it is dangerous to run unknown methods, hence I added a rollback after such execution.

Many thanks also to phildawson from laracasts, https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships

You can use the following trait:

<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Relations\Relation;

trait EloquentRelationshipTrait
{
/**
 * Get eloquent relationships
 *
 * @return array
 */
public static function getRelationships()
{
    $instance = new static;

    // Get public methods declared without parameters and non inherited
    $class = get_class($instance);
    $allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC);
    $methods = array_filter(
        $allMethods,
        function ($method) use ($class) {
            return $method->class === $class
                   && !$method->getParameters()                  // relationships have no parameters
                   && $method->getName() !== 'getRelationships'; // prevent infinite recursion
        }
    );

    \DB::beginTransaction();

    $relations = [];
    foreach ($methods as $method) {
        try {
            $methodName = $method->getName();
            $methodReturn = $instance->$methodName();
            if (!$methodReturn instanceof Relation) {
                continue;
            }
        } catch (\Throwable $th) {
            continue;
        }

        $type = (new \ReflectionClass($methodReturn))->getShortName();
        $model = get_class($methodReturn->getRelated());
        $relations[$methodName] = [$type, $model];
    }

    \DB::rollBack();

    return $relations;
}

}

Then you can implement it in any model.

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use App\Traits\EloquentRelationshipTrait;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens, EloquentRelationshipTrait;

Finally with (new User)->getRelationships() or User::getRelationships() you will get:

[
 "notifications" => [
   "MorphMany",
   "Illuminate\Notifications\DatabaseNotification",
 ],
 "readNotifications" => [
   "MorphMany",
   "Illuminate\Notifications\DatabaseNotification",
 ],
 "unreadNotifications" => [
   "MorphMany",
   "Illuminate\Notifications\DatabaseNotification",
 ],
 "clients" => [
   "HasMany",
   "Laravel\Passport\Client",
 ],
 "tokens" => [
   "HasMany",
   "Laravel\Passport\Token",
 ],
]
Pablo Merener
  • 211
  • 2
  • 6
  • 3
    Hi - I like where you are going with this but there is a real danger here. I see that first you get all the methods that don't take parameters (as relationships don't) and then when you are checking the class type of the object that is returned by invoking the method the code inside that method will actually run (with no parameters as per the first filter). This is dangerous if the code inside is not a relationship definition as it will be executed. – GuruBob Jan 29 '21 at 22:45
  • Also there is a typo ... the `$instance` should be `$model`. – GuruBob Jan 29 '21 at 22:46
  • I can't think of any way around this, other than using reflection to read the source and filter by the actual content of the source, which is fine for simple definitions (e.g. you can just look for keywords like `hasMany`, `belongTo` etc.) but won't work for anything non-trivial. – GuruBob Jan 29 '21 at 22:49
1

I have published a package in order to get all eloquent relationships from a model. Such package contains the helper "rel" to do so.

enter image description here

Just run (Composer 2.x is required!):

require pablo-merener/eloquent-relationships

If you are on laravel 9, you are able to run artisan command model:show

enter image description here

Pablo Merener
  • 211
  • 2
  • 6