Skip to content

Model Relationships in Elasticsearch

In Laravel applications, model relationships are crucial for structuring complex data and interactions between different entities. The Laravel-Elasticsearch integration supports a variety of relationship types, enabling Elasticsearch documents to relate to each other similarly to how models relate in traditional relational databases. This section provides a comprehensive guide on implementing Elasticsearch-to-Elasticsearch model relationships within a Laravel application.

Defining Relationships

Just like in a traditional Laravel Eloquent model, you can define belongsTo, hasMany, hasOne, morphOne, and morphMany relationships in models that use Elasticsearch as their data storage. Here’s a full example illustrating various relationship types in an Elasticsearch context:


Common relationships example

Using belongsTo, hasMany, hasOne, morphOne, and morphMany relationships.

Relationship Diagram

Illustrates how different models can interconnect within Elasticsearch, maintaining structured data interactions without the need for traditional joins.

Diagram

Company Model

/**
* App\Models\Company
*
******Fields*******
* @property string $_id
* @property string $name
* @property integer $status
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*
******Relationships*******
* @property-read CompanyLog $companyLogs
* @property-read CompanyProfile $companyProfile
* @property-read Avatar $avatar
* @property-read Photos $photos
*
* @mixin \Eloquent
*
*/
class Company extends Model
{
protected $connection = 'elasticsearch';
//Relationships =====================================
public function companyLogs()
{
return $this->hasMany(CompanyLog::class);
}
public function companyProfile()
{
return $this->hasOne(CompanyProfile::class);
}
public function avatar()
{
return $this->morphOne(Avatar::class, 'imageable');
}
public function photos()
{
return $this->morphMany(Photo::class, 'photoable');
}
}

CompanyLog Model

/**
* App\Models\CompanyLog
*
******Fields*******
* @property string $_id
* @property string $company_id
* @property string $title
* @property integer $code
* @property mixed $meta
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*
******Relationships*******
* @property-read Company $company
*
* @mixin \Eloquent
*
*/
class CompanyLog extends Model
{
protected $connection = 'elasticsearch';
//Relationships =====================================
public function company()
{
return $this->belongsTo(Company::class);
}
}

Avatar Model

/**
* App\Models\Avatar
*
******Fields*******
* @property string $_id
* @property string $url
* @property string $imageable_id
* @property string $imageable_type
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*
******Relationships*******
* @property-read Company $company
*
* @mixin \Eloquent
*
*/
class Avatar extends Model
{
protected $connection = 'elasticsearch';
//Relationships =====================================
public function imageable()
{
return $this->morphTo();
}
}

Photo Model

/**
* App\Models\Photo
*
******Fields*******
* @property string $_id
* @property string $url
* @property string $photoable_id
* @property string $photoable_type
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*
******Relationships*******
* @property-read Company $company
*
* @mixin \Eloquent
*
*/
class Photo extends Model
{
protected $connection = 'elasticsearch';
//Relationships =====================================
public function photoable()
{
return $this->morphTo();
}
}

Example Usage

$company = Company::first();
$companyLogs = $company->companyLogs->toArray(); //Shows all company logs (has many)
$companyProfile = $company->companyProfile->toArray(); //Shows the company profile (has one)
$companyAvatar = $company->avatar->toArray(); //Shows the company avatar (morph one)
$companyPhotos = $company->photos->toArray(); //Shows the company photos (morph many)

This comprehensive example demonstrates the application of various relationship types in a Laravel-Elasticsearch context, providing insights into effective data structuring for complex applications.


Many to Many (belongsToMany)

Relationship Diagram

The following diagram illustrates how a many-to-many (polymorphic) relationship functions in Elasticsearch:

Diagram

Migration

public function up(): void
{
Schema::create('users', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
});
Schema::create('roles', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
});
// Pivot index, automatically managed by belongsToMany from the User and Role models
Schema::create('role_user', function (Blueprint $index): void {
$index->keyword('role_id');
$index->keyword('user_id');
});
}

User Model

/**
* App\Models\User
*
******Fields*******
* @property string $_id
* @property string $name
*
******Relationships*******
* @property-read Role[] $roles
*/
class User extends Model
{
protected $connection = 'elasticsearch';
public function roles()
{
return $this->belongsToMany(Role::class);
}
}

Role Model

/**
* App\Models\Role
*
******Fields*******
* @property string $_id
* @property string $name
*
******Relationships*******
* @property-read User[] $users
*/
class Role extends Model
{
protected $connection = 'elasticsearch';
public function users()
{
return $this->belongsToMany(User::class);
}
}

Example Usage

// Returing relationships
$user = User::first();
$roles = $user->roles->toArray(); //Shows all roles (belongs to many)
$role = Role::first();
$users = $role->users->toArray(); //Shows all users (belongs to many)
// Creating relationships
$user = User::first();
$user->roles()->attach([$roleId1,$roleId2]); //Attach roles to a user
$user->roles()->create(['name' => 'Can sing']); //Create a role and attach to user

One to Many (Polymorphic)

The one-to-many relationship typically involves a single parent model (e.g., a post) linked to multiple child models (e.g., comments). This relationship is crucial for scenarios where an entity can own or be associated with multiple other entities.

Relationship Diagram

Illustrates a straightforward one-to-many relationship between posts and their comments in an Elasticsearch environment.

Diagram

Index Structure

Documents in Elasticsearch are designed to support relationships directly within their structures:

posts
_id - objectid
name - keyword / text
videos
_id - objectid
name - keyword / text
comments
_id - objectid
body - text
commentable_id - keyword
commentable_type - keyword

Migration

The schema for posts and comments is defined to facilitate direct referencing between a post and its associated comments:

public function up(): void
{
Schema::create('posts', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
});
Schema::create('videos', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
});
Schema::create('comments', function (Blueprint $index): void {
$index->text('body');
$index->keyword('commentable_id');
$index->keyword('commentable_type');
});
}

Implementation Details

Managing one-to-many relationships in Laravel with Elasticsearch does not involve complex pivot table operations. Instead, the relationship is maintained through direct document references:

  • Creation: When creating comments, the post_id field is used to link each comment to its respective post.
  • Querying: To retrieve all comments for a post, Elasticsearch queries are designed to look up all comments where the post_id matches the post’s ID.
  • Updates and Deletes: Modifying or deleting a post or its comments is handled through direct operations on the respective documents.

This approach enhances the performance and scalability of applications by leveraging Elasticsearch’s powerful indexing and search capabilities.

Many to Many (Polymorphic)

In a polymorphic many-to-many setup, each model type can relate to multiple instances of another model type through a shared identifier. This is managed in Elasticsearch by storing arrays of references directly on the model documents, avoiding the use of an additional table.

Relationship Diagram

The following diagram illustrates how a many-to-many (polymorphic) relationship functions in Elasticsearch:

Diagram

Index Structure

In Elasticsearch, the document structure reflects the flexibility needed to accommodate the polymorphic nature of relationships:

posts
_id - objectid
name - keyword / text
tag_id - keyword
videos
_id - objectid
name - keyword / text
tag_id - keyword
tags
_id - objectid
name - keyword / text
taggables - nested
taggable_id - keyword
taggable_type - keyword

Each ‘tag’ can relate to ‘posts’ and ‘videos’ through the taggables nested field, which stores multiple relationships in the form of taggable_id and taggable_type.

Migration

public function up(): void
{
Schema::create('posts', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
$index->keyword('tag_id');
});
Schema::create('videos', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
$index->keyword('tag_id');
});
Schema::create('tags', function (Blueprint $index): void {
$index->text('name', hasKeyword: true);
$index->keyword('tag_id');
$index->nested('taggables');
});
}

Implementation Details

In Laravel, managing these relationships involves standard methods like attach(), detach(), and sync(), which are adapted to work without pivot tables. For example, when you associate a post with a tag, the tag document’s taggables field is updated to include the post’s _id and a taggable_type of ‘post’.

This Elasticsearch-driven approach provides a scalable and efficient way to manage polymorphic relationships, leveraging Elasticsearch’s capabilities for handling nested structures and dynamic queries.