简体   繁体   中英

Laravel/Eloquent: calculated database field in model how

(Question posted on Laracast too, didn't reveal any answer yet, so I give Stackoverflow a try)

Hi community,

I have to solve a database problem in Eloquent with a model which has a field which is to be read from different databases. Since I have difficulties expressing my problem properly in English/OOP/Laravel/Eloquent/PHP terminology, I'll try to express my needs in an example (don't care much about correct PHP syntax).

Given there is a model resembling a tree-like structure, attributes used are usually:

[treenode]
id
parent_id (=self referencing key to id)
name
...

Now say the tree is to store pointers to nodes of different kinds, say, houses, cars and users, each of which is stored in a different table/model:

[house]
id
name
street address
zip
...

[car]
id
name
brand
horsepower
...

[user]
id
name
age
gender
...

The problem is obviously to have a model for "treenode" which transparently accesses the other databases to populate the "name" field, which all the models have in common, to draw the tree.

I thought about changing the treenode model to:

[treenode]
id
parent_id
kind (house | car | user)
data_id (key into house | car | user table)
...

The next step was to "somehow" code the "name" field in Eloquent, so it appears like it still was part of the "treenode" model, and this is where I am stuck. I tried a "calculated field" inside the model, like described in the docs, like so:

public function name() {

select case $this->kind {
    case house: return House::findorfail($this->data_id)->first()
    case car: return Car::findorfail($this->data_id)->first()
    case user: return User::findorfail($this->data_id)->first()
}
}

This does work somehow at first glance, but obviously "name" isn't really included in the model's attributes list, so it doesn't get serialized, ie if I JSON_encode a treenode, "name" is not included.

And a similar problem arises when setting "name" by changing a treenode, if the name is to be changed, I'd have to write it back into the correct database, and JSON support was also required. I have no clue how I could achieve this. I read about accessors, like getNameAttribute and setNameAttribute but as far as I have seen they work only if there is a "name" field in the treenode model.

Can anyone point my nose into the right direction ... can I somehow rework my treenode model so it incudes a "name" field which gets its contents from the other tables, and, if I asssign a value to it, writes the name into the other tables, but otherwise behave exactly like when it was included in the "treenode" model?

Thx,

Armin.

From reading problem it seems that laravel's Polymorphic Relations are the right answer for your problem, since you can link a model with different models and not "harcode" the type of the model as a foreign key.

Since polymorphic relations allow a model to belong to more than one other model on a single association.

Check this out with example:

https://laravel.com/docs/5.4/eloquent-relationships#polymorphic-relations

From your example it sounds like you're after Polymorphic Relationships .

Firstly, change data_id and kind to kind_id and kind_type ie

data_id becomes kind_id and

kind becomes kind_type

you can do this in your migrations file with:

$table->morphs('kind');

Then in your treenode model add:

/**
 * Kind Relationship
 * 
 * @return \Illuminate\Database\Eloquent\Relations\MorphTo
 */
public function kind()
{
    return $this->morphTo();
}

/**
 * Get the name from the "kind" relationship
 * 
 * @return mixed
 */
public function getNameAttribute()
{
    return $this->kind->name;
}

Then you could do the following:

$treeNode = App\TreeNode::with('kind')->first();

$treeNode->name; //This would be the name on the other model

(The above assumes that you treenode model is App\\TreeNode . If it isn't then simply change it to whatever is correct).

Edit

If you're already working with a production environment / need to keep your data as is then you will need to update the kind_type to be the fully qualified namespace of the model that it's referencing eg

DB::table('treenode')
    ->where('kind_type', 'user')
    ->update(['kind_type' => \App\User::class]);

You will need to do this for all the different kind_type s (you should only need to do this at the beginning, after that when you use the associate() method this will happen automatically).

Alternatively, you can set up a morph map:

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'user' => 'App\User',
]);

You can add the above to the boot() method in your AppServiceProvider .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM