It is possible to extend or use different class during run time?
Example:
Let say we have a model called Player (Our A -model)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Player extends Model{
}
And we have 2 other models (B and C models)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
protected $connection= 'db_b';
class PlayerInfoB extends Model{
function getName(){
return $this->name;
}
}
Our C model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
protected $connection= 'db_c';
class PlayerInfoC extends Model{
function getName(){
return $this->name_g;
}
}
How Model A (Player) can extend Model B or C during run time based on configuration or other data
Why I need this.
I have a 2 or more different tables, this tables columns have different names, so for example:
Table 1 - name
Table 2 - name_g
Table 3 - name_full
So I need a wrapper that I can always call getName(), without checking what table is used now.
$player = Player::get();
echo $player->getName();
If something is not clear, please comment and I will update my question.
Update based on madalin-ivascu answer can be done this way?
class Player extends Model{
protected $model;
public function __construct(){
$this->setModel();
parent::__construct();
}
protected function setModel(){
$this->model = $this->column_model_name
}
function getAttributeName(){
return $this->model->getName();
}
}
It is not possible to compose a calss at runtime without using eval or or dirty hacks. You have to reconsider your class design, because it's very unlikely that you need to do that with a good design.
What you can do is changing table and db connection at runtime on the model instance using methods setTable and on:
Player::on('db_b')->setTable('player_info_b')->find($id);
Another approach (preferable) would be defining the model PlayerInfoC and PlayerInfoB that extend your Player model, and then based on your condition you instantiate the class B or C when needed.
In your code your script must have a state that you check in order to know when to use the correct model?
In this case why not use a parameter in get name?
class Player extends Model{
function getName($field){
return isset($this->{$field}) ? $this->{$field} : null;
}
}
If you do this a lot then use magic methods:
class Player extends Model{
function __get($key){
return isset($this->{$field}) ? $this->{$field} : null;
}
}
...
echo $myModelInstance->{$field};
http://php.net/manual/en/language.oop5.overloading.php#object.get
In Laravel when you have pulled data back via a collection method it does this magic method anyway since all attributes are stored in a nested object called attributes so the __set() and __get() looks something like this:
function __get($key){
return isset($this->attributes->{$key}) ? $this->attributes->{$key} : null;
}
function __set($key, $value){
return $this->attributes->{$key} = $value;
}
The latter with attributes sub set is advised, this way you prevent data conflicts with database column names returned vs names you have already used in a model.
This way you only have to manage one attribute name as a reserved name in every model you create and not worry amount the hundreds of var names you use overwriting another in a model or extension of a model.
use that model value to call the function
$player = Player::get();
echo Player::getName($player->id,'PlayerInfoC');
in the Player model you simply call
public static function getName($id,$class)
return $class::where('player_id',$id)->getName();//each class will have the function
}
ps: you will need to do some validation to test if the class exist with that name
another option will be to create relationships between the models
Related
I have three relational table attached below.
https://drive.google.com/file/d/1q1kdURIwFXxHb2MgdRyBkE1e3DMug7r-/view?usp=sharing
I have also three separate models where defined relation among all of my table's.I can read the City Model's information from Country model using hasManyThrough() relation But cannot read the Country information from City model. I have tried to retrieve City model's using ``hasManyThrough``` but didn't get result (attached as commented country method ). Please read my model and it's relational method here..
Is there someone to help me for getting City model's information using Eloquent method hasManyThrough / hasManyThrough or using inverse of hasManyThrough / hasManyThrough ?
01.
<?php
namespace App\Hrm;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Country extends Model
{
//use SoftDeletes;
protected $fillable = ['name','description','status'];
public function districts(){
return $this->hasMany(District::class);
}
public function cities(){
return $this->hasManyThrough(City::class,District::class);
}
}
02.
<?php
namespace App\Hrm;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class District extends Model
{
//use SoftDeletes;
protected $fillable = ['country_id','name','description','status'];
public function country(){
return $this->belongsTo(Country::class);
}
public function cities(){
return $this->hasMany(City::class);
}
}
3.
namespace App\Hrm;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class City extends Model
{
//use SoftDeletes;
protected $fillable = ['district_id','name','description','status'];
public function district(){
return $this->belongsTo(District::class);
}
// public function country(){
// return $this->hasOneThrough(Country::class, District::class);
// }
Doesn't look like there is a native way to define the inverse of a "hasManyThrough" relationship yet in Laravel. There have been a few issues opened on github to request it, but they were closed.
You could use the staudenmeir/belongs-to-through package if you don't mind installing a third-party package for this functionality. Then you should be able to define a belongsToThrough relationship like this:
class City extends Model
{
use \Znck\Eloquent\Traits\BelongsToThrough;
public function country() {
return $this->belongsToThrough(Country::class, District::class);
}
}
Why can't use parent method?
$city = City::find(1);
$country = $city->district->country();
i just had a similar situation i was able to accomplish a belongsToThrough with hasOneThrough
public function country()
{
return $this->hasOneThrough(
Country::class, // model we are trying to get
District::class, // model we have an _id to
'id', // WHERE `district`.`id` = `city`.`district_id`
'id', // `countries`.`id`
'district_id', // local column relation to our through class
'country_id' // `district`.`country_id`
);
}
what this should generate is
SELECT * FROM `countries`
INNER JOIN `districts`
ON `districts`.`country_id` = `countries`.`id`
WHERE `districts`.`id` = ?
-- ? == city.district_id
Database structure:
City:
id: increments
district_id: integer
...
Country:
id: increments
...
District:
id: increments
country_id: integer
...
we can then do $city->country
note: i have not fully tested this but with the testing that i have done it 'works'
Edit: i originally thought that i needed to leave the localKey
parameter null otherwise the relation wont work. it turns out i didnt
fully understand what that column was doing and that was wrong. That
key is the local column that relates to our through column (unless i
still have more to learn/figure out), when left the value as null, it
would use the local id column which a. is the wrong value, b. can also
be out of range (which is how i discovered it was using the wrong
value)
in my testing i only had two rows, both with the same relations. what
i didnt realize though was that on the "through table" both row 1 and
2 and the same related (relation where are trying to reach) so i didnt
notice the issue right away. hopefully now its all working
I had an accessor set on my Eloquent model that worked fine, but the associated database query was getting run once for every instance of the model I created. On my index page this meant 5 dozen queries.
<?php
class Thingy extends Model {
protected $appends = ["parentType"];
public function getParentTypeAttribute($value) {
return self::where("type"=>$this->type, "parent"=>1)->value("name");
}
}
class ThingyController extends Controller {
public function index() {
$thingys = Thingy::all();
return view("things.index", compact("thingys"));
}
}
To explain briefly: there are two classes of "thingy" in the same database table, the class being indicated by a boolean value named "parent." I want to get the name of the parent when I access the child. I know this should be two tables but it's not.
I wanted to reduce the number of database reads, so I tried changing it to a relationship instead. I figured this way I could take advantage of eager loading.
<?php
class Thingy extends Model {
public function parent() {
return $this->hasOne("Thingy", "id")->where("type"=>$this->type, "parent"=>1);
}
}
class ThingyController extends Controller {
public function index() {
$thingys = Thingy::with(["parent"]);
return view("things.index", compact("thingys"));
}
}
The problem is that within the relationship method, $this is an empty instance of the model, unlike in the accessor, so $this->type is null.
Is there a way to access properties of the model I'm working with from within a relationship method?
Figured that out. Since I'm essentially doing a self-join on the same table, I can specify the "local" and "foreign" ID columns as the column I'm trying to match:
public function parent() {
return $this->hasOne("Thingy", "type", "type")->where("parent"=>1);
}
I guess the key concept was to remember that I'm defining a relationship between two instances of the model, which is independent of the particular instances I'm dealing with.
My repository is like this :
<?php
namespace App\Repositories;
use App\Models\UsersBank;
use Illuminate\Contracts\Container\Container;
use Rinvex\Repository\Repositories\EloquentRepository;
class UserBankRepository extends EloquentRepository
{
public function __construct(Container $container)
{
$this->setContainer($container)
->setModel(UsersBank::class)
->setRepositoryId('rinvex.repository.uniqueid');
}
}
My model is like this :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UsersBank extends Model
{
protected $fillable = ['user_id','bank_id','status','account_name','account_number'];
}
My service is like this :
public function setMain($id)
{
$param['status'] = 1;
$this->user_bank_repository->update($id, $param);
}
When setMain function executed, It will update field status = 1 by the id
I want to update status = 1 to all record. So, Not by id
How can I do it?
Note :
Update which I mean here is the update via the repository. Not update through a model
Look into the source code of update() method of the package and you'll see it's impossible, so you'll need to use foreach() and create a bunch of queries.
My opinion is this and similar packages are useless since they are still using Eloquent and do not provide any handful functionality. I'd recommend you to use Eloquent directly to update all rows with just one query:
Model::query()->update(['status' => 1]);
I have a table called payments which contains a field called Vendor ZIP.
I have a table called 201502_postcodes and my "join" in this case is the postcode field in this table.
How do I return field values in this 201502_postcodes table using Eloquent?
My Models are;
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Payment extends Model {
public function postcodeExtract()
{
return $this->belongsTo('App\Models\PostcodeExtract', 'postcode', 'Vendor ZIP');
}
_
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PostcodeExtract extends Model {
protected $connection = 'postcodes';
public function scopeFromTable($query, $tableName)
{
return $query->from($tableName);
}
public function payment()
{
return $this->hasMany('App\Models\Payment', 'Vendor ZIP', 'postcode');
}
So, I have a scope on this model because the 201502 part of my table name is a variable (in that, a new one comes in every quarter).
In my controller... I have no idea what to put. I don't know how to get both scope and relationship to work. How can I write a query that will take a postcode/zip and output one of the fields from the (do I refer to them as "methods"?) postcode extract table?
It is not a duplicate of this question Laravel 4: Dynamic table names using setTable() because relationships are not involved or discussed on that question.
--- UPDATE ---
If I am to use getTable - would it go something like this...
class PostcodeExtract {
public function setTableByDate($selected_tablename)
{
$this->table = $selected_tablename;
// Return $this for method chaining
return $this;
}
public function getTable()
{
if (isset($this->table))
$this->setTableByDate($this->table);
return $this->table;
}
}
And then I would use it in my controller like;
$selected_tablename = 201502_postcode //created by some other controller
$postcode_extract = new PostcodeExtract;
$data = $postcode_extract->setTableByDate($selected_tablename)->get()->toArray();
The Carbon stuff isn't really relevant. I have a lookup to get those tablenames the fact the prefix with a date like value shouldn't mean it's treated like a date.
There are a couple of things going on here.
scopeFromTable() is redundant
Laravel employs magic methods to handle calls to undefined methods. Calling from() on the model will actually call from() on the models internal Query object (assuming you didn't define a method called 'from' on the model itself). It's worth reading the __call and __callStatic methods on the Model class.
relationships use getTable()
Another aspect of the Laravel is the concept of convention over configuration. This basically means that the framework assumes some things so that you don't have to define every detail. In regards to table naming convention, it will naturally use a table name derived from the class name.
// Uses table 'foos'
class Foo {}
There are a few ways to change this behavior. First, you can define a 'table' data member like this.
class Foo {
protected $table = 'bars';
}
If you need a more dynamic behavior, then you can redefine the getTable method.
class Foo {
public function getTable()
{
// return your special table name based on today's date
}
}
Ultimately the models and their relationships refer to getTable to figure out what the table names should be.
your use cases
If you only ever need to query the current table, then I would suggest redefining getTable.
If you need to query both current and past tables, then I suggest pairing a new method along side redefining getTable
class Foo {
public function setTableByDate(\DateTime $date)
{
$this->table = // generate table name from $date
// Return $this for method chaining
return $this;
}
public function getTable()
{
if (isset($this->table))
$this->setTableByDate(\Carbon\Carbon::now());
return $this->table;
}
}
With this in place, you don't have to worry about the table name in your controller or anywhere else unless you need to query past records.
setting the table by date per user
$foos = Foo::setTableByDate($user->some_date)->where(...)->get();
here is my Model:
class MyTable extends CActiveRecord{
...
public function myOwnFunction{
...
$this->find($myCriteria, $myParams);
$myLocalVar = $this->MyTableColumn;
...
}
}
My objective is to retrieve the record inside a function of MyTable CActiveRecord class
If I echo myLocalVar I find it empty
I guess I'm making a philosophical error... What is it? And what's the correct way to reach my objecive, if there is one?