All Projects → devemio → Elasticsearch Eloquent

devemio / Elasticsearch Eloquent

Licence: mit
⚡️ Eloquent models for Elasticsearch.

Projects that are alternatives of or similar to Elasticsearch Eloquent

Laravel Deletable
👾 Gracefully restrict deletion of Laravel Eloquent models
Stars: ✭ 137 (+37%)
Mutual labels:  eloquent-models, laravel, laravel-package
Eloquent Approval
Approval process for Laravel Eloquent models
Stars: ✭ 79 (-21%)
Mutual labels:  eloquent-models, laravel, laravel-package
Eloquent Viewable
Associate views with Eloquent models in Laravel
Stars: ✭ 404 (+304%)
Mutual labels:  eloquent-models, laravel, laravel-package
Laravel Schedulable
Schedule and unschedule eloquent models elegantly without cron jobs
Stars: ✭ 78 (-22%)
Mutual labels:  eloquent-models, laravel, laravel-package
Elasticquent
Maps Laravel Eloquent models to Elasticsearch types
Stars: ✭ 1,172 (+1072%)
Mutual labels:  eloquent-models, elasticsearch, laravel
Laravel Console Logger
Logging and Notifications for Laravel Console Commands.
Stars: ✭ 79 (-21%)
Mutual labels:  laravel, laravel-package
Laravel Url Shortener
Powerful URL shortening tools in Laravel
Stars: ✭ 80 (-20%)
Mutual labels:  laravel, laravel-package
Bigbluebutton
Package that provides easily communicate between bigbluebutton server and laravel framework
Stars: ✭ 85 (-15%)
Mutual labels:  laravel, laravel-package
Laravel Sync Migration
Developer tool helps to sync migrations without refreshing the database
Stars: ✭ 89 (-11%)
Mutual labels:  laravel, laravel-package
Laravel Impersonate
Laravel Impersonate is a plugin that allows you to authenticate as your users.
Stars: ✭ 1,201 (+1101%)
Mutual labels:  laravel, laravel-package
Laravel Nullable Fields
Handles saving empty fields as null for Eloquent models
Stars: ✭ 88 (-12%)
Mutual labels:  eloquent-models, laravel
Elastic Scout Driver Plus
Extension for Elastic Scout Driver
Stars: ✭ 90 (-10%)
Mutual labels:  elasticsearch, laravel
Laravel Pdf
A Simple package for easily generating PDF documents from HTML. This package is specially for laravel but you can use this without laravel.
Stars: ✭ 79 (-21%)
Mutual labels:  laravel, laravel-package
Laravel Schedule List
Laravel package to add command to list all scheduled artisan commands
Stars: ✭ 84 (-16%)
Mutual labels:  laravel, laravel-package
Admin One Laravel Dashboard
Admin One — Free Laravel Dashboard (Bulma Buefy Vue.js SPA)
Stars: ✭ 94 (-6%)
Mutual labels:  laravel, laravel-package
Laravel Id Generator
Easy way to generate custom ID from database table in Laravel framework.
Stars: ✭ 88 (-12%)
Mutual labels:  laravel, laravel-package
Dropzone Laravel Image Upload
Laravel 5.2 and Dropzone.js auto image uploads with removal links
Stars: ✭ 92 (-8%)
Mutual labels:  laravel, laravel-package
Lara Lens
Laravel package for display diagnostic (config, database, http connections...)
Stars: ✭ 96 (-4%)
Mutual labels:  laravel, laravel-package
Laravel Google Translate
This package makes using the Google Translate API in your laravel app a breeze with minimum to no configuration, clean syntax and a consistent package API.
Stars: ✭ 97 (-3%)
Mutual labels:  laravel, laravel-package
Laraupdater
Enable Laravel App Self-Update. Allow your Laravel Application to auto-update itself.
Stars: ✭ 75 (-25%)
Mutual labels:  laravel, laravel-package

Persimmon / Elasticsearch Eloquent

Latest Version on Packagist Software License Build Status Coverage Status Quality Score Total Downloads

This package allows you to interact with Elasticsearch as you interact with Eloquent models in Laravel.
Feel free to improve the project.

Install

Via Composer

$ composer require isswp101/elasticsearch-eloquent

Usage

Configure dependencies

Warning! First of all you should create a base model and inherit from it their models.

use Elasticsearch\Client;
use Isswp101\Persimmon\DAL\ElasticsearchDAL;
use Isswp101\Persimmon\ElasticsearchModel as Model;
use Isswp101\Persimmon\Event\EventEmitter;

class ElasticsearchModel extends Model
{
    public function __construct(array $attributes = [])
    {
        $dal = new ElasticsearchDAL($this, app(Client::class), app(EventEmitter::class));

        parent::__construct($dal, $attributes);
    }

    public static function createInstance()
    {
        return new static();
    }
}

In this example we use Laravel IoC Container to resolve Elasticsearch\Client dependency as app(Client::class).

Create a new model

You must override static variables index and type to determine the document path.

class Product extends ElasticsearchModel
{
    protected static $_index = 'test';
    protected static $_type = 'test';

    public $name;
    public $price = 0;
}

Here name and price are fields which will be stored in Elasticsearch.

Warning! Don't use field names starting with underscore $_*, for example $_name.

Use the static create() method to create document in Elasticsearch:

$product = Product::create(['id' => 3, 'name' => 'Product 3', 'price' => 30]);

Save the model

$product = new Product();
$product->id = 1;
$product->name = 'Product 1';
$product->price = 20;
$product->save();

Use save() method to store model data in Elasticsearch. Let's see how this looks in Elasticsearch:

{
   "_index": "test",
   "_type": "test",
   "_id": "1",
   "_version": 1,
   "found": true,
   "_source": {
      "name": "Product 1",
      "price": 10,
      "id": 1,
      "user_id": null,
      "created_at": "2016-06-03 08:11:08",
      "updated_at": "2016-06-03 08:11:08"
   }
}

Fields created_at and updated_at were created automatically. The user_id field is persistent field to store user id.

Find existing model

$product = Product::find(1);

If you have big data in Elasticsearch you can specify certain fields to retrieve:

$product = Product::find(1, ['name']);

In this case the price field equals 0 because it's populated as the default value that you specified in the model.

There are the following methods:

  • findOrFail() returns ModelNotFoundException exception if no result found.
  • findOrNew() returns a new model if no result found.

Model cache

There is a smart model cache when you use methods like find(), findOrFail() and so on.

$product = Product::find(1, ['name']);  // will be retrieved from the elasticsearch
$product = Product::find(1, ['name']);  // will be retrieved from the cache
$product = Product::find(1, ['price']); // elasticsearch
$product = Product::find(1, ['price']); // cache
$product = Product::find(1, ['name']);  // cache
$product = Product::findOrFail(1);      // elasticsearch
$product = Product::find(1);            // cache
$product = Product::find(1, ['name']);  // cache
$product = Product::find(1, ['price']); // cache

Partial update

You can use partial update to update specific fields quickly.

$product = Product::find(1, ['name']);
$product->name = 'Product 3';
$product->save('name');

Delete models

$product = Product::find(1);
$product->delete();

You can use the static method:

Product::destroy(1);

Model events

Out of the box you are provided with a simple implementation of events.
You can override the following methods to define events:

  • saving() is called before saving, updating, creating the model
  • saved() is called after saving, updating, creating the model
  • deleting() is called before deleting the model
  • deleted() is called after deleting the model

For example:

class Product extends ElasticsearchModel
{
    public static $_index = 'test';
    public static $_type = 'test';

    public $name;
    public $price = 0;

    protected function saving()
    {
        if ($this->price <= 0) {
            return false;
        }

        return true;
    }

    protected function deleting()
    {
        if (!$this->canDelete()) {
            throw new LogicException('No permissions to delete the model');
        }

        return true;
    }
}

Basic search

There are helpers to search documents:

The first($query) method returns the first document according to the query or null.

$product = Product::first($query);

The firstOrFail($query) method returns ModelNotFoundException exception if first($query) returns null.

$product = Product::firstOrFail($query);

The search($query) method returns documents (default 50 items) according to the query.

$products = Product::search($query);

The map($query, callable $callback) method returns all documents (default 50 items per request) according to the query.

$total = Product::map([], function (Product $product) {
    // ...
});

The all($query) method returns all documents according to the query.

$products = Product::all($query);

If $query is not passed the query will be as match_all query.

Query Builder

use Isswp101\Persimmon\QueryBuilder\QueryBuilder;

$query = new QueryBuilder();

Simple usage:

$query = new QueryBuilder(['query' => ['match' => ['name' => 'Product']]]);
$products = Product::search($query);

The match query:

$query = new QueryBuilder();
$query->match('name', 'Product');
$products = Product::search($query);    

The range query:

$query = new QueryBuilder();
$query->betweenOrEquals('price', 20, 30)->greaterThan('price', 15);
$products = Product::search($query);

Filters

Feel free to add your own filters.

The TermFilter filter:

$query = new QueryBuilder();
$query->filter(new TermFilter('name', '2'));
$products = Product::search($query);

The IdsFilter filter:

$query = new QueryBuilder();
$query->filter(new IdsFilter([1, 3]));
$products = Product::search($query);

The RangeOrExistFilter filter:

$query = new QueryBuilder();
$query->filter(new RangeOrExistFilter('price', ['gte' => 20]));
$products = Product::search($query);

Aggregations

Feel free to add your own aggregations.

$query = new QueryBuilder();
$query->aggregation(new TermsAggregation('name'));
$products = Product::search($query);
$buckets = $products->getAggregation('name');
// Usage: $buckets[0]->getKey() and $buckets[0]->getCount()

Parent-Child Relationship

The parent-child relationship is similar in nature to the nested model: both allow you to associate one entity with another. The difference is that, with nested objects, all entities live within the same document while, with parent-child, the parent and children are completely separate documents.

Let's create two models:

  1. PurchaseOrder has many PurchaseOrderLine models
  2. PurchaseOrderLine belongs to PurchaseOrder model
class PurchaseOrder extends ElasticsearchModel
{
    protected static $_index = 'test_parent_child_rel';
    protected static $_type = 'orders';

    public $name;

    public function lines()
    {
        return $this->hasMany(PurchaseOrderLine::class);
    }
}

class PurchaseOrderLine extends ElasticsearchModel
{
    protected static $_index = 'test_parent_child_rel';
    protected static $_type = 'lines';
    
    protected static $_parentType = 'orders';

    public $name;

    public function po()
    {
        return $this->belongsTo(PurchaseOrder::class);
    }
}

To save() models you can use the following code:

$po = new PurchaseOrder(['id' => 1, 'name' => 'PO1']);
$line = new PurchaseOrderLine(['id' => 1, 'name' => 'Line1']);

$po->save();
$po->lines()->save($line);

You can use the associate() method to save models:

$po = new PurchaseOrder(['id' => 1, 'name' => 'PO1']);
$line = new PurchaseOrderLine(['id' => 1, 'name' => 'Line1']);

$po->save();
$line->po()->associate($po);
$line->save();

To get parent you can use the following code:

$line = PurchaseOrderLine::findWithParentId(1, 1);
$po = $line->po()->get();

To get children you can use the following code:

$po = PurchaseOrder::findOrFail(1);
$line = $po->lines()->find(1); // by id
$lines = $po->lines()->get(); // all children

Inner hits

The parent/child and nested features allow the return of documents that have matches in a different scope. In the parent/child case, parent document are returned based on matches in child documents or child document are returned based on matches in parent documents. In the nested case, documents are returned based on matches in nested inner objects.

You can get parent model using only one request with InnerHitsFilter filter:

$query = new QueryBuilder();
$query->filter(new InnerHitsFilter(PurchaseOrderLine::getParentType()));
$line = PurchaseOrderLine::search($query)->first();
$po = $line->po()->get(); // will be retrieved from inner_hits cache

Logging and data access layer events

To debug all elasticsearch queries to search you can use own DALEmitter class:

use Isswp101\Persimmon\DAL\DALEvents;
use Isswp101\Persimmon\Event\EventEmitter;

class DALEmitter extends EventEmitter
{
    public function __construct()
    {
        $this->on(DALEvents::BEFORE_SEARCH, function (array $params) {
            Log::debug('Elasticsearch query', $params);
        });
    }
}

And configure it in your service provider:

use Elasticsearch\Client;
use Isswp101\Persimmon\DAL\ElasticsearchDAL;
use Isswp101\Persimmon\ElasticsearchModel as Model;
use Isswp101\Persimmon\Test\Models\Events\DALEmitter;

class ElasticsearchModel extends Model
{
    public function __construct(array $attributes = [])
    {
        $dal = new ElasticsearchDAL($this, app(Client::class), app(DALEmitter::class));

        parent::__construct($dal, $attributes);
    }
    // ...
}

There are the following events:

  • DALEvents::BEFORE_SEARCH is triggered before any search.
  • DALEvents::AFTER_SEARCH is triggered after any search.

TO BE CONTINUED...

@TODO:

  • Add documentation about filters

Change log

Please see CHANGELOG for more information what has changed recently.

Testing

$ composer test

Contributing

Please see CONTRIBUTING and CONDUCT for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].