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
Requires
- php: ^5.6 || ^7.0
- doctrine/annotations: 1.7.0
- elasticsearch/elasticsearch: ^7.0
This package is not auto-updated.
Last update: 2025-05-08 16:57:33 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);