fereydooni / laravel-elastoquent
A robust Elasticsearch ORM for Laravel, mirroring Eloquent functionality
Requires
- php: ^8.1
- elasticsearch/elasticsearch: ^8.0
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^9.0|^10.0|^11.0|^12.0
- illuminate/pagination: ^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
- spatie/laravel-data: ^3.12.0|^4.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^8.35
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- phpstan/phpstan: ^1.10
This package is auto-updated.
Last update: 2025-04-24 20:11:13 UTC
README
A robust Elasticsearch ORM for Laravel, mirroring Eloquent functionality. This package provides a familiar Eloquent-like API for indexing, querying, and managing data in Elasticsearch.
Requirements
- PHP 8.1+
- Laravel 10.x+
- Elasticsearch 8.x
Installation
composer require fereydooni/laravel-elastoquent
Publish Configuration
php artisan vendor:publish --provider="Fereydooni\LaravelElastoquent\ElasticORMServiceProvider"
Run Migrations
php artisan migrate
Configuration
The package configuration is located at config/elastic-orm.php
. Key options include:
return [ // Elasticsearch connection settings 'connection' => [ 'hosts' => ['localhost:9200'], 'username' => env('ELASTIC_USERNAME', null), 'password' => env('ELASTIC_PASSWORD', null), ], // Default index prefix for all indices 'index_prefix' => env('ELASTIC_INDEX_PREFIX', 'app_'), // Enable/disable soft deletes 'soft_deletes' => true, // Bulk indexing batch size 'bulk_size' => 1000, ];
Basic Usage
Define a Model
use Fereydooni\LaravelElastoquent\Attributes\ElasticModel; use Fereydooni\LaravelElastoquent\Attributes\ElasticField; use Fereydooni\LaravelElastoquent\Models\Model; #[ElasticModel(index: 'users', settings: ['number_of_shards' => 1])] class User extends Model { #[ElasticField(type: 'text', index: true)] public string $name; #[ElasticField(type: 'keyword')] public string $email; public function posts() { return $this->hasMany(Post::class, 'user_id'); } }
CRUD Operations
// Create a user $user = User::create([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); // Find a user $user = User::find('user_id'); // Update a user $user->name = 'Jane Doe'; $user->save(); // Delete a user $user->delete();
Query Builder
// Basic where clause $users = User::where('name', 'John')->get(); // Advanced query $users = User::where('name', 'like', 'J*') ->orWhere('email', 'contains', 'example.com') ->orderBy('name', 'asc') ->limit(10) ->get(); // Pagination $users = User::where('active', true) ->paginate(15);
Full-Text Search
// Simple search $results = User::search('John Doe')->get(); // Advanced search with filters $results = User::search('John') ->filter('active', true) ->get();
Advanced Elasticsearch Features
Vector Search
// Define a model with vector fields #[ElasticModel(index: 'documents')] class Document extends Model { #[ElasticField(type: 'dense_vector', options: ['dims' => 768])] public array $embedding; #[ElasticField(type: 'text')] public string $content; } // Perform KNN vector search $results = Document::query() ->vectorSearch('embedding', $queryVector, k: 10) ->get();
Semantic Search
// Using embedding services (OpenAI, HuggingFace, etc.) $results = Document::query() ->semanticSearch( "What is machine learning?", 'embedding', 'openai' ) ->get(); // Sparse vector search with ELSER $results = Document::query() ->sparseVectorSearch("How does AI work?", 'content_embedding') ->get();
Hybrid Search & Semantic Reranking
// Hybrid search combining text and vector scores $results = Document::query() ->hybridSearch( "machine learning applications", ['content'], 'embedding', $queryVector, textWeight: 0.3, vectorWeight: 0.7 ) ->get(); // Semantic reranking to improve relevance $results = Document::query() ->search("artificial intelligence") ->semanticRerank("How do neural networks work?") ->get();
ES|QL Support
// Using Elasticsearch's SQL-like query language $results = Document::query() ->esql("FROM documents WHERE match(content, 'artificial intelligence') | LIMIT 10") ->get();
Performance Optimizations
// Field selection and exclusion $results = Document::query() ->select(['content', 'title']) // Only return these fields ->exclude(['embedding']) // Exclude large vector fields ->trackTotalHits(false) // Disable exact hit counting for better performance ->search("machine learning") ->get();
Data Transfer Objects & Pagination
Laravel Elastoquent integrates with spatie/laravel-data to provide structured DTOs for your Elasticsearch results:
// Using the built-in ElasticDocument DTO $results = Document::query() ->where('category', 'technology') ->asDocument() // Return results as ElasticDocument DTOs ->get(); // Using a custom DTO that extends ElasticDocument class ProductDocument extends ElasticDocument { public function getFormattedPrice(): string { return '$' . number_format($this->field('price', 0), 2); } } $results = Product::query() ->asData(ProductDocument::class) // Use custom DTOs ->get(); // Enhanced pagination with DTOs $paginatedResults = Product::query() ->where('active', true) ->asDocument() ->paginate(15); // Accessing pagination metadata echo "Showing {$paginatedResults->count()} of {$paginatedResults->total()} results"; echo "Page {$paginatedResults->currentPage()} of {$paginatedResults->lastPage()}"; // Converting to Spatie Data collection use Spatie\LaravelData\Data; class ProductData extends Data { public function __construct( public string $id, public string $name, public float $price, ) {} // Transform from ElasticDocument to Spatie Data public static function fromElasticDocument(ElasticDocument $doc): self { return new self( id: $doc->id, name: $doc->field('name'), price: (float)$doc->field('price', 0) ); } } // Map the results to Spatie Data objects $dataCollection = $results->map(fn($doc) => ProductData::fromElasticDocument($doc));
Retriever API Support
// Using retrievers for advanced semantic search $results = Document::query() ->retriever( "How does machine learning work?", ['content'], 'hybrid' // Use hybrid retrieval (text + semantic) ) ->get();
Relationships
// Define relationships class User extends Model { public function posts() { return $this->hasMany(Post::class, 'user_id'); } } class Post extends Model { public function user() { return $this->belongsTo(User::class, 'user_id'); } } // Using relationships $user = User::find('user_id'); $posts = $user->posts()->get(); // Eager loading $users = User::with('posts')->get();
Bulk Operations
// Bulk create User::bulkIndex([ ['name' => 'Jane Doe', 'email' => 'jane@example.com'], ['name' => 'Bob Smith', 'email' => 'bob@example.com'], ]);
User Model Example
Here's an example of how to use the User model with Elasticsearch and Spatie Data:
use Fereydooni\LaravelElastoquent\Examples\Models\User; use Fereydooni\LaravelElastoquent\Examples\Models\UserData; // Create a new user $user = new User([ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => 'hashed_password', 'age' => 30, 'is_active' => true, 'roles' => ['user', 'admin'], 'profile' => [ 'bio' => 'Software Developer', 'location' => 'New York', 'website' => 'https://example.com', 'avatar' => 'https://example.com/avatar.jpg' ] ]); // Save the user to Elasticsearch $user->save(); // Find a user by ID $user = User::find('user_id'); // Update user attributes $user->setName('Jane Doe'); $user->setAge(31); $user->save(); // Delete a user $user->delete(); // Query users $activeUsers = User::where('is_active', true) ->where('age', '>', 25) ->get(); // Convert to Spatie Data DTO $userData = $user->toData(); // Use DTO in API responses return response()->json($userData->toArray()); // Or use Spatie Data's collection $usersData = $activeUsers->map(fn($user) => $user->toData()); return response()->json($usersData->toArray());
Available Query Methods
The User model supports all standard Eloquent-like query methods:
// Basic queries User::all(); User::find('id'); User::first(); User::where('age', '>', 25)->get(); // Pagination User::paginate(15); User::simplePaginate(15); User::cursorPaginate(15); // Aggregations User::where('is_active', true)->count(); User::where('age', '>', 25)->exists(); // Nested queries User::where('profile.location', 'New York')->get(); // Sorting User::orderBy('created_at', 'desc')->get(); // Limiting User::limit(10)->get(); User::offset(5)->limit(10)->get();
Elasticsearch Features
The User model includes several Elasticsearch-specific features:
-
Nested Fields: The
profile
field is defined as a nested type, allowing for complex queries on nested objects. -
Field Types:
text
for full-text search (name, profile.bio)keyword
for exact matches (email, roles)integer
for numeric values (age)boolean
for true/false values (is_active)date
for timestamps (created_at, updated_at)
-
Index Settings:
- Single shard for consistent ordering
- One replica for high availability
-
Mapping:
- Custom field mappings for optimal search performance
- Nested object support for complex data structures
-
Spatie Data Integration:
- Automatic snake_case to camelCase conversion
- Type-safe data transfer objects
- Built-in validation and transformation
- Easy serialization to JSON
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This package is open-sourced software licensed under the MIT license.