T.O.
T.O.

Reputation: 130

Laravel 4: Dynamic table names using setTable()

I'm not sure what do I need to do to get dynamic table names working.

Consider following Model (table 'test' does not exist):

<?php

// app/models/Test.php

class Test extends Eloquent {

}

and then if I do ('fields' table does exist):

<?php

// app/routes.php

$test = new \Test;
$test->setTable('fields');
$data = $test->find(1);
dd($data);

I get an error:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'test.tests' doesn't exist (SQL: select * from `tests` where `id` = ? limit 1) (Bindings: array ( 0 => 1, ))"

Note that setting the table name from the Test model works just fine.

L4 actually uses setTable() method, very much the way I would want to, in vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations.Pivot.php constructor, though I just couldn't get it to work by following that example.

Thanks for help.

Upvotes: 2

Views: 5756

Answers (3)

malhal
malhal

Reputation: 30575

Larvael's design doesn't fully support dynamic models. Although there is the setTable method its limitation is its an instance method. Furthermore within the framework a lot of queries begin with creating a model instance via "new static", thus there is no way for you to set the table on the internal queries. Yes you can call setTable on your own queries but as you develop more of your application using more of Laravel's features, e.g. SoftDeletes forceDelete you will gradually come up against a brick wall. A work around is to use PHP's eval function to generate model classes at runtime, that way new static will work the way it was designed.

    // load all the models
    $eval = "namespace App\\Records;";
    foreach($container->tables()->get() as $table){
        $recordType = $table->recordType;
        $recordTable = $table->getRecordTableName();
        $eval .= "class $recordType extends \\App\\Record { protected \$table = '$recordTable'; }";

    }
    eval($eval);

In the case of when I have a 'Venue' recordType that uses the 'venue' recordTable I now have a class \App\Records\Venue that uses the table "venue", thus I can now do this

$class = "\App\Records\Venue";
$venue = $class::find(1);

Now you are free to use Laravel as designed without any more hackery. This is a very powerful workaround that comes with a security risk, so be sure to sanitise any parameters used in eval. Hopefully someday Laravel will implement a way to support dynamic models.

Upvotes: 0

trm42
trm42

Reputation: 2676

You need to override Eloquent Model's static find method as it's used for find:

public static function find($id, $columns = array('*'))
{
    if (is_array($id) && empty($id)) return new Collection;

    $instance = new static;

    return $instance->newQuery()->find($id, $columns);
}

That method is creating new instance of your model class instead of reusing the instiated class. By default this "forgets" the table you've set. And probably you need to pass the table name for it. In my use case my code infers the used table based on the id so I don't have to pass the whole table name. Hooray for legacy ;-)

Other option is to use some additional factory class like Daniel Cantarin suggested

You may need to override some additional static methods to handle all use cases. For example I just noticed that using eager loading breaks my table mods, so I need to copy with method's behaviour without breaking it's calling API, yay -_____-

Upvotes: 0

Daniel Cantarin
Daniel Cantarin

Reputation: 319

Perhaps this is useful for you: http://laravel.io/forum/08-01-2014-defining-models-in-runtime

More exactly, from here: http://laravel.io/forum/08-01-2014-defining-models-in-runtime?page=1#reply-11779

class ModelBuilder extends Eloquent {

    protected static $_table;


    public static function fromTable($table, $parms = Array()){
        $ret = null;
        if (class_exists($table)){
            $ret = new $table($parms);
        } else {
            $ret = new static($parms);
            $ret->setTable($table);
        }
        return $ret;
    }

    public function setTable($table)
    {
        static::$_table = $table;
    }

    public function getTable()
    {
        return static::$_table;
    }
}

$user = ModelBuilder::fromTable("users")->find(1);

That's not my final implementation, which's much more complex (and dirtier) than that because of my use cases. But i guess the example may lead you to what you need.

Upvotes: 7

Related Questions