How to dynamically set table name in Eloquent Model - php

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([...]);

Related

Trying to fill the intermediate table using attach() but getting "Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::attach()"

I have tables called users, places and user_place. users has a column called id that contains the id of the user and places has a column called place_id as well. The user_place table has 2 columns called user_id and place_id and I'm trying to automatically populate them with the corresponding ids. I read I have to use attach() function after setting up the relationships which I believe I have done but I might be wrong. Here they are:
class PlaceController extends Controller
{
public function likePlace(Request $request){
$placeId = $request['placeId'];
$userId = $request['userId'];
$user = User::where('id', $userId)->first();
$place = new Place();
$place->place_id = $placeId;
$place->save();
$user->places()->attach($place);
}
}
User model:
class User extends \Eloquent implements Authenticatable
{
use AuthenticableTrait;
public function places(){
return $this->hasMany('App\Place');
}
}
Place mode:
class Place extends Model
{
public function user(){
return $this->belongsToMany('App\User');
}
}
In a Many to Many relationship, you should define both relationships like the following:
User.php
class User extends \Eloquent implements Authenticatable
{
use AuthenticableTrait;
public function places()
{
return $this->belongsToMany('App\Place', 'user_place', 'user_id', 'place_id');
} // ^^^^^^^^^^^^
}
Note: Given that your intermetiate table name doesn't follow the naming convention we specified so Laravel knows where table to look up.
Place.php
Notice that you mentioned that the primmary key of your Place model is place_id, and this also scapes from the Laravel convention you should specify it:
protected $primaryKey = 'place_id'; // <----
class Place extends Model
{
public function user()
{
return $this->belongsToMany('App\User', 'user_place', 'place_id', 'user_id');
}
}
So now in your controller:
class PlaceController extends Controller
{
public function likePlace(Request $request)
{
$placeId = $request['placeId'];
$userId = $request['userId'];
$user = User::where('id', $userId)->first();
$place = new Place();
$place->place_id = $placeId;
$place->save();
$user->places()->attach($place);
}
}
Side note
As I side note, you could save a couple of line replacing some sentences with their equivalent:
$userId = $request['userId'];
$user = User::where('id', $userId)->first();
Using the find() method, this is equal to:
$user = User::find($request['userId']);
Then, you could create your related object using the static method create of an Eloquent model so this:
$placeId = $request['placeId'];
$place = new Place();
$place->place_id = $placeId;
$place->save();
Is equal to this:
$place = Place::create(['place_id' => $request['placeId']]);
Then your controller will be reduced to this:
class PlaceController extends Controller
{
public function likePlace(Request $request)
{
$user = User::find($request['userId']);
$place = Place::create(['place_id' => $request['placeId']]);
$user->places()->attach($place);
}
}

Show data from 2 tables in laravel 5.4

I have a simple code, and now I want to select data from 2 tables. Then I want to show in my view. Here's my code
model:
Tikpro.php
class Tikpro extends Model {
public $table = "TIKPRO";
public $primaryKey = "ID_TIKPRO";
public function permintaan() {
return $this->hasMany('Permintaan', 'TIKPRO_ID', 'ID_TIKPRO');
}
model:
Permintaan.php
class Permintaan extends Model {
public $table = "PERMINTAAN";
public $fillable = array(
'NOMOR_TICKET',
'TGL_PERMINTAAN',
'NAMA_REQUESTER',
'BARANG_PERMINTAAN',
'NO_FPBJ',
'TGL_INPUT_FPBJ',
'TARGET_SELESAI',
'KETERANGAN',
'TINDAK_LANJUT_AKHIR',
'STATUS',
'FPB',
'RFQ',
'DO',
'BAST',
'TGL_DEADLINE',
'titik_proses',
'TIKPRO_ID',
);
public $primaryKey = "ID_PERMINTAAN";
public function tikpro() {
return $this->belongsTo('Tikpro','TIKPRO_ID','ID_TIKPRO');
}
Controller:
PermintaanController.php
public function details($ID_PERMINTAAN) {
$query = DB::table('PERMINTAAN')->select('TIKPRO.NAMA_TIKPRO')->join('TIKPRO','TIKPRO.ID_TIKPRO','=','PERMINTAAN.TIKPRO_ID')->get('all');
$jebret = Permintaan::find($ID_PERMINTAAN);
return view('permintaan.details', compact('jebret'))->with($query);
}
And how can I show it? In my view I try {{ $jebret->$query }} . But still can't show the data.
How should I write code? Thanks
I think you can try this
public function details($ID_PERMINTAAN) {
$query = DB::table('PERMINTAAN')->select('TIKPRO.NAMA_TIKPRO')->join('TIKPRO','TIKPRO.ID_TIKPRO','=','PERMINTAAN.TIKPRO_ID')->where('PERMINTAAN.ID_PERMINTAAN',$ID_PERMINTAAN)->get();
return view('permintaan.details', compact('query'));
}
Hope this work for you
There's 2 ways here. The first and easiest (if you've set it up correctly) is the relationship. https://laravel.com/docs/5.4/eloquent-relationships. To use the one in your example you want to do this:
$permintaans = Permintaan::query()->get()->all();
return view('permintaan.details', [
'permintaans' => $permintaans,
]);
//In the view
#foreach($permintaans as $permintaan)
<tr>{{$permintaan->tikpro->attribute}}</tr>
#endforeach
Another way like you've attempted is the join. In the select though I would leave it blank so you get all columns!
$permintaans = Permintaan::query()
->join('TIKPRO','TIKPRO.ID_TIKPRO','=','PERMINTAAN.TIKPRO_ID')
->get()
->all();

Laravel get Model by Table Name

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;
}

Laravel Update Related Model with push?

I am stuck on updating a eagerloaded model that has a "hasMany" relation.
I have one model like so:
class UserGroup extends Model
{
public function enhancements()
{
return $this->hasMany('App\UserGroupEnhancement');
}
}
My controller is passing $userGroup to the view like so:
$userGroup = $this->userGroup->with('enhancements')->whereId($id)->first();
and then in my view I have
#foreach($userGroup->enhancements as $enhancement)
<label>{{$enhancement->type}}</label>
<input class="form-control" name="enhancements[{{$enhancement->id}}][price]" value="{{$enhancement->price}}">
#endforeach
When updating, how do I update all of records in the enhancement relationship? It's passed back into multiple arrays. I am currently doing something like this.
public function update($id)
{
$userGroup = $this->userGroup->findOrFail($id);
$enhancement = \Input::get('enhancements');
if (is_array($enhancement)) {
foreach ($enhancement as $enhancements_id => $enhancements_price) {
$userGroup->enhancements()->whereId($enhancements_id)->update($enhancements_price);
}
}
}
Is there a way I can do this without needing the foreach loop? I see the push() method, but seems to only work on a single array.
There isn't a better way to do this. There is an Eloquent method called saveMany but it is used to create new records and not update. ExampleDoc:
$comments = [
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another comment.']),
new Comment(['message' => 'The latest comment.'])
];
$post = Post::find(1);
$post->comments()->saveMany($comments);
I would stick with your solution, you can even create a trait or a base Eloquent class and put that logic in a method so it can be used by all other models, if you ever need to.
Something like:
trait UpdateMany {
public function updateMany($updates, $relationshipName)
{
if (!empty($updates)) {
foreach ($updates as $update_id => $update) {
$this->{$relationshipName}()->whereId($update_id)->update($update);
}
}
}
}
Then attach to your model(s):
class UserGroup extends Model
{
use UpdateMany;
public function enhancements()
{
return $this->hasMany('App\UserGroupEnhancement');
}
}
And simply use as:
$userGroup = $this->userGroup->findOrFail($id);
$userGroup->updateMany(\Input::get('enhancements'), 'enhancements');

In Laravel Eloquent inserts an empty record to table

I have the class Word that extends Eloquent. I have added two records manually, and they are fetching fine with Word::all() method. But when I'm trying to create new object and save it, Eloquent inserts empty values into table.
So, here is the model
class Word extends Eloquent {
protected $table = 'words';
public $timestamps = false;
public $word;
public $senseRank = 1;
public $partOfSpeech = "other";
public $language;
public $prefix;
public $postfix;
public $transcription;
public $isPublic = true;
}
Here is the database migration script
Schema::create('words', function($table) {
$table->increments('id');
$table->string('word', 50);
$table->tinyInteger('senseRank');
$table->string('partOfSpeech', 10);
$table->string('language', 5);
$table->string('prefix', 20)->nullable();
$table->string('postfix', 20)->nullable();
$table->string('transcription', 70)->nullable();
$table->boolean('isPublic');
});
And here is the code I'm trying to run
Route::get('create', function()
{
$n = new Word;
$n->word = "hello";
$n->language = "en";
$n->senseRank = 1;
$n->partOfSpeech = "other";
$n->save();
});
And all I get is a new record with correct new id, but all the other fields are empty strings or zeros. How could it be possible?
You need to remove all properties from your model because now Eloquent won't work as it should, your class should look like this:
class Word extends Eloquent {
protected $table = 'words';
public $timestamps = false;
}
If you need default values for some fields, you could add them for example when creating table using default, for example:
$table->tinyInteger('senseRank')->default(1);
Comment out / get rid of class fields you are setting:
// public $word;
// public $senseRank = 1;
// public $partOfSpeech = "other";
// public $language;
Laravel uses magic __get() and __set() methods to store fields internally. When you have fields defined, this does not work.
You can use model events to set defaults, add this method to you model:
public static function boot() {
parent::boot();
static::creating(function($object) {
$object->senseRank = 1;
$object->partOfSpeech = "other";
});
}

Categories