ov-code/elasticoc

A Object oriented client for Elastic.No more infinite arrays that you can't control. No more extra fields, no more dates as string. Now Elastic will be work as it sould work

1.6 2020-02-07 10:05 UTC

This package is not auto-updated.

Last update: 2024-09-26 14:14:36 UTC


README

Install

composer require ov-code/elasticoc

Features

  • Annotation system for objects. No more infinity methods to check if is a valid Elasticsearch Object
  • Management of the ElasticsearchClient. You don't need to create the hosts or anything, just leave ElasticOc do that job for you
  • Get|Search Functions like Laravel. Get all the objects that pass a filter in only one line, no more whiles, for and Elastic Scrolls
  • Easier to make timeseries, just put the index with a '?' in the part it has to remplace.

ToDo

  • Suport multiple TemplateValues in the Index
  • Objects to make the aggs easier to read
  • QueryBuilder
  • Complex Fields
  • Better cache

ElasticOc

Elasticsearch oficial client is not bad at all...trust me, i use Elastic with curl for a year. But, it has a problem...well...is the PhP problem, you never know what type is each variable. Apart of that you will ALWAYS forgot to filter some key in some array and it will make the objects dirty.

So, you can make two thousands filters, make functions to transform all that daytimes to a good format, etc etc...or you can install ElasticOc and forget about that.

How To Use It

Prepare the object

First, lets create a class that will be our object. Let's call it Product. We import the two classes that will need, the annotations and EsEntityV2, this will transform our object to an ElasticOc Object. Apart of that we will add the two more important part annotations, EsIndex and EsPrimaryKey

    <?php
    namespace App;
    use ElasticSearchOC\Entities\EsEntityV2;
    use ElasticSearchOC\Annotations  as ElasticAnnotations;
    
	/**
	* @ElasticAnnotations\EsIndex("products-Index")
	* @ElasticAnnotations\EsPrimaryKey('productId')
	*/
    class Product extends EsEntityV2{}

Ok this wil be the header of our object, this will tell what and where is the index, and what property to use for the _uid in Elasticsearch.

EsIndex

This is the most important Annotations, we will use for mounting the host configuration, and the index where this object is alocated. If you need multiple hosts, you can use more than one Annotations of this type.

The properties of this annotations are:

  • Index*
  • TemplateField
  • TimeFormat
  • Url
  • Port
  • Protocol
  • User
  • Pass
  • sslVerification

Index, TemplateField & TimeFormat

This is easy, is the Index where this object will be save...yeah ok, this is Elastic so, how do you save in a timeseries? Easy, you put a ? where the changing part is, you have to set the property TemplateField to the property will be use to replace that ? to a proper string.

So, imagine we want to create an index, for each manufacturer, this will be the code

    /**
    @ElasticAnnotations\EsIndex("product-index-?",templateField="manufacturer")
    */

BUT, BUT, because always there is a but, imagine if we save a product with the manufacturer Jacobs, and we change it to Maliwan, if you save the object like that you will end with a copy, one in Jacobs, and one in Maliwan. For now ElasticOc doesn´t auto delete the first one.

If you use a EsFieldDate as templateField, by default, it will transform it to a dateFormat of Ym if you want to use a custom one, set the property timeformat

    /**
    @ElasticAnnotations\EsIndex("product-index-?",templateField="created",timeFormat='Y-m-d')
    */
URL, Protocol and that importants part

By default the index will aim to localhost:9200 using http. You can set the this envs to change the default parameters:

  • ES_PROTOCOL
  • ES_URL
  • ES_PORT
  • ES_USER
  • ES_PASSWORD

And if you need two hosts? No problem, you can set the env to use in the annotation, you can even hardcode the values if you want...but of course, this is not a good practice

    /**
    * @ElasticAnnotations\EsIndex("product-index", protocol='ES_PROTOCOL_2', url='ES_URL_2', port='ES_PORT_2', user='root', pass='please_dont_do_this')
    */
sslVerification

If you work some time with Elastic you know how a pain in the ass can be to put a ssl certificate to Elastic, if for some reason you want to disable the certificate check, put this property to false, by default is true

EsPrimaryKey

This annotations it use to generate the _uid of the object. If you don't use this annotation every object you make will be set the a uniqid of PHP, starting with ID.

The properties of this annotations are:

  • Properties*
  • md5

This annotations doesn´t have much to config, you can put one property

    /**
    @ElasticAnnotations\EsPrimaryKey("productId")
    */

Or an array of properties

    /**
    @ElasticAnnotations\EsPrimaryKey({"productId","manufacturer"})
    */

The property md5 is a boolean, if is set to true, the primary key will be transform to MD5, perfect for objects with multiple PrimaryKey

EsFieldBasic

Lets move on and we will create three properties, productID, that will be our primary key, name and Price

    /**
    * @ElasticAnnotations\EsFieldBasic("productId", dataType="integer")
    */
    public $productId;
    
    /**
    * @ElasticAnnotations\EsFieldBasic("name", dataType="string")
    */
    public $name;
    
    /**
    * @ElasticAnnotations\EsFieldBasic("price", dataType="float",decimals="2")
    */
    public $price;

We will need to create the Gets and Sets of al the properties, or make them public. The first value, is the name of the property in Elasticsearch, it can be diferent from the one in PhP, the dataType is the type of the property.

The properties of this annotations are:

  • PropertyName*
  • canNull
  • canArray
  • default
  • dataTipe*
  • decimals*(If dataType is Float or NoDecimal)

DataType

  • String
  • Integer
  • Float
  • Bool
  • NoDecimal
NoDecimal Type

If you use Elastic for some time, you know the bitch that can be the auto type. So, why fight it? NoDecimal will save any decimal number multiple by 10 * N (Number of decimals). So if you have 2.25, in Elastic will be 225. This will avoid many errors with the index Type

CanNull

Elastic maybe doesn´t have primary keys, or unique fields...or anything like that, but at least now we can have an error if the code try to put a null where it souldn´t . Use the property canNull an avoid that importants fields as null

    /**
    * @ElasticAnnotations\EsFieldBasic("price", dataType="float",decimals="2", canNull=false)
    */
Default Value

Sometimes a 0 is more important than any number...but working with 0 and not nulls is so anoying, so use the property default if that variable was going to Elastic with a null, ElasticOc will set it for you

    /**
    * @ElasticAnnotations\EsFieldBasic("price", dataType="float",decimals="2",default=0)
    */
Arrays and CanArray

Maybe your products have multiple prices, for example, on effective price, and one catalog price, and you don´t want to create two properties for that. No problemo, ElasticOc, will transform all the properties, keeping the original keys. (For now it will transform ALL the fields to the same type).

But maybe you hate arrays, ElasticOc covers you. Put the property canArray to false, so if a property become an array it will crash when trying to save the object.

    /**
    * @ElasticAnnotations\EsFieldBasic("price", dataType="float",decimals="2", canArray=false)
    */

EsFieldDate

Let´s add a date to our Product object, like Date of Expiry.

    /**
    * @ElasticAnnotations\EsFieldDate("expiry")
    */
    public $expiry;

As you can see is really simple. You can use Strings or DateTime, but ElasticOc will transform this property to a DateTime before saving, only to apply the format of the date, so if you use a String becareful with the format.

The properties of this annotations are:

  • PropertyName*
  • canNull
  • canArray
  • default
  • saveFormat

SaveFormat

By Default the format will be the Atom format, Y-m-d\TH:i :sP, Why? Because Elastic will map this automatic to date, so you don't have to make the index yourself. But of course, you can set the one you like using saveFormat

    /**
    * @ElasticAnnotations\EsFieldDate("expiry", saveFormat='y-m-d')
    */
    public $expiry;

Save the object

So, we have created our product object, for this example it will have, ProductId as primaryKey, and the properties of ProductId, Manufacturer and Price. So if you want to save it, is easy as one Line

    $product = new Product();
    $product->productId = 1;
    $product->manufacturer = 'Jacobs';
    $product->price = 2.25;
    $product->save();

Maybe you need to save thousand objects...and you don't want to make thousand calls of course, Elastic is powerfull, but can´t make miracles. So we create a bulk

    Product::beginBulk();
    while($count<100){
        $product = new Product();
        $product->productId = $count;
        $product->price = $count*$count;
        $product->manufacturer = 'Jacobs';
        $product->save();
        $count++;
    }
    Product::commitBulk();

And if you don't want to create an object that is not created allready just change the function save to update

    Product::beginBulk();
    foreach($products as $product){
        $product->productId = $count;
        $product->price = $count*$count;
        $product->manufacturer = 'Jacobs';
        $product->update();
    }
    Product::commitBulk();

Get the object

For retrieving you have 4 diferents functions:

  • get
  • paginate
  • search

Get

This is the easy one, use this when you want one object, or a small collection. The default way of using is simple, just pase the _uid as parameter

    $product = Product::get(1);

Ok, and if you want two products? Use an array

    $product = Product::get([1,2]);

Yeah, ok, maybe is usefull, but what if you want to pass a filter? Maybe you want all the objects of one manufacturer, ElasticOc got your cover

    $product = Product::get(['manufacturer'=>'Jacobs'])

Multiple fields to filter? Of course

    $product = Product::get(['manufacturer'=>'Jacobs','price'=>2.25]);

Paginate

The get is a good function, but i don´t want all the objects in the same query, you are thinking. For that thing, we have the function Paginate.

    //FIRST PAGE
    $res = Products::paginate(['manufacturer'=>'Jacobs'], 20);
    $products = $res->getObjects();
    $search_after = $res->getSearch_After();

    //SECOND PAGE
    $res = Products::paginate(['manufacturer'=>'Jacobs'], 20,$search_after;
    $products_second = $res->getObjects();

Easy right? But wait, is not like the Get function, it doesn´t return the objects. It return the custom object EsResponse. This object is nothing more than the response of ElasticSearch but with the objects converted to the correct format and some special functions.

Like the getSearch_After(), this function will return you an array, that you can pass again to the paginate function again, for the next page.

Search

The important function, when you want raw ElasticPower, pass the query to search, and it will return you a EsResponse

    $query = [
    "size" => 20,
    "query" => [
        "term" => [
            "manufacturer" => 'Jacobs'
            ]
        ]
    ];
    $res = Products::search($query);

Options

In 3 of this functions the last parameter will always be Options as an array, if you have an Elasticsearch option query you put it here, apart of them, it accept some custom ones:

  • custom-index: If you only want to search in a specific index, of course you concatenate two or more index, using the normal Elastic Rules.
  • custom-index-values: If you are search a object with an index with TemplateField, you can use this option to pass the values, for example, if you want a search in and Index witch as TemplateField have a Datetime, i can pass all the DateTime that i want to search
  • force-exists: Elastic will thrown an error if you are searching a index that doesn´t exists. ElasticOc fix this adding a * at the end of every index, if you don't want this a prefere the error, put force-exists to TRUE

Delete

The Delete function work exactly like the get function, in fact, is more or less the same of get.

    Products::delete('2') //Will delete the object with id 2
    Products::delete(['manufacturer'=>'Jacobs']) //Will delete all the objects with the manufacturer jacobs

If you have the object in memory that you want to delete you can always call the function deleteThis

    $product->deleteThis();

Refresh

If you make some changes and need immediately to make a query to that index call the function Refresh so the index make the changes available

    $product->deleteThis();
    Product::refresh();
    Product::get($id);