Is there any way to get a model by table name?
For example, I have a "User" model, its table is defined as protected $table = "users"
Now, what I want to do is to get the model by table name which is equal to "users".
This function is more like the reverse of Model::getTable();
I have searched everywhere but I could not find a solution, perhaps I might be missing something simple?
EDIT
I am building something like an API :
Route::get('/{table}', 'ApiController#api');
Route::get('/{table}/filter', 'ApiController#filter');
Route::get('/{table}/sort', 'ApiController#sort');
Route::get('/{table}/search', 'ApiController#search');
so in the address bar, for example when I search for the "users", I could just hit on the URL:
api/users/search?id=1
then on the controller, something like:
public function search(){
// get all the params
// get the model function
$model = //function to get model by table name
// do some filtering, then return the model
return $model;
}
Maybe something like this will help you:
$className = 'App\\' . studly_case(str_singular($tableName));
if(class_exists($className)) {
$model = new $className;
}
studly_case() and str_singular() are deprecated functions.
You can use the Illuminate\Support\Str facade.
$className = 'App\\' . Str::studly(Str::singular($tableName));
I know that it is an old question, but it can help someone:
public function getModelFromTable($table)
{
foreach( get_declared_classes() as $class ) {
if( is_subclass_of( $class, 'Illuminate\Database\Eloquent\Model' ) ) {
$model = new $class;
if ($model->getTable() === $table)
return $class;
}
}
return false;
}
It will return the class name, so you need to instantiate it.
You must determine for which table name which class to call.
I see 2 ways to do this.
Use Laravel's models naming convention as #IgorRynkovoy suggested
or
Use some kind of dictionary
public function search($tableName)
{
$dictionary = [
'table_name' => 'CLASS_NAME_WITH_NAMESPACE',
'another_table_name' => 'CLASS_NAME_WITH_NAMESPACE',
];
$className = $dictionary[$tableName];
$models = null;
if(class_exists($className)) {
$models = $className::all();
}
// do some filtering, then return the model
return $models;
}
Alternative variant.
I have my base model App\Models\Model
This model have static method getModelByTable, ofcourse you can store this method anywhere you want.
public static function getModelByTable($table)
{
if (!$table) return false;
$model = false;
switch ($table) {
case 'faq':
$model = Faq::class;
break;
case 'faq_items':
$model = FaqItems::class;
break;
}
if ($model) {
try {
$model = app()->make($model);
} catch (\Exception $e) {
}
}
return $model;
}
Inherit from the following, instead of from Model.
use Illuminate\Support\Str;
class EnhancedModel extends \Illuminate\Database\Eloquent\Model
{
/**
* The table associated with the model. Copies $table in Model
*
* #var string
*/
protected static string $tableName;
/**
* Get the table associated with the model. Copies getTable() in Model
*
* #return string
*/
public static function getTableName(): string
{
return static::$tableName ?? Str::snake(Str::pluralStudly(class_basename(static::class)));
}
/**
* Get the table associated with the model. Overrides getTable() in Model
*
* #return string
*/
public function getTable(): string
{
return $this::getTableName();
}
}
To override the auto-guessed table name, add this to your EnhancedModel descendent class:
protected static string $tableName = 'the_table_name';
Looks Laravel 6 make some changes. The following works fine for me
use Illuminate\Support\Str;
....
$className = 'App\\' . Str::studly(str::singular($table_name));
if(class_exists($className)) {
$model = new $className;
}
Related
This one has happened to me before but I have no idea why and how to avoid it. So I have a static function in a Model which gets all the database rows and uses a foreach loop to read another table but I am unable to correctly read the row data:
public static function test()
{
$accounts = self::where( 'is_enabled', 1 )->get();
foreach ( $accounts as $account ) {
$map = AccountMap::where( 'account_id', $account->id )->first();
$location = Location::getLocation( $map->location_id );
$data = $location->getData();
}
}
So the above function gathers an array of items ($accounts) this is then passed into a foreach loop all is fine to this point but if i now use $account->id it is null. The id is shown in the Account object in its attributes folder.
A very similar function is used elsewhere in this model but it uses a passed id and this one works (however $account->id is null). The issue is not the database or column names:
public static function getThisLocation( $id )
{
$account = self::find( $id );
$map = AccountMap::where( 'account_id', $id )->first();
location = Location::getLocation( $map->location_id );
$data = $location->getData();
return $data;
}
*** EDIT ***
Account, AccountMap and Location are all Eloquent models
namespace App\Models;
use Eloquent;
use App\Notifications\AccountMessages;
use Kyslik\ColumnSortable\Sortable;
use Illuminate\Notifications\Notifiable;
/**
* #method static find(int $id)
*/
class Account extends Eloquent
{
use Sortable;
use Notifiable;
public $sortable = [
'id',
'name',
'lastupdate',
'url'
];
public static function test()
{
$accounts = self::where( 'is_enabled', 1 )->get();
foreach ( $accounts as $account ) {
$map = AccountMap::where( 'account_id', $account->id )->first();
$location = Location::getLocation( $map->location_id );
$data = $location->getData();
}
}
public static function getThisLocation( $id )
{
$account = self::find( $id );
$map = AccountMap::where( 'account_id', $id )->first();
location = Location::getLocation( $map->location_id );
$data = $location->getData();
return $data;
}
}
namespace App\Models;
use Eloquent;
use Kyslik\ColumnSortable\Sortable;
/**
* #method static where(string $string, int $id)
*/
class AccountMap extends Eloquent
{
use Sortable;
public $sortable = [
'id',
'account_id',
'location'
];
}
*** MORE EDIT ***
I have confirmed that using $account->attributes['id'] has worked but I've no idea why what I expected to work didn't ($account->id)
The problem must be something related to communication of your model and migration.
Add this dd() to your current test function:
public static function test()
{
$accounts = self::where( 'is_enabled', 1 )->get();
foreach ( $accounts as $account ) {
if ($account->id){
$map = AccountMap::where( 'account_id', $account->id )->first();
$location = Location::getLocation( $map->location_id );
$data = $location->getData();
} else {
dd($account)
}
}
}
Then Check the result and see is there the id filed on your response? If not, The id field doesn't exist on your self Model and it's Your problem's cause.
Finally, Check your model fields easily with :
public function testReturnOfSelfModel()
{
$data= self::all();
dd($data);
}
If you have id on this dd function, Your Model working properly. If not, you dont have id field.
Also, it is more professional to change Capitalize your model's first charachter. It sholud be Self, not self.
I'd suggest to setup two proper data Model (a migration would need to create these tables):
class Account extends Model {
protected $table = 'accounts';
public $timestamps = false;
/**
* The attributes that are mass assignable.
* #var array
*/
protected $fillable = [];
/**
* The attributes that should be hidden for arrays.
* #var array
*/
protected $hidden = [];
}
Unless defining protected $table it will definitely not know what to do.
It's rather unclear what you're even trying to accomplish with AcountMap, but it may need a relation defined; which eg. with return $this->belongsTo(Account::class); ...while simply adding lat & lng to class Account would be far less complex and perfectly fine, while it's only 1 location.
Context: Trying to extend Laravel models from a database table. Table models linked to App\Models\BootableModel, App\Models\User extends this class.
I have the following code:
<?php
class BootableModel extends Model
{
protected $table = 'models';
protected $fillable = [
'name',
'class',
'table',
];
public function __construct(array $attributes = [])
{
$this->bootFromDatabase();
parent::__construct($attributes);
}
private function bootFromDatabase()
{
$class = static::class;
$bModClass = self::class;
Log::debug($class);
Log::debug($bModClass);
//$bootableModel = DB::table('models')->where('class', $class)->first();
$bootableModel = $bModClass::where('class', $class)->first();
if(!$bootableModel) {
return;
}
Log::debug($bootableModel->id);
The debug of $bModClass shows App\Models\BootableModel as expected (self vs static), but for some reason the $bModClass::where is trying to query the users table. Using a direct reference to App\Models\BootableModel::class does not change this, so it's not self::class that is the issue. The debug output as proof:
[2021-02-21 17:33:39] local.DEBUG: App\Models\User
[2021-02-21 17:33:39] local.DEBUG: App\Models\BootableModel
It should never try to access User::where(), and as such, it should never try to use User::$table either, but somehow it does.
Is Laravel doing some weird reflection, or is this normal PHP behavior? Is there a way around this?
Update:
I have found a workaround, but I'm not satisfied with this being the correct/only solution:
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
static::bootFromDatabase();
}
public static function bootFromDatabase()
{
$class = static::class;
$bModClass = self::class;
if($class === $bModClass) {
return;
}
Have you ever tried self::where(...) instead of $bModClass::where(...) ?
Similar situation:
class Base {
public static function where()
{
return 'where from base';
}
public static function getName()
{
return self::where();
}
}
class User extends Base {
public static function where()
{
return 'where from user';
}
}
echo User::getName();
Output: where from base
I am new to Laravel. I am trying to use Eloquent Model to access data in DB.
I have tables that shares similarities such as table name.
So I want to use one Model to access several tables in DB like below but without luck.
Is there any way to set table name dynamically?
Any suggestion or advice would be appreciated. Thank you in advance.
Model:
class ProductLog extends Model
{
public $timestamps = false;
public function __construct($type = null) {
parent::__construct();
$this->setTable($type);
}
}
Controller:
public function index($type, $id) {
$productLog = new ProductLog($type);
$contents = $productLog::all();
return response($contents, 200);
}
Solution For those who suffer from same problem:
I was able to change table name by the way #Mahdi Younesi suggested.
And I was able to add where conditions by like below
$productLog = new ProductLog;
$productLog->setTable('LogEmail');
$logInstance = $productLog->where('origin_id', $carrier_id)
->where('origin_type', 2);
The following trait allows for passing on the table name during hydration.
trait BindsDynamically
{
protected $connection = null;
protected $table = null;
public function bind(string $connection, string $table)
{
$this->setConnection($connection);
$this->setTable($table);
}
public function newInstance($attributes = [], $exists = false)
{
// Overridden in order to allow for late table binding.
$model = parent::newInstance($attributes, $exists);
$model->setTable($this->table);
return $model;
}
}
Here is how to use it:
class ProductLog extends Model
{
use BindsDynamically;
}
Call the method on instance like this:
public function index()
{
$productLog = new ProductLog;
$productLog->setTable('anotherTableName');
$productLog->get(); // select * from anotherTableName
$productLog->myTestProp = 'test';
$productLog->save(); // now saves into anotherTableName
}
I created a package for this: Laravel Dynamic Model
Feel free to use it:
https://github.com/laracraft-tech/laravel-dynamic-model
This basically allows you to do something like this:
$foo = App::make(DynamicModel::class, ['table_name' => 'foo']);
$foo->create([
'col1' => 'asdf',
'col2' => 123
]);
$faz = App::make(DynamicModel::class, ['table_name' => 'faz']);
$faz->create([...]);
I have function showItemList inside User class
class User extends Eloquent {
//...
protected $item = ['axe', 'sword', 'knife'];
public function showItemList() {
return $this->$item;
}
}
In my controller it's possible to use this.
$id = 1;
$user = User::find($id);
$user -> showItemList();
But how can I just call this function directly(irrelevant to $id query)?
I looks for something like below (sure now it's not working):
$list = User::showItemList();
You need to use the static protected variable and return it from static method.
class User extends Eloquent {
//...
static protected $item = ['axe', 'sword', 'knife'];
public static function showItemList() {
return self::$item;
}
}
I hope I can explain this clearly, apologies in advance if it is confusing. I have a goals table which hasOne of each of bodyGoalDescs, strengthGoalDescs and distanceGoalDescs as shown below
goals.php
class Goal extends BaseModel
{
protected $guarded = array();
public static $rules = array();
//define relationships
public function user()
{
return $this->belongsTo('User', 'id', 'userId');
}
public function goalStatus()
{
return $this->hasOne('GoalStatus', 'id', 'goalStatus');
}
public function bodyGoalDesc()
{
return $this->hasOne('BodyGoalDesc', 'id', 'bodyGoalId');
}
public function distanceGoalDesc()
{
return $this->hasOne('DistanceGoalDesc', 'id', 'distanceGoalId');
}
public function strengthGoalDesc()
{
return $this->hasOne('StrengthGoalDesc', 'id', 'strengthGoalId');
}
//goal specific functions
public static function yourGoals()
{
return static::where('userId', '=', Auth::user()->id)->paginate();
}
}
each of the three tables looks like this with the function details changed
class BodyGoalDesc extends BaseModel
{
protected $guarded = array();
public static $rules = array();
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'bodyGoalDescs';
//define relationships
public function goal()
{
return $this->belongsTo('Goal', 'bodyGoalId', 'id');
}
}
a goal has either a body goal, a strength goal, or a distance goal. I am having a problem with this method in the controller function
<?php
class GoalsController extends BaseController
{
protected $goal;
public function __construct(Goal $goal)
{
$this->goal = $goal;
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
$thisgoal = $this->goal->find($id);
foreach ($this->goal->with('distanceGoalDesc')->get() as $distancegoaldesc) {
dd($distancegoaldesc->DistanceGoalDesc);
}
}
}
when I pass through goal 1 which has a distance goal the above method dies and dumps the Goal object with the details of goal 1 and an array of its relations including an object with DistanceGoalDes.
when I pass through goal 2 it passes through exactly the same as if I had passed through goal 1
if I dd() $thisgoal i get the goal that was passed through
what I want ultimately is a method that returns the goal object with its relevant goal description object to the view but this wont even show me the correct goal details not too mind with the correct relations
this function is now doing what I want it to do, I am sure there is a better way (besides the fact that its happening in the controller right now) and I would love to hear it.
public function show($id)
{
$thisgoal = $this->goal->find($id);
if (!$thisgoal->bodyGoalDesc == null) {
$goaldesc = $thisgoal->bodyGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('bodygoaldesc', $goaldesc);
} elseif (!$thisgoal->strengthGoalDesc == null) {
$goaldesc = $thisgoal->strengthGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('strengthgoaldesc', $goaldesc);
} elseif (!$thisgoal->distanceGoalDesc == null) {
$goaldesc = $thisgoal->distanceGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('distancegoaldesc', $goaldesc);
}
}