Installation
composer require pdphilip/opensearch
Publish the config file:
php artisan vendor:publish --provider="PdPhilip\OpenSearch\OpenSearchServiceProvider" --tag="config"
Configure OpenSearch Connection
Edit .env or config/opensearch.php:
OPENSEARCH_HOST=http://localhost:9200
OPENSEARCH_USERNAME=admin
OPENSEARCH_PASSWORD=admin
Basic Model Setup
Extend your Eloquent model with PdPhilip\OpenSearch\Eloquent\Model:
use PdPhilip\OpenSearch\Eloquent\Model;
class Product extends Model
{
protected $connection = 'opensearch'; // Optional if default connection is set
protected $index = 'products'; // Required: OpenSearch index name
protected $primaryKey = 'id'; // Optional: Defaults to 'id'
}
First Query
$products = Product::where('name', 'like', '%phone%')->get();
Indexing Data
php artisan opensearch:import App\Models\Product
Or manually:
$product = new Product();
$product->save();
config/opensearch.php for connection settings and default behaviors.php artisan list to explore available commands like opensearch:import and opensearch:reindex.Basic CRUD:
// Create
$product = new Product(['name' => 'Laptop', 'price' => 999]);
$product->save();
// Read
$product = Product::find(1);
$products = Product::all();
// Update
$product->price = 899;
$product->save();
// Delete
$product->delete();
Querying:
// Basic where
$products = Product::where('price', '>', 500)->get();
// Full-text search
$products = Product::search('wireless headphones')->get();
// Aggregations
$results = Product::selectRaw('avg(price) as avg_price')->get();
Import Data:
php artisan opensearch:import App\Models\Product --chunk=100
Or programmatically:
Product::import(); // Uses default chunk size
Product::import(200); // Custom chunk size
Reindexing:
php artisan opensearch:reindex App\Models\Product
class Category extends Model
{
public function products()
{
return $this->hasMany(Product::class);
}
}
class Product extends Model
{
protected $mapping = [
'properties' => [
'name' => ['type' => 'text', 'analyzer' => 'english'],
'price' => ['type' => 'float'],
'tags' => ['type' => 'keyword']
]
];
}
protected static function boot()
{
parent::boot();
static::booted(function () {
if (!static::indexExists()) {
static::createIndex();
}
});
}
use PdPhilip\OpenSearch\Events\DocumentSaved;
use Illuminate\Support\Facades\Event;
Event::listen(DocumentSaved::class, function ($event) {
logger()->info("Document saved: {$event->model->id}");
});
// Full-text search in OpenSearch
$results = Product::search('wireless')->get();
// Transactional data in SQL
$product = Product::find(1);
$product->price = $product->price - 100;
$product->save(); // Saves to SQL
$products = Cache::remember('featured_products', now()->addHours(1), function () {
return Product::where('is_featured', true)->limit(10)->get();
});
$products = Product::paginate(15);
PdPhilip\OpenSearch\Testing\CreatesOpenSearchIndexes trait:
use PdPhilip\OpenSearch\Testing\CreatesOpenSearchIndexes;
use Tests\TestCase;
class ProductTest extends TestCase
{
use CreatesOpenSearchIndexes;
public function test_search()
{
$this->createIndex(Product::class);
// Test logic here
}
}
createIndex() in model boot:
protected static function boot()
{
parent::boot();
static::booted(function () {
if (!static::indexExists()) {
static::createIndex();
}
});
}
float field).
casts:
protected $casts = [
'price' => 'float',
'is_active' => 'boolean',
];
php artisan opensearch:import App\Models\Product --chunk=50
Product::import(50, true); // Enable logging
// Example: Store category as nested object
$product = new Product([
'name' => 'Laptop',
'category' => ['id' => 1, 'name' => 'Electronics']
]);
config/opensearch.php:
'timeout' => 30, // seconds
'retries' => 3,
config/opensearch.php:
'debug' => env('OPENSEARCH_DEBUG', false),
storage/logs/laravel.log for raw OpenSearch queries.toSearch() to inspect the generated query:
$query = Product::where('name', 'like', '%phone%')->toSearch();
logger()->info($query);
if (!Product::indexExists()) {
logger()->error('Index does not exist!');
}
Product::validateMapping();
namespace App\OpenSearch\Query;
use PdPhilip\OpenSearch\Builder;
class ProductQueryBuilder extends Builder
{
public function inStock()
{
return $this->where('stock', '>', 0);
}
}
How can I help you explore Laravel packages today?