Background
In our Laravel system, we have a Post which is e.g. either a VideoPost or a TextPost (the type is stored in a type
-attribute). Every Post (no matter what type it is) depends on translations, so we have made every Post have a PostTranslation, which contains a one-to-many relation to VideoPostTranslation or TextPostTranslation.
This means that for me to access the translated content of the Post, I first have to fetch the Post, then check the type, and fetch both the relevant PostTranslations and whatever sub-type it is (e.g. VideoPostTranslation).
We've now realized that this is sub-optimal, and we're hoping to get some help on how this can be achieved.
Goal
We want to transform our database to something like so:
[Post]
int: id
str: type // one of [video, text]
[VideoPostTranslation]
int: id
int: post_id
str: language_code
str: video_specific_data
[TextPostTranslation]
int: id
int: parent_id
str: language_code
str: text_specific_data
In this structure, there is no redundant data, and hopefully it's possible to get Laravel to help us with the querying of this.
Preferrably, we want to be able to write something like the following:
$posts = Post::with('translations')->get();
foreach($posts as $post) {
switch($post->type) {
case 'video':
// handle $post->translations[]->video_specific_data
case 'text':
// handle $post->translations[]->text_specific_data
}
}
We've seen that Laravel has a good system for morphing between classes, but it generally requires that the one containing the type-data is also the one which handles the morphing. In their one-to-many example, they use a commentable-system, where a Comment can refer to both Posts and Videos through the commentable
-relation, but I'm not sure how this would work in our case.
This, for example, will not work:
class Post extends Model
{
public function translations()
{
return $this->morphTo();
}
}
class VideoPostTranslation extends Model
{
public function post()
{
return $this->morphMany(Post::class, 'translations');
}
}
class TextPostTranslation extends Model
{
public function post()
{
return $this->morphMany(Post::class, 'translations');
}
}
What changes do we need to "implement"? Do we need to add an intermediary many-to-many table called PageTranslation to handle the the relationship? Or is it possible to get Laravel to work with the proposed database structure?
My Answer:
To achieve the desired structure and functionality in Laravel, you can use polymorphic relationships along with custom accessor methods. Here's how you can set it up:
1. Define the database structure as mentioned in the goal section:
php
// Post table
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('type');
});
// VideoPostTranslation table
Schema::create('video_post_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('post_id');
$table->string('language_code');
$table->string('video_specific_data');
});
// TextPostTranslation table
Schema::create('text_post_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('post_id');
$table->string('language_code');
$table->string('text_specific_data');
});
2. Define the models with polymorphic relationships:
php
class Post extends Model
{
public function translations()
{
return $this->morphTo();
}
}
class VideoPostTranslation extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
class TextPostTranslation extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
3. Use custom accessor methods in the Post model to access the specific translation data based on the post type:
php
class Post extends Model
{
public function translations()
{
return $this->morphTo();
}
public function getSpecificDataAttribute()
{
if ($this->type === 'video') {
return $this->translations->video_specific_data;
} elseif ($this->type === 'text') {
return $this->translations->text_specific_data;
}
}
}
4. Now you can retrieve posts with translations and access specific data based on the post type:
php
$posts = Post::with('translations')->get();
foreach ($posts as $post) {
echo $post->specific_data;
}
By using polymorphic relationships and custom accessor methods, you can achieve the desired functionality in Laravel with the proposed database structure.
Rate this post
4 of 5 based on 9431 votesComments