Basically the scenario is as follows. A section is a resource blueprint that can contain multiple fields, an Entry is a record of this blueprint.
class Entry extends Model
{
public function section()
{
return $this->hasOne('Section');
}
}
My problem so far:
So let's assume I have a Field model, where the field can have different types, and one value (its content).
Ideally the dynamic content property would be a one to one relation like
class Field extends Model
{
public function content()
{
return $this->hasOne('ContentModel');
}
}
However, the ContentModel class would be based on the type value of the field.
Each type represents a different typemodel, that knows how to write its value (for the sake of simplicity, let's assume there are 3 types, input, text, int, and corresponding 3 model classes ContentInput, ContentText, and ContentInt)
sections (Section)
- id (char, 36) // uuid
- label (varchar)
- handle (varchar)
entries (Section)
- id (char, 36) // uuid
- section_id (char, 36) // uuid of the section
fields (Field)
- id (integer)
- section_id (char, 36) // uuid of the section
- type (varchar) // the content model class
- handle (varchar)
- label (varchar)
content_input (ContentInput)
- id
- entry_id (char, 36) // entry record of a section
- field_id (integer)
- value (varchar)
content_text (ContentText)
- id
- entry_id (char, 36) // entry record of a section
- field_id (integer)
- value (text)
content_int (ContentInt)
- id
- entry_id (char, 36) // entry record of a section
- field_id (integer)
- value (integer)
// ... there're many more content types.
I was thinking of polymorphic relations, but they won't fit in this specific case.
Is there any efficient way to achieve this with eloquent?
[EDIT NOTES]:
I've updated the examples above as I was a bit unclear.
One possible solution would be to dynamically create the entry model class based on the section definition.
class Entry_249fcb61_4253_47d5_80ab_e012e19e7727 extends Entry
{
protected $with = ['gettext', 'getinput', 'getint'];
public function gettext()
{
return $this->hasMany('ContentText', 'entry_id');
}
// and so on
}
Still don't know if that's the way to go.
There is probably a better way but here is the solution I came up with for a similar problem I had
fields (Field)
- id
- inputId -> nullable -> default = null
- textId -> nullable -> default = null
- intId -> nullable -> default = null
Field.php
public function input()
{
return $this->hasOne('Content_input', 'id', 'inputId');
}
public function text()
{
return $this->hasOne('Content_text', 'id', 'textId');
}
public function integer()
{
return $this->hasOne('Content_int', 'id', 'intId');
}
you can load these relationships when you are retrieving Field and then you can check which content type each has by checking which of the 3 fields is not null
Related
I want to get nested relationship data from polymorphic table, I'm using MongoDB as database in my laravel app so you might notice that i don't use pivot table between movies and genres table for many to many relationship given the example below.
So what I want to achieve is to get the nested data like frontends > genres > movies all in one result.
let's say I have polymorphic frontends table, looks like this
frontends
id - integer
position - integer
frontable_id - integer
frontable_type - string
genres
id - integer
name - string
movie_ids - array[]
movies
id - integer
title - string
genre_ids - array[]
This is the model I have set
use Jenssegers\Mongodb\Eloquent\Model;
class Frontend extends Model
{
public function frontable()
{
return $this->morphTo(__FUNCTION__, 'frontable_type_api', 'frontable_id');
}
}
class Genre extends Model
{
public function movies()
{
return $this->belongsToMany(Movie::class, null, 'genre_ids', 'movie_ids');
}
}
class Movie extends Model
{
public function genres()
{
return $this->belongsToMany(Genre::class, null, 'movie_ids', 'category_ids');
}
}
What i have tried is to get the frontend table first and then its frontable_id relationship, not until the movies table
$frontends = Frontend::with('frontable.movies')->orderBy('position')->get();
return $frontends;
The relation movies has been declared in related model, but it won't get the movies data
Trying to implement a polymorphic many-to-many relationship, similar to the one in the documentation.
In my case, for label instead of tag. And using different table names
apples / oranges / ...
id - integer
name - string
label_types
id - integer
name - string
labels
label_type_id - integer -- foreign ID for LabelType
labeling_type - string
labeling_id - integer - ID of the model this labelType is used for
// Apple / Orange / ...
public function labels(LabelType $type = null)
{
// need to provide table name, as expected default table name is labelings
return $this->morphToMany(Label::class, 'labeling', 'labels');
}
// Label
public function apples()
{
return $this->morphedByMany(Apple::class, 'labeling', 'labels');
}
public function oranges()
{
return $this->morphedByMany(Orange::class, 'labeling', 'labels');
}
This results in an error.
$apple->labels;
Illuminate\Database\QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: 'labels'
select
`labels`.*,
`labels`.`label_id` as `pivot_label_id`,
`labels`.`label_type` as `pivot_label_type`
from
`labels`
inner join `labels` on `labels`.`id` = `labels`.`label_id`
where
`labels`.`label_id` = 1
and `labels`.`label_type` = 'App\Models\Apple'
In the example above I have added the table name to morphToMany().
Same error when using labelings as table name and adding to Label model: protected $table = 'labelings';
How do I get this to work? I do want to use label_types as it better fits with the rest of my application.
I looked at other polymorphic questions on Stackoverflow, but could not find an answer.
This is more a design question than a coding question. Suppose the following schema:
// schema.prisma
// Solution 1
model Entity {
id Int #id #default(autoincrement())
attrs EntityAttr[]
}
model EntityAttr {
id Int #id #default(autoincrement())
value Json // or String, doesnt matter much here
// the point is I need to attach info on the
// join table of this relation
attr Attr #relation(fields: [attrId], references: [id])
entity Entity #relation(fields: [entityId], references: [id])
entityId Int
attrId Int
##unique([entityId, attrId])
}
model Attr {
id Int #id #default(autoincrement())
entities EntityAttr[]
}
// Solution 2
model Entity {
id Int #id #default(autoincrement())
dateAttrs DateAttr[]
recordAttrs RecordAttr[]
// ... this pattern could continue for more Attr-like models
}
model DateAttr {
id Int #id #default(autoincrement())
name String
entity Entity #relation(fields: [entityId], references: [id])
value DateTime // Stronger typing in generated code
}
model RecordAttr {
// ... define another Entity #relation(...)
name String
value String
// ...
}
// ... and so on
Please note that the schema might not be 100% complete or accurate. It is mainly to get the point across.
Solution 1 has its merits where redundancy and the number of tables in the database is reduced significantly (depending on the number of Attrs). Its downfall comes as confusing queries*, possible case-specific type casting and no code-completion for the value field for each Attr-like model.
* by confusing, I mean that the option for simplified m-n queries in prisma is functionally disabled when using a custom join table (e.g. EntityAttr)
Solution 2 has its merits where the generated code results in more strongly typed code generation for the value field, however it falls in the number of generated tables (I don't actually know if more tables is a good thing or a bad thing, all I think is that if you have similar values, they ought to be in the same table).
What would you do in my shoes?
I was looking pretty long for an appropriate answer and found it here.
I'm not sure if it could be applied to your question, but this is question about prisma and polymorphism, so I think this code snippet might be useful for developers:
model Photo {
id Int #id #default(autoincrement())
likes Like[] #relation("PhotoLike")
}
model Video {
id Int #id #default(autoincrement())
likes Like[] #relation("VideoLike")
}
enum LikableType {
Photo
Video
}
model Like {
id Int #id #default(autoincrement())
Photo Photo? #relation("PhotoLike", fields: [likableId], references: [id], map: "photo_likableId")
Video Video? #relation("VideoLike", fields: [likableId], references: [id], map: "video_likableId")
likableId Int
likableType LikableType
}
Resuling relations in dbdocs:
Sometimes the use case can't be generalized to abstract and have a typing's.
if you control them and has a limited attribute sure you can create each attribute as a separate table each has it is own schema.
Some Times more freedom is needed or the blocks are dynamic.
Use Case: Build A Block Document Editor Like 'notion.so' and you want to let the user create custom blocks or configure them.
you can do it like :
model Document {
id String #id
blocks Block[]
}
model Block {
id String #id
value Json
index Int
customConfig Json?
document Document? #relation(fields: [documentID], references: [id])
documentID String?
blockType BlockType #relation(fields: [blockTypeID], references: [id])
blockTypeID String
}
model BlockType {
id String #id
name String
config Json
blocks Block[]
}
where config and custom config can contains html,custom css classes, link attribute color or anything.
using type script you can create block.types.ts and add different let say templates for the config's .
I hope that I was useful to you, To sum it, it depends on the requirements :>)
I will use the db example below (modified the example in laravel docs) to explain.
Table structure (modified example)
posts
id - integer
name - string
videos
id - string // here I have to use "string"
name - string
tags
id - integer
name - string
taggables
tag_id - string // should normally be "integer"
taggable_id - string // should normally be "integer"
taggable_type - string
Model Relationship (modified example)
// Post.php
public function tags()
{
return $this->morphToMany( Tag::class, 'taggable', 'taggables', 'taggable_id', 'tag_id');
}
// Video.php
public function tags()
{
return $this->morphToMany( Tag::class, 'taggable', 'taggables', 'taggable_id', 'tag_id');
}
// Tag.php
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable', 'taggables', 'tag_id', 'taggable_id');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable', 'taggables', 'tag_id', 'taggable_id');
}
In my project I need to use the videos table id type as string. According to this, being able to store the relation "videos<->tags" the tabbables table's taggable_id must be a string as well, otherwise I can't store the relation.
As long as NO "video<->tag" relation exist it the table everything works fine but as soon as I add even one single "video<->tag" relation the problem starts.
I can use all available methods on the "video<->tags" relation without any problem. $video->tags()->attach([1,2]) or $video->tags()->sync() or $video->tags()->detach() are all working.
But the post<->tag relation's methods have problems. Altough I can attach relations like $post->tags()->attach([1,2]) or call like $post->tags, I can't use $post->tags()->sync() or $post->tags()->detach() methods. When I try, I get this error below:
Illuminate/Database/QueryException with message 'SQLSTATE[22007]: Invalid datetime format:
1292 Truncated incorrect DOUBLE value: 'here_string_id_of_video' (SQL: delete from
`taggables` where `taggable_id` = 300 and `taggable_type` = App/Models/Video)'
This is a uncommon and tricky situation. I know that this is logical because the query expects the serched record type to be ingeter. But I need a way to overcome this problem. Any recommendations and views would be appreciated.
UPDATE
Those are some solutions that could come to mind.
I could use string type for the other tables as well. Which does not sound plausible. This would need a lot of work.
I could process the intermediate table manually with a model.
App\Models\Taggable::where('taggable_id', '300')
->where('taggable_type', 'App\Models\Video')->delete()
I could (as #GauravGupta suggested) create an extra id field (external_id) that is string type and keep the native integer id intact.
i have 4 table
inst
id
subject
report
id
subject
object
id
type
container
id
obj_id
i want with obj_id of container table, achieve the The corresponding object record.
then if type of object = 1, fetch data from inst table or type = 2, fetch data from report table
based on your information,
you can do like #Mahfuz Shishir answer
or
create new appends attribute inside Object Model
protected $appends = ['data'];
public function getDataAttribute()
{
if($this->attributes['type'] == 1) {
return Inst::where('inst_column', $this->attributes['object_column_to_match']); //this just sample
} else {
return Report::where('inst_column', $this->attributes['object_column_to_match']); //this just sample
}
}
The relation looks like.
In your Object.php Model
public function containers()
{
return $this->hasMany('App\Container', 'foreign_id');
}
And in your Container.php Model
public function object()
{
return $this->belongsTo('App\Object', 'foreign_id');
}
You provide little information. Implement rest of the controller code in your own sense. Or put some code that you have tried.