2

Suppose I have an of mysql table names and a row id:

['table_name' => 'things', 'row_id' => 11],
['table_name' => 'stuff', 'row_id' => 5]

Each of these tables has an Eloquent model referencing the table. How can I iterate the list and dynamically use the Model to create a new instance from the table_name key and find the row based on the row_id key?

I can get the data I need through a Builder instance but I really need to construct a Model instance. This is because I am implementing a contract through the Eloquent model and looking for a specific attribute from that contract. I can't get the attribute through the Builder instance.

(The package that I am using: https://github.com/jarektkaczyk/revisionable. I am implementing Revisionable on the models)

To clarify a bit, this works:

dd(\App\Models\Thing::find(11)->latestRevision); // returns Revision model

While this does not:

// foreach($rows as $row)
$model = new Dynamic([])->setTable($row['table_name']);
dd($model->find($row)); // returns model with correct data
dd($model->find($row['row_id'])->latestRevision); // returns null
// endforeach

Please let me know if this is not clear enough.

EDIT:

dd($model->find($row)); // returns model with correct data but shows table as `null` as if it isn't being persisted across the request.

Also, here is the Dynamic model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Sofa\Revisionable\Laravel\RevisionableTrait;
use Sofa\Revisionable\Revisionable;

class Dynamic extends Model implements Revisionable
{
    use RevisionableTrait;

    /**
     * @param $table
     */
    public function __construct($attributes = [])
    {
        parent::__construct($attributes);
    }

    /**
     * Dynamically set a model's table.
     *
     * @param  $table
     * @return void
     */
    public function setTable($table)
    {
        $this->table = $table;

        return $this;
    }
}
Jared Eitnier
  • 7,012
  • 12
  • 68
  • 123
  • 1
    Have you added `use RevisionableTrait;` to your `Dynamic` class? `latestRevision` is a relationship added by that trait, so if your `Dynamic` class doesn't use the trait, it won't have the relationship. – patricus Jan 10 '16 at 02:09
  • I actually did complete forget to add the trait to the `Dynamic` model however the relationship still doesn't load. I think it may have something to do with the Edit I provided in my original question. – Jared Eitnier Jan 10 '16 at 02:22
  • That's because `find()` will return a new instance of the model, which won't have the table name set. You'd need to do something like: `$model = new Dynamic([]); $model->setTable($row['table_name']); $model = $model->find($row['row_id']); $model->setTable($row['table_name']); $revision = $model->latestRevision;` – patricus Jan 10 '16 at 02:35
  • 1
    Yup, that did it. Can you make your comments into an answer so I can accept? Thanks! – Jared Eitnier Jan 10 '16 at 02:43

1 Answers1

2

First, latestRevision is a relationship added by the RevisionableTrait, so you need to make sure that your Dynamic class has the use RevisionableTrait; statement.

Next, since the find() method (and any other retrieval methods) return new instances of the models, you will need to reset the table again on every model after you find it. So, your code would look something like:

$model = new Dynamic([]);
$model->setTable($row['table_name']);
$model = $model->find($row['row_id']);
$model->setTable($row['table_name']);
$revision = $model->latestRevision;

Another option, assuming you follow Laravel's naming conventions, is that you could determine the Model name from the table_name, and just use the correct Model from the start, instead of this Dynamic model:

$modelName = Str::studly(Str::singular($row['table_name']));
$model = $modelName::find($row['row_id']);
$revision = $model->latestRevision;
patricus
  • 59,488
  • 15
  • 143
  • 145
  • Thank you, I was also thinking about the second approach as well. – Jared Eitnier Jan 10 '16 at 02:49
  • @JaredEitnier No problem. Both methods have their pros and cons. The second method would have fewer "gotchas", but if you ever stray from the Laravel naming convention, the code would no longer work. Glad I could help, though. – patricus Jan 10 '16 at 02:53
  • I'm going to stick to the first method. Dynamically namespacing the models in Laravel 5 is going to get messy. – Jared Eitnier Jan 10 '16 at 03:08
  • @[Jared Eitner](https://stackoverflow.com/users/1133306/jared-eitnier) Can I simply create an eloquent model that uses the same view as the eloquent model "on the fly" without saving it to the database? I thought maybe if I create the constructor with the parent constructor nested and pass null values and apply property values after instantiation I could create a model. Lets say `$model = new Model();` $model->name = "test-dummy";` but I know I have to do more to make it work because I tested it. – yardpenalty.com Feb 12 '19 at 02:45