From eefb2b2fc8604e5dd6153cc977e0685808020f37 Mon Sep 17 00:00:00 2001 From: VBoss Date: Tue, 23 Apr 2019 01:52:45 +0200 Subject: [PATCH 1/9] Documentation --- doc/01_intro.md | 52 ++++++++--- doc/02_neon_configuration.md | 1 + doc/03_entity_class.md | 155 +++++++++++++++++++++++++------ doc/05_new_index_with_mapping.md | 20 ++++ doc/06_fill_data.md | 20 ++++ doc/07_save_explained.md | 5 + doc/08_basic_get.md | 11 +++ doc/09_match_get.md | 24 +++++ doc/10_aggregate.md | 20 ++++ doc/11_entity_factory.md | 26 ++++++ doc/12_entity_service.md | 65 +++++++++++++ doc/13_advanced_get.md | 21 +++++ 12 files changed, 379 insertions(+), 41 deletions(-) create mode 100644 doc/06_fill_data.md create mode 100644 doc/07_save_explained.md create mode 100644 doc/08_basic_get.md create mode 100644 doc/09_match_get.md create mode 100644 doc/10_aggregate.md create mode 100644 doc/11_entity_factory.md create mode 100644 doc/12_entity_service.md create mode 100644 doc/13_advanced_get.md diff --git a/doc/01_intro.md b/doc/01_intro.md index 57ebbdd..24f5de2 100644 --- a/doc/01_intro.md +++ b/doc/01_intro.md @@ -11,7 +11,7 @@ Use composer `composer require spameri/elastic` In your config neon, enable extensions. Kdyby/Console is there because we need it to do some command line commands. Monolog is required by elasticsearch/elasticsearch and we can use existing extension in Kdyby/Monolog. -``` +```neon extensions: elasticSearch: \Spameri\Elastic\DI\ElasticSearchExtension console: Kdyby\Console\DI\ConsoleExtension @@ -19,41 +19,63 @@ extensions: ``` Then configure where is your ElasticSearch. -``` +```neon elasticSearch: host: 127.0.0.1 port: 9200 ``` -For more config options see default values in `\Spameri\Elastic\DI\ElasticSearchExtension::$defaults`. +For more config options see default values in `\Spameri\Elastic\DI\ElasticSearchExtension::$defaults`. [Here](../src/DI/ElasticSearchExtension.php#L9). + +--- ### 2. First entity -#### [Neon file configuration](../blob/master/doc/02_neon_configuration.md) +#### [Neon file configuration](02_neon_configuration.md) + +#### [Create entity class](03_entity_class.md) + +#### [Create entity service](12_entity_service.md) -#### [Create Entity class](../blob/master/doc/03_entity_class.md) +#### [Create entity factory](11_entity_factory.md) + +--- ### 3. Mapping -#### [Create new index with mapping]((../blob/master/doc/05_new_index_with_mapping.md)) +#### [Create new index with mapping](05_new_index_with_mapping.md) + +--- ### 4. Fill with data -TODO + +#### [Create and save entity](06_fill_data.md) + +#### [Saving process explained](07_save_explained.md) + +--- ### 5. Get data from ElasticSearch -TODO Tady factories -TODO -TODO -TODO -TODO + +#### [Get data by ID](08_basic_get.md) + +#### [Get data by tag](13_advanced_get.md) + +--- ### 6. Filter data from ElasticSearch -TODO + +#### [Match data](09_match_get.md) + +--- ### 7. Aggregate data from ElasticSearch -TODO + +#### [Aggregate data](10_aggregate.md) + +--- ### x. Other -#### [Data interfaces]((../blob/master/doc/04_data_interfaces.md)) +#### [Data interfaces](04_data_interfaces.md) diff --git a/doc/02_neon_configuration.md b/doc/02_neon_configuration.md index 38b9c28..c91e85a 100644 --- a/doc/02_neon_configuration.md +++ b/doc/02_neon_configuration.md @@ -1,4 +1,5 @@ # Define structure in neon file +TODO convert to video example - Create neon file for example in `app/ProductModule/Config/Product.neon` - Import created file to your application config neon. Usually located in`app/config/config.neon`. diff --git a/doc/03_entity_class.md b/doc/03_entity_class.md index f471e64..a795ec6 100644 --- a/doc/03_entity_class.md +++ b/doc/03_entity_class.md @@ -1,11 +1,11 @@ # Entity class -Lets create entity class, continuing our example, in folder `app/ProductModule/Entity/Product.php` given file contents: +Lets create entity class, continuing our example, in folder `tests/SpameriTests/Data/Entity/Video.php` given file contents: ```php -namespace App\ProductModule\Entity; +namespace SpameriTests\Data\Entity; -class Product implements \Spameri\Elastic\Entity\IElasticEntity +class Video implements \Spameri\Elastic\Entity\IElasticEntity { /** @@ -38,7 +38,7 @@ class Product implements \Spameri\Elastic\Entity\IElasticEntity ### Lets look at class part by part. -- Entity is in our defined namespace for `ProductModule` in own folder `Entity` which is shared for multiple entities. +- Entity is in our defined namespace in own folder `Entity` which is shared for multiple entities. - Class extends interface `\Spameri\Elastic\Entity\IElasticEntity`, this is core interface for ElasticSearch document. It has to have `id` provided by ElasticSearch, library takes care of handling this field, no need to add in mapping. - Based on this interface, library figures out how to save this class. @@ -47,26 +47,26 @@ It has to have `id` provided by ElasticSearch, library takes care of handling th - Interface requires function `id()` based on returned value it updates or creates entity. - Interface requires `entityVariables()` in this exact form. (This may be changed in future versions, but now is required) -### Adding properties to product entity +### Adding properties to video entity -#### Single value property - `name` +#### Single value property - `Video.Story.KeyWord` -- Lets say our product has limited name length to maximum of 255 characters and also 0 characters is not enough to -describe product. +- Lets say our Video has limited keyword length to maximum of 55 characters and also 0 characters is not enough to +describe keyword. - We do not have reliable data input so lets validate this with help of interface `\Spameri\Elastic\Entity\IValue`. -- First create value object for name `\App\ProductModule\Entity\Product\Name`. +- First create value object for keyword `\SpameriTests\Data\Entity\Video\Story\KeyWord`. - Class should implement interface `\Spameri\Elastic\Entity\IValue`. - `__construct` should have one parameter **string $value**. -- In construct do our validation for product name. +- In construct do our validation for keyword. - Implement `value()` method. -- Add property to `\App\ProductModule\Entity\Product` constructor. -- Generate getter in `\App\ProductModule\Entity\Product` entity. +- Add property to `\SpameriTests\Data\Entity\Video` constructor. +- Generate getter in `\SpameriTests\Data\Entity\Video` entity for KeyWord. - Result: ```php -namespace App\ProductModule\Entity\Product; +namespace SpameriTests\Data\Entity\Video\Story; -class Name implements \Spameri\Elastic\Entity\IValue +class KeyWord implements \Spameri\Elastic\Entity\IValue { /** @@ -79,11 +79,11 @@ class Name implements \Spameri\Elastic\Entity\IValue string $value ) { - if (\strlen($value) < 0) { - throw new \InvalidArgumentException('Empty string is not supported for product name: ' . $value); + if ($value === '') { + throw new \InvalidArgumentException(); } - if (\strlen($value) > 255) { - $value = \substr($value, 0, 255); + if (\strlen($value) > 55) { + throw new \InvalidArgumentException(); } $this->value = $value; @@ -98,14 +98,117 @@ class Name implements \Spameri\Elastic\Entity\IValue } ``` -#### Value collection property - `details.tags` -TODO +#### Value collection property - `Video.Story.KeyWordCollection` +TODO Description +```php +namespace SpameriTests\Data\Entity\Video\Story; + + +class KeyWordCollection implements \Spameri\Elastic\Entity\IValueCollection +{ + + /** + * @var array<\SpameriTests\Data\Entity\Video\Story\KeyWord> + */ + private $collection; + + + public function __construct( + KeyWord ... $entities + ) + { + $this->collection = []; + foreach ($entities as $keyWord) { + $this->add($keyWord); + } + } + + + public function add( + KeyWord $keyWord + ) : void + { + $this->collection[$keyWord->value()] = $keyWord; + } -#### Single entity property - `details` -TODO -#### Entity collection property - `parameterValues` -TODO + public function getIterator() : \ArrayIterator + { + return new \ArrayIterator($this->collection); + } +} +``` + +#### Single entity property - `Video.Story` +TODO Description +```php +namespace SpameriTests\Data\Entity\Video; + + +class Story implements \Spameri\Elastic\Entity\IEntity +{ + + /** + * @var \SpameriTests\Data\Entity\Video\Story\KeyWordCollection + */ + private $keyWords; + + + public function __construct( + \SpameriTests\Data\Entity\Video\Story\KeyWordCollection $keyWord + ) + { + $this->keyWords = $keyWord; + } + + + public function entityVariables() : array + { + return \get_object_vars($this); + } + + + public function key() : string + { + + } +} +``` + +#### Entity collection property - `Video.Connections.FollowsCollection` +TODO Description +```php +namespace SpameriTests\Data\Entity\Video\Connections; + + +class FollowsCollection extends \Spameri\Elastic\Entity\Collection\EntityCollection +{ + +} +``` + +#### ElasticEntity collection property - `Video.People` +TODO Description +```php +namespace SpameriTests\Data\Entity\Video; + + +class People extends \Spameri\Elastic\Entity\Collection\ElasticEntityCollection +{ + + public function personByImdb( + \SpameriTests\Data\Entity\Property\ImdbId $imdb + ) : ?\SpameriTests\Data\Entity\Person + { + /** @var \SpameriTests\Data\Entity\Person $entity */ + foreach ($this->collection() as $entity) { + if ($imdb->value() === $entity->identification()->imdb()->value()) { + return $entity; + } + } + + return NULL; + } +} +``` -#### ElasticEntity collection property - `details.accessories` -TODO diff --git a/doc/05_new_index_with_mapping.md b/doc/05_new_index_with_mapping.md index e69de29..042dea3 100644 --- a/doc/05_new_index_with_mapping.md +++ b/doc/05_new_index_with_mapping.md @@ -0,0 +1,20 @@ +# Create index with mapping for entity + +Entity is configured (link) and defined in php (link) and next thing is to get these settings to ElasticSearch. + +## Index creating + +### New index - no previous data +TODO +```bash +php www/index spameri:elastic:create-index +``` + +### New index - deleting previous data +TODO + +### New index - preserving previous data +TODO + + + diff --git a/doc/06_fill_data.md b/doc/06_fill_data.md new file mode 100644 index 0000000..052dd32 --- /dev/null +++ b/doc/06_fill_data.md @@ -0,0 +1,20 @@ +# Create and save entity + +## Create +TODO +[Example](../tests/SpameriTests/Model/Insert.phpt#L16) +```php +$video = new \SpameriTests\Data\Entity\Video( + new \Spameri\Elastic\Entity\Property\EmptyElasticId(), + new \SpameriTests\Data\Entity\Video\Identification( + new \SpameriTests\Data\Entity\Property\ImdbId(4154796) + ) +); +``` + +## Save +TODO +```php +$videoService->insert($video); +``` + diff --git a/doc/07_save_explained.md b/doc/07_save_explained.md new file mode 100644 index 0000000..eb9dbdb --- /dev/null +++ b/doc/07_save_explained.md @@ -0,0 +1,5 @@ +# Insert explained + +- Converter +- ElasticSearch point of view +TODO \ No newline at end of file diff --git a/doc/08_basic_get.md b/doc/08_basic_get.md new file mode 100644 index 0000000..dbe5546 --- /dev/null +++ b/doc/08_basic_get.md @@ -0,0 +1,11 @@ +# Basic Get + +## Description +Basic get by id from ElasticSearch, with exact match. + +## Example +```php +$video = $videoService->get( + new \Spameri\Elastic\Entity\Property\ElasticId($id) +); +``` diff --git a/doc/09_match_get.md b/doc/09_match_get.md new file mode 100644 index 0000000..828eb8f --- /dev/null +++ b/doc/09_match_get.md @@ -0,0 +1,24 @@ +# Filter data + +## Description +TODO + +## Example +```php +$elasticQuery = new \Spameri\ElasticQuery\ElasticQuery(); +$elasticQuery->query()->must()->add( + new \Spameri\ElasticQuery\Query\Range( + 'year', + 2018, + 2019 + ) +); +$elasticQuery->query()->must()->add( + new \Spameri\ElasticQuery\Query\Match( + 'name', + 'Avengers' + ) +); + +$video = $videoService->getBy($elasticQuery); +``` diff --git a/doc/10_aggregate.md b/doc/10_aggregate.md new file mode 100644 index 0000000..03249a4 --- /dev/null +++ b/doc/10_aggregate.md @@ -0,0 +1,20 @@ +# Aggregate + +## Description +TODO + +## Example +```php +$elasticQuery = new \Spameri\ElasticQuery\ElasticQuery(); +$elasticQuery->aggregation()->add( + new \Spameri\ElasticQuery\Aggregation\LeafAggregationCollection( + 'video-by-year', + NULL, + new \Spameri\ElasticQuery\Aggregation\Term( + 'year' + ) + ) +); + +$aggregateResult = $videoService->aggregate($elasticQuery); +``` diff --git a/doc/11_entity_factory.md b/doc/11_entity_factory.md new file mode 100644 index 0000000..bcac360 --- /dev/null +++ b/doc/11_entity_factory.md @@ -0,0 +1,26 @@ +# Entity factory + +## Description +TODO +Creates entity from result hit. + +## Example +```php +namespace SpameriTests\Elastic\Factory; + + +class VideoFactory implements \Spameri\Elastic\Factory\IEntityFactory +{ + + public function create(\Spameri\ElasticQuery\Response\Result\Hit $hit) + { + return new \SpameriTests\Data\Entity\Video( + new \Spameri\Elastic\Entity\Property\ElasticId($hit->getValue('id')), + new \SpameriTests\Data\Entity\Video\Identification( + new \SpameriTests\Data\Entity\Property\ImdbId($hit->getValue('identification.imdb')) + ) + ); + } + +} +``` diff --git a/doc/12_entity_service.md b/doc/12_entity_service.md new file mode 100644 index 0000000..5c1d050 --- /dev/null +++ b/doc/12_entity_service.md @@ -0,0 +1,65 @@ +# Entity service + +## Description +TODO +Every service should extend BaseService which has all methods for entity manipulation. Like **Insert**, **Get**, +**GetBy**, **GetAllBy**. + +## Example +```php +namespace SpameriTests\Data\Model; + + +class VideoService extends \Spameri\Elastic\Model\BaseService +{ + + /** + * @param \Spameri\Elastic\Entity\IElasticEntity|\SpameriTests\Data\Entity\Video $entity + * @return string + */ + public function insert( + \Spameri\Elastic\Entity\IElasticEntity $entity + ) : string + { + return parent::insert($entity); + } + + + /** + * @param \Spameri\Elastic\Entity\Property\ElasticId $id + * @return \Spameri\Elastic\Entity\IElasticEntity|\SpameriTests\Data\Entity\Video + */ + public function get( + \Spameri\Elastic\Entity\Property\ElasticId $id + ) : \Spameri\Elastic\Entity\IElasticEntity + { + return parent::get($id); + } + + + /** + * @param \Spameri\ElasticQuery\ElasticQuery $elasticQuery + * @return \Spameri\Elastic\Entity\IElasticEntity|\SpameriTests\Data\Entity\Video + * @throws \Spameri\Elastic\Exception\DocumentNotFound + */ + public function getBy( + \Spameri\ElasticQuery\ElasticQuery $elasticQuery + ) : \Spameri\Elastic\Entity\IElasticEntity + { + return parent::getBy($elasticQuery); + } + + + /** + * @param \Spameri\ElasticQuery\ElasticQuery $elasticQuery + * @return \Spameri\Elastic\Entity\IElasticEntityCollection|array<\SpameriTests\Data\Entity\Video> + */ + public function getAllBy( + \Spameri\ElasticQuery\ElasticQuery $elasticQuery + ) : \Spameri\Elastic\Entity\IElasticEntityCollection + { + return parent::getAllBy($elasticQuery); + } + +} +``` diff --git a/doc/13_advanced_get.md b/doc/13_advanced_get.md new file mode 100644 index 0000000..47c832d --- /dev/null +++ b/doc/13_advanced_get.md @@ -0,0 +1,21 @@ +# Advanced Get + +## Description +TODO +Get data from ElasticSearch by tag. + +## Example +```php +$video = $videoService->getBy( + new \Spameri\ElasticQuery\ElasticQuery( + new \Spameri\ElasticQuery\Query\QueryCollection( + new \Spameri\ElasticQuery\Query\MustCollection( + new \Spameri\ElasticQuery\Query\Match( + 'story.tag', + 'action' + ) + ) + ) + ) +); +``` From d62925c5e3628d0b63b45541125c830e28785970 Mon Sep 17 00:00:00 2001 From: VBoss Date: Tue, 23 Apr 2019 17:38:23 +0200 Subject: [PATCH 2/9] Entity documentation + Raw usage documentation --- doc/01_intro.md | 21 +++ doc/03_entity_class.md | 230 ++++++++++++++++++++++++- src/Entity/AbstractEntity.php | 20 +++ src/Entity/AbstractValueCollection.php | 55 ++++++ 4 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 src/Entity/AbstractEntity.php create mode 100644 src/Entity/AbstractValueCollection.php diff --git a/doc/01_intro.md b/doc/01_intro.md index 24f5de2..320d0d8 100644 --- a/doc/01_intro.md +++ b/doc/01_intro.md @@ -27,6 +27,27 @@ elasticSearch: For more config options see default values in `\Spameri\Elastic\DI\ElasticSearchExtension::$defaults`. [Here](../src/DI/ElasticSearchExtension.php#L9). +#### Raw client usage +- After this configuration you are ready to use ElasticSearch in your Nette application. +- Where needed just inject `\Spameri\Elastic\ClientProvider` and then directly call what you need, like this: +```php +$result = $this->clientProvider->client()->search( + ( + new \Spameri\ElasticQuery\Document( + $index, + new \Spameri\ElasticQuery\Document\Body\Plain( + $elasticQuery->toArray() + ), + $index + ) + )->toArray() +); +``` +- [Client](https://github.com/elastic/elasticsearch-php/blob/master/src/Elasticsearch/Client.php) is provided from **elasticsearch/elasticsearch** and you can see their [documentation](https://github.com/elastic/elasticsearch-php#quickstart) +what methods and arrays are supported. +- When in doubt what how many arrays or how many arguments **match** supports use [Spameri/ElasticQuery](https://github.com/Spameri/ElasticQuery/blob/master/doc/02-query-objects.md) +- This is library used in later examples. But direct approach is also possible. + --- ### 2. First entity diff --git a/doc/03_entity_class.md b/doc/03_entity_class.md index a795ec6..524e53b 100644 --- a/doc/03_entity_class.md +++ b/doc/03_entity_class.md @@ -99,7 +99,13 @@ class KeyWord implements \Spameri\Elastic\Entity\IValue ``` #### Value collection property - `Video.Story.KeyWordCollection` -TODO Description +- If you need array of scalar values lets create ValueCollection. +- For easy setup you can use `\Spameri\Elastic\Entity\AbstractValueCollection` just create your collection and extend this abstract as you need. +For more advanced and typed approach use interface, as described next. +- Interface `\Spameri\Elastic\Entity\IValueCollection` is when you want typed and validated scalar value collection. +- After implementing interface you need to implement **getIterator()** method. +- Next to be type save you want to add methods **add**, **remove**, **get**, **__construct** +- For **__construct** you best fill values to collection as here [\Spameri\Elastic\Entity\AbstractValueCollection#L20](../src/Entity/AbstractValueCollection.php#L20) ```php namespace SpameriTests\Data\Entity\Video\Story; @@ -125,13 +131,28 @@ class KeyWordCollection implements \Spameri\Elastic\Entity\IValueCollection public function add( - KeyWord $keyWord + \SpameriTests\Data\Entity\Video\Story\KeyWord $keyWord ) : void { $this->collection[$keyWord->value()] = $keyWord; } + public function remove(string $key) : void + { + unset($this->collection[$key]); + } + + + public function get(string $key) : ?\SpameriTests\Data\Entity\Video\Story\KeyWord + { + if ( ! isset($this->collection[$key])) { + return NULL; + } + + return $this->collection[$key]; + } + public function getIterator() : \ArrayIterator { return new \ArrayIterator($this->collection); @@ -140,7 +161,11 @@ class KeyWordCollection implements \Spameri\Elastic\Entity\IValueCollection ``` #### Single entity property - `Video.Story` -TODO Description +- If you need some nested structure `\Spameri\Elastic\Entity\IEntity` interface is here for you. +- Also when feeling lazy there is `\Spameri\Elastic\Entity\AbstractEntity` for you to extend with methods implemented. +- In our example we have entity **Story** to encapsulate keywords and other story related properties. +- Library then can convert this entity to array and save it as array with no more help. + ```php namespace SpameriTests\Data\Entity\Video; @@ -170,13 +195,15 @@ class Story implements \Spameri\Elastic\Entity\IEntity public function key() : string { - + return \md5(\implode('_', $this->entityVariables())); } } ``` #### Entity collection property - `Video.Connections.FollowsCollection` -TODO Description +- ElasticSearch is powerful tool and it allows you to nest objects and collection as you need, so you can make collection of nested objects. +- This is simple you have Entity **Story** with implemented `IEntity` interface and all you need is create collection, extend `class FollowsCollection extends \Spameri\Elastic\Entity\Collection\EntityCollection` +and you are done. ```php namespace SpameriTests\Data\Entity\Video\Connections; @@ -188,7 +215,12 @@ class FollowsCollection extends \Spameri\Elastic\Entity\Collection\EntityCollect ``` #### ElasticEntity collection property - `Video.People` -TODO Description +- `\Spameri\Elastic\Entity\Collection\ElasticEntityCollection` provides basic relations for entities in ElasticSearch. +- It saves **_id** to current entity as reference in raw data but when loaded you have full entity with that id. +Any changes made to related entity/ies will be persisted when main entity is saved. +- Entity can be manually related 1:1 with manual lazy load in Factory (example in [factory](11_entity_factory.md) documentation) +- Or multiple entities can be in collection lazily loaded all at once, also in factory example. +- All you need is extend `\Spameri\Elastic\Entity\Collection\ElasticEntityCollection` and fill with your entities, library will do saving and resolving for you. ```php namespace SpameriTests\Data\Entity\Video; @@ -212,3 +244,189 @@ class People extends \Spameri\Elastic\Entity\Collection\ElasticEntityCollection } ``` +## Final product [Example](../tests/SpameriTests/Data/Entity/Video.php) +```php +namespace SpameriTests\Data\Entity; + + +class Video implements \Spameri\Elastic\Entity\IElasticEntity +{ + + /** + * @var \Spameri\Elastic\Entity\Property\IElasticId + */ + private $id; + + /** + * @var \SpameriTests\Data\Entity\Video\Identification + */ + private $identification; + + /** + * @var \SpameriTests\Data\Entity\Property\Name + */ + private $name; + + /** + * @var \SpameriTests\Data\Entity\Property\Year + */ + private $year; + + /** + * @var \SpameriTests\Data\Entity\Video\Technical + */ + private $technical; + + /** + * @var \SpameriTests\Data\Entity\Video\Story + */ + private $story; + + /** + * @var \SpameriTests\Data\Entity\Video\Details + */ + private $details; + + /** + * @var \SpameriTests\Data\Entity\Video\HighLights + */ + private $highLights; + + /** + * @var \SpameriTests\Data\Entity\Video\Connections + */ + private $connections; + + /** + * @var \SpameriTests\Data\Entity\Video\SeasonCollection + */ + private $season; + + /** + * @var \SpameriTests\Data\Entity\Video\People + */ + private $people; + + + public function __construct( + \Spameri\Elastic\Entity\Property\IElasticId $id + , \SpameriTests\Data\Entity\Video\Identification $identification + , \SpameriTests\Data\Entity\Property\Name $name + , \SpameriTests\Data\Entity\Property\Year $year + , \SpameriTests\Data\Entity\Video\Technical $technical + , \SpameriTests\Data\Entity\Video\Story $story + , \SpameriTests\Data\Entity\Video\Details $details + , \SpameriTests\Data\Entity\Video\HighLights $highLights + , \SpameriTests\Data\Entity\Video\Connections $connections + , \SpameriTests\Data\Entity\Video\People $people + , \SpameriTests\Data\Entity\Video\SeasonCollection $season = NULL + ) + { + $this->id = $id; + $this->identification = $identification; + $this->name = $name; + $this->year = $year; + $this->technical = $technical; + $this->story = $story; + $this->details = $details; + $this->highLights = $highLights; + $this->connections = $connections; + + if ($season === NULL) { + $season = new \SpameriTests\Data\Entity\Video\SeasonCollection(); + } + $this->season = $season; + $this->people = $people; + } + + + public function entityVariables() : array + { + return \get_object_vars($this); + } + + + public function id() : \Spameri\Elastic\Entity\Property\IElasticId + { + return $this->id; + } + + + public function identification() : \SpameriTests\Data\Entity\Video\Identification + { + return $this->identification; + } + + + public function name() : \SpameriTests\Data\Entity\Property\Name + { + return $this->name; + } + + + public function rename(\SpameriTests\Data\Entity\Property\Name $name) : void + { + $this->name = $name; + } + + + public function year() : \SpameriTests\Data\Entity\Property\Year + { + return $this->year; + } + + + public function setYear(\SpameriTests\Data\Entity\Property\Year $year) : void + { + $this->year = $year; + } + + + public function technical() : \SpameriTests\Data\Entity\Video\Technical + { + return $this->technical; + } + + + public function setTechnicalFromImdb(\SpameriTests\Data\Entity\Video\Technical $technical) : void + { + $this->technical = $technical; + } + + + public function story() : \SpameriTests\Data\Entity\Video\Story + { + return $this->story; + } + + + public function details() : \SpameriTests\Data\Entity\Video\Details + { + return $this->details; + } + + + public function highLights() : \SpameriTests\Data\Entity\Video\HighLights + { + return $this->highLights; + } + + + public function connections() : \SpameriTests\Data\Entity\Video\Connections + { + return $this->connections; + } + + + public function season() : \SpameriTests\Data\Entity\Video\SeasonCollection + { + return $this->season; + } + + + public function people() : \SpameriTests\Data\Entity\Video\People + { + return $this->people; + } +} +``` diff --git a/src/Entity/AbstractEntity.php b/src/Entity/AbstractEntity.php new file mode 100644 index 0000000..d0aef41 --- /dev/null +++ b/src/Entity/AbstractEntity.php @@ -0,0 +1,20 @@ +entityVariables())); + } + +} diff --git a/src/Entity/AbstractValueCollection.php b/src/Entity/AbstractValueCollection.php new file mode 100644 index 0000000..57265e8 --- /dev/null +++ b/src/Entity/AbstractValueCollection.php @@ -0,0 +1,55 @@ + + */ + protected $collection; + + + public function __construct( + \Spameri\Elastic\Entity\IValue ... $collection + ) + { + $this->collection = []; + foreach ($collection as $value) { + $this->add($value); + } + } + + + public function add( + \Spameri\Elastic\Entity\IValue $value + ) : void + { + $this->collection[$value->value()] = $value; + } + + + public function remove($key) : void + { + unset($this->collection[$key]); + } + + + public function get($key) : ?\Spameri\Elastic\Entity\IValue + { + if ( ! isset($this->collection[$key])) { + return NULL; + } + + return $this->collection[$key]; + } + + + public function getIterator() : \ArrayIterator + { + return new \ArrayIterator($this->collection); + } + +} From 55956ca1d339748d7978e79e5c44c0e8a6bde891 Mon Sep 17 00:00:00 2001 From: VBoss Date: Wed, 24 Apr 2019 00:46:27 +0200 Subject: [PATCH 3/9] Documentation --- doc/05_new_index_with_mapping.md | 43 ++++++++++++++++++++----- doc/06_fill_data.md | 15 ++++++--- doc/07_save_explained.md | 42 +++++++++++++++++++++++-- doc/09_match_get.md | 6 ++-- doc/10_aggregate.md | 4 ++- doc/13_advanced_get.md | 54 ++++++++++++++++++++++++++------ 6 files changed, 136 insertions(+), 28 deletions(-) diff --git a/doc/05_new_index_with_mapping.md b/doc/05_new_index_with_mapping.md index 042dea3..da64332 100644 --- a/doc/05_new_index_with_mapping.md +++ b/doc/05_new_index_with_mapping.md @@ -1,20 +1,49 @@ # Create index with mapping for entity -Entity is configured (link) and defined in php (link) and next thing is to get these settings to ElasticSearch. +Entity is configured [link](03_entity_class.md) and defined in php [link](03_entity_class.md#L247) and next thing is to get these settings to ElasticSearch. ## Index creating -### New index - no previous data -TODO +### Variant 1. - No previous data +- When to use? +- You dont have any index, your ElasticSearch installation is clean and with no index for our entities. +- How? +- Simple neon is configured from previous [step](02_neon_configuration.md) and we just need to run command. + ```bash php www/index spameri:elastic:create-index ``` -### New index - deleting previous data -TODO +- Can be used for specific entity +```bash +php www/index spameri:elastic:create-index video +``` -### New index - preserving previous data -TODO +### Variant 2. - Deleting previous data +- When to use? +- You have existing index with outdated data with old configuration, but you dont need to keep them. This is +usually when you are generating ElasticSearch data from another database. +- How? +- Just add -f option. First thing command does is delete old index, then creating new empty index with new settings. +```bash +php www/index spameri:elastic:create-index -f +``` +- Can be used for specific entity +```bash +php www/index spameri:elastic:create-index -f video +``` +### Variant 3. - Preserving previous data +- When to use? +- You dont have source for data saved in ElasticSearch. +- Best is backup your data with command. This creates bulk json document with all data from index. +```bash +php www/index spameri:elastic:dump-index +``` +- With data backed up, now you can delete index and create it fresh with new mapping - following variant 2. +- Last thing is get your old data to new index wit this command. +```bash +php www/index spameri:elastic:restore-index +``` diff --git a/doc/06_fill_data.md b/doc/06_fill_data.md index 052dd32..ecc1989 100644 --- a/doc/06_fill_data.md +++ b/doc/06_fill_data.md @@ -1,20 +1,25 @@ # Create and save entity ## Create -TODO -[Example](../tests/SpameriTests/Model/Insert.phpt#L16) +- When creating new entity, mostly you need to construct it manually. +- Your data is from another database or API or csv or wherever. Quality may wary. +- So best approach is to validate all you can with objects. +- In this example we have property **imdb** and it needs to be in range from one digit to ten digits, rather than adding +if conditions wherever we create entity we use **ImdbId** class to validate this rule and this helps to properly convert +entity data to array. +- [Example](../tests/SpameriTests/Model/Insert.phpt#L16) ```php +$sqlData = $dibi->fetchRow(); $video = new \SpameriTests\Data\Entity\Video( new \Spameri\Elastic\Entity\Property\EmptyElasticId(), new \SpameriTests\Data\Entity\Video\Identification( - new \SpameriTests\Data\Entity\Property\ImdbId(4154796) + new \SpameriTests\Data\Entity\Property\ImdbId($sqlData['imdb']) ) ); ``` ## Save -TODO +- Entity is created, validated and ready to be saved. Just pass entity to [service](12_entity_service.md). And done. ```php $videoService->insert($video); ``` - diff --git a/doc/07_save_explained.md b/doc/07_save_explained.md index eb9dbdb..047564b 100644 --- a/doc/07_save_explained.md +++ b/doc/07_save_explained.md @@ -1,5 +1,41 @@ # Insert explained -- Converter -- ElasticSearch point of view -TODO \ No newline at end of file +## \Spameri\Elastic\Model\Insert\PrepareEntityArray +- This class is responsible for converting ElasticSearch entity to array that can be then saved to ElasticSearch. +- Configuring is done by implementing [interfaces](04_data_interfaces.md), no need for annotations or neon. + +### ::prepare(\Spameri\Elastic\Entity\IElasticEntity $entity) +- This method is here for last before convert modifications. +- If implemented `\Spameri\Elastic\Entity\ITrackedEntity` interface for entity tracking (and properties specified), +this method adds timestamp and user who edited/created entity. +- Then calling iterateVariables to prepare rest of entity array. + +### ::iterateVariables(array $variables) +This method accepts entity variables and based on their type performs converting to array to ready entity for insert. +There are 9 types of property handled: +1. **\Spameri\Elastic\Entity\IElasticEntity** in this case service locator comes to play and locates service for related +entity and saves connected entity. And prepares related entity's id to property. Because to ElasticSearch goes only +string id. + +2. **\Spameri\Elastic\Entity\IEntity** this is structural entity and is saved directly to parent entity. Its properties +are iterated with `::iterateVariables($property->entityVariables())` + +3. **\Spameri\Elastic\Entity\IValue** raw value in object, directly converted to array. + +4. **\Spameri\Elastic\Entity\IEntityCollection** Collection of structural class **IEntity**, iterate and act as step 2. + +5. **\Spameri\Elastic\Entity\IElasticEntityCollection** Collection of ElasticSearch entities **IElasticEntity**, +iterate and act as step 1. + +6. **\Spameri\Elastic\Entity\IValueCollection** Collection of **IValue**, iterate and act as step 3. + +7. Scalar values **string**, **int**, **bool** or **NULL**, no action just pass to array. + +8. **\Spameri\Elastic\Entity\DateTimeInterface** Date interface with specified format by this library so ElasticSearch +can save it without problems. +- **\Spameri\Elastic\Entity\Property\Date** for `Y-m-d` format +- **\Spameri\Elastic\Entity\Property\DateTime** for `Y-m-d\TH:i:s` format + +9. **\DateTime** All other Dates are converted to `Y-m-d\TH:i:s` + +10. Exception thrown property is none of above. diff --git a/doc/09_match_get.md b/doc/09_match_get.md index 828eb8f..8375272 100644 --- a/doc/09_match_get.md +++ b/doc/09_match_get.md @@ -1,15 +1,17 @@ # Filter data ## Description -TODO +You can specify complicated ElasticSearch Query and still get pretty entity. [Service](12_entity_service.md) accepts +`\Spameri\ElasticQuery\ElasticQuery` object and returns entity or collection depending if you want one or more results. ## Example +In this example we are looking for video named 'Avengers' made in years from 2017 to 2018. ```php $elasticQuery = new \Spameri\ElasticQuery\ElasticQuery(); $elasticQuery->query()->must()->add( new \Spameri\ElasticQuery\Query\Range( 'year', - 2018, + 2017, 2019 ) ); diff --git a/doc/10_aggregate.md b/doc/10_aggregate.md index 03249a4..34cccbe 100644 --- a/doc/10_aggregate.md +++ b/doc/10_aggregate.md @@ -1,9 +1,11 @@ # Aggregate ## Description -TODO +When aggregating you dont get directly entity just array data, for this we have `\Spameri\ElasticQuery\Response\ResultSearch` +object to encapsulate result. ## Example +In this example we want number of videos released in each year. ```php $elasticQuery = new \Spameri\ElasticQuery\ElasticQuery(); $elasticQuery->aggregation()->add( diff --git a/doc/13_advanced_get.md b/doc/13_advanced_get.md index 47c832d..fac65c1 100644 --- a/doc/13_advanced_get.md +++ b/doc/13_advanced_get.md @@ -1,21 +1,55 @@ # Advanced Get ## Description -TODO -Get data from ElasticSearch by tag. + ## Example +In this example we want videos with tag **action** and are public. Also we want only first 50 sorted by year, but if +year has multiple videos sort them by score. Bonus points if movie is on beach with someone named john or here +misspelled as jon. + +Query does not have to be specified all at once, this is demonstration of capabilities. Query can be constructed empty +and extended through application runtime. ```php -$video = $videoService->getBy( - new \Spameri\ElasticQuery\ElasticQuery( - new \Spameri\ElasticQuery\Query\QueryCollection( - new \Spameri\ElasticQuery\Query\MustCollection( - new \Spameri\ElasticQuery\Query\Match( - 'story.tag', - 'action' - ) +$elasticQuery = new \Spameri\ElasticQuery\ElasticQuery( + new \Spameri\ElasticQuery\Query\QueryCollection( + new \Spameri\ElasticQuery\Query\MustCollection( + new \Spameri\ElasticQuery\Query\Term( + 'story.tag', + 'action' + ), + new \Spameri\ElasticQuery\Query\Term( + 'isPublic', + TRUE + ) + ), + new \Spameri\ElasticQuery\Query\ShouldCollection( + new \Spameri\ElasticQuery\Query\Match( + 'story.description', + 'beach' + ), + new \Spameri\ElasticQuery\Query\Fuzzy( + 'story.description', + 'jon' + ) + ) + ), + NULL, + NULL, + new \Spameri\ElasticQuery\Options( + 50, + NULL, + new \Spameri\ElasticQuery\Options\SortCollection( + new \Spameri\ElasticQuery\Options\Sort( + 'year', + \Spameri\ElasticQuery\Options\Sort::DESC + ), + new \Spameri\ElasticQuery\Options\Sort( + '_score', + \Spameri\ElasticQuery\Options\Sort::DESC ) ) ) ); +$videos = $this->videoService->getAllBy($elasticQuery); ``` From 63f5269c8fba511d95f12b6d220661d0b4048588 Mon Sep 17 00:00:00 2001 From: VBoss Date: Wed, 24 Apr 2019 15:53:37 +0200 Subject: [PATCH 4/9] BoolValue, IntegerValue, StringValue and NullValue - as prepared implemented strict value objects - still preferable to extend these and add your specific validation and name to differentiate values in your application --- src/Entity/Value/BoolValue.php | 28 +++++++++++++++++++++++++ src/Entity/Value/IntegerValue.php | 28 +++++++++++++++++++++++++ src/Entity/Value/NullValue.php | 28 +++++++++++++++++++++++++ src/Entity/Value/StringValue.php | 28 +++++++++++++++++++++++++ src/Model/Insert/PrepareEntityArray.php | 2 +- 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/Entity/Value/BoolValue.php create mode 100644 src/Entity/Value/IntegerValue.php create mode 100644 src/Entity/Value/NullValue.php create mode 100644 src/Entity/Value/StringValue.php diff --git a/src/Entity/Value/BoolValue.php b/src/Entity/Value/BoolValue.php new file mode 100644 index 0000000..9455051 --- /dev/null +++ b/src/Entity/Value/BoolValue.php @@ -0,0 +1,28 @@ +value = $value; + } + + + public function value(): bool + { + return $this->value; + } + +} diff --git a/src/Entity/Value/IntegerValue.php b/src/Entity/Value/IntegerValue.php new file mode 100644 index 0000000..ca8eea4 --- /dev/null +++ b/src/Entity/Value/IntegerValue.php @@ -0,0 +1,28 @@ +value = $value; + } + + + public function value(): int + { + return $this->value; + } + +} diff --git a/src/Entity/Value/NullValue.php b/src/Entity/Value/NullValue.php new file mode 100644 index 0000000..ae30802 --- /dev/null +++ b/src/Entity/Value/NullValue.php @@ -0,0 +1,28 @@ +value; + } + +} diff --git a/src/Entity/Value/StringValue.php b/src/Entity/Value/StringValue.php new file mode 100644 index 0000000..f0f9ae1 --- /dev/null +++ b/src/Entity/Value/StringValue.php @@ -0,0 +1,28 @@ +value = $value; + } + + + public function value(): string + { + return $this->value; + } + +} diff --git a/src/Model/Insert/PrepareEntityArray.php b/src/Model/Insert/PrepareEntityArray.php index cb0dbcb..18859e2 100644 --- a/src/Model/Insert/PrepareEntityArray.php +++ b/src/Model/Insert/PrepareEntityArray.php @@ -76,7 +76,7 @@ public function iterateVariables( } elseif ($property instanceof \Spameri\Elastic\Entity\IValueCollection) { $preparedArray[$key] = []; - /** @var $value \Spameri\Elastic\Entity\IValue */ + /** @var \Spameri\Elastic\Entity\IValue $value */ /** @var \Spameri\Elastic\Entity\IValueCollection $property */ foreach ($property as $value) { if ($value instanceof \Spameri\Elastic\Entity\IValue) { From 98de222132eda7a0ba9550c1824570af9daed5d1 Mon Sep 17 00:00:00 2001 From: VBoss Date: Fri, 25 Oct 2019 16:03:31 +0200 Subject: [PATCH 5/9] QuickStart guide - Register extension --- doc/00_quick_start.md | 35 ++++++++++++++++++++++ doc/01_intro.md | 6 ++-- doc/02_neon_configuration.md | 8 ++--- tests/SpameriTests/Data/Config/Common.neon | 4 +-- tests/SpameriTests/Data/Config/Person.neon | 2 +- tests/SpameriTests/Data/Config/Video.neon | 2 +- 6 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 doc/00_quick_start.md diff --git a/doc/00_quick_start.md b/doc/00_quick_start.md new file mode 100644 index 0000000..89272dd --- /dev/null +++ b/doc/00_quick_start.md @@ -0,0 +1,35 @@ +# Quick Start + +## 1. Install + +Use composer to install this library. +```bash +composer require spameri/elastic +``` + +## 2. Configure + +You need to set up few things first, before you can dive into ElasticSearch. + +### I. Register extension + +In your configuration neon file you need to add these lines to `extension:` section. + +```yaml +extensions: + spameriElasticSearch: \Spameri\Elastic\DI\SpameriElasticSearchExtension + console: Kdyby\Console\DI\ConsoleExtension + monolog: Kdyby\Monolog\DI\MonologExtension +``` + + + + + + + + + + + + diff --git a/doc/01_intro.md b/doc/01_intro.md index 320d0d8..ea9e501 100644 --- a/doc/01_intro.md +++ b/doc/01_intro.md @@ -13,19 +13,19 @@ Monolog is required by elasticsearch/elasticsearch and we can use existing exten ```neon extensions: - elasticSearch: \Spameri\Elastic\DI\ElasticSearchExtension + spameriElasticSearch: \Spameri\Elastic\DI\SpameriElasticSearchExtension console: Kdyby\Console\DI\ConsoleExtension monolog: Kdyby\Monolog\DI\MonologExtension ``` Then configure where is your ElasticSearch. ```neon -elasticSearch: +spameriElasticSearch: host: 127.0.0.1 port: 9200 ``` -For more config options see default values in `\Spameri\Elastic\DI\ElasticSearchExtension::$defaults`. [Here](../src/DI/ElasticSearchExtension.php#L9). +For more config options see default values in `\Spameri\Elastic\DI\SpameriElasticSearchExtension::$defaults`. [Here](../src/DI/ElasticSearchExtension.php#L9). #### Raw client usage - After this configuration you are ready to use ElasticSearch in your Nette application. diff --git a/doc/02_neon_configuration.md b/doc/02_neon_configuration.md index c91e85a..91e158b 100644 --- a/doc/02_neon_configuration.md +++ b/doc/02_neon_configuration.md @@ -13,7 +13,7 @@ have to worry about it because of type deprecation. More details here https://ww - Entity definition is in neon under namespace `elasticSearch.entities.EntityName` continuing our example in file `app/ProductModule/Config/Product.neon`: ```neon -elasticSearch: +spameriElasticSearch: entities: Product: index: shop_product @@ -25,7 +25,7 @@ This means newly introduced fields not specified in mapping will throw error whe all fields introduced and specify their type. But if your application can add fields as needed you need to remember this strict limitation or just do not enable it. ```neon -elasticSearch: +spameriElasticSearch: entities: Product: dynamic: strict @@ -33,7 +33,7 @@ elasticSearch: - Now to specify entity mapping. Each object or encapsulation of sub fields stars with `properties:` then property name and under it you can specify type and analyzer. ```neon -elasticSearch: +spameriElasticSearch: entities: Product: properties: @@ -46,7 +46,7 @@ elasticSearch: - ElasticSearch default analyzers: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html - Subfields example: ```neon -elasticSearch: +spameriElasticSearch: entities: Product: properties: diff --git a/tests/SpameriTests/Data/Config/Common.neon b/tests/SpameriTests/Data/Config/Common.neon index f68cf82..6688643 100644 --- a/tests/SpameriTests/Data/Config/Common.neon +++ b/tests/SpameriTests/Data/Config/Common.neon @@ -1,7 +1,7 @@ extensions: - elasticSearch: Spameri\Elastic\DI\ElasticSearchExtension + spameriElasticSearch: Spameri\Elastic\DI\SpameriElasticSearchExtension -elasticSearch: +spameriElasticSearch: host: 127.0.0.1 port: 9200 diff --git a/tests/SpameriTests/Data/Config/Person.neon b/tests/SpameriTests/Data/Config/Person.neon index 06e0701..44bc514 100644 --- a/tests/SpameriTests/Data/Config/Person.neon +++ b/tests/SpameriTests/Data/Config/Person.neon @@ -1,4 +1,4 @@ -elasticSearch: +spameriElasticSearch: entities: Person: index: spameri_person diff --git a/tests/SpameriTests/Data/Config/Video.neon b/tests/SpameriTests/Data/Config/Video.neon index cffbe5d..c6015ab 100644 --- a/tests/SpameriTests/Data/Config/Video.neon +++ b/tests/SpameriTests/Data/Config/Video.neon @@ -1,4 +1,4 @@ -elasticSearch: +spameriElasticSearch: entities: Video: index: spameri_video From 8fbcd2415fb7c1c9611a1ead8060d3531c810977 Mon Sep 17 00:00:00 2001 From: VBoss Date: Fri, 25 Oct 2019 16:09:09 +0200 Subject: [PATCH 6/9] QuickStart guide - Configure ES url --- doc/00_quick_start.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/00_quick_start.md b/doc/00_quick_start.md index 89272dd..55f3e3c 100644 --- a/doc/00_quick_start.md +++ b/doc/00_quick_start.md @@ -22,9 +22,19 @@ extensions: monolog: Kdyby\Monolog\DI\MonologExtension ``` +### II. Configure +Now you need to tell library where is ElasticSearch running. Default values are **localhost** +and port **9200**. That means if you are running ElasticSearch locally with default port, no +need to configure anything. +```yaml +spameriElasticSearch: + host: 192.168.0.14 + port: 9200 +``` +### III. From cf77334e75e81fcf3a6f85b775082ea9812d014b Mon Sep 17 00:00:00 2001 From: VBoss Date: Fri, 25 Oct 2019 16:34:01 +0200 Subject: [PATCH 7/9] QuickStart guide - Configure Entity --- doc/00_quick_start.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/00_quick_start.md b/doc/00_quick_start.md index 55f3e3c..bf446a4 100644 --- a/doc/00_quick_start.md +++ b/doc/00_quick_start.md @@ -34,10 +34,28 @@ spameriElasticSearch: port: 9200 ``` -### III. - +### III. Configure Entity +Next step is to configure your first entity. This entity is for e-shop product. +```yaml +1.| spameriElasticSearch: +2.| entities: +3.| SimpleProduct: +4.| index: spameri_simple_product +5.| dynamic: strict +6.| config: @simpleProductConfig +7.| properties: +``` +- First line is extensionName +- Second line is entities config array +- Third line is EntityName +- Fourth line is index name for this entity +- Fifth line is for specifying whether index should accept new not specified fields +- Sixth line is reference to where is object with entity configuration +- Seventh line is where you can configure your entity within this neon + +## 3. From a0e2a0429db199198c2c5088b7b22a1d17a68868 Mon Sep 17 00:00:00 2001 From: VBoss Date: Sat, 26 Oct 2019 14:45:36 +0200 Subject: [PATCH 8/9] QuickStart guide - Configure Entity --- doc/00_quick_start.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/00_quick_start.md b/doc/00_quick_start.md index bf446a4..3aebe4c 100644 --- a/doc/00_quick_start.md +++ b/doc/00_quick_start.md @@ -7,6 +7,8 @@ Use composer to install this library. composer require spameri/elastic ``` +--- + ## 2. Configure You need to set up few things first, before you can dive into ElasticSearch. @@ -55,6 +57,8 @@ Next step is to configure your first entity. This entity is for e-shop product. - Sixth line is reference to where is object with entity configuration - Seventh line is where you can configure your entity within this neon +--- + ## 3. From 6e4cab7a3f704579cb3afbf30c2f83e0f5cba5ed Mon Sep 17 00:00:00 2001 From: VBoss Date: Sat, 26 Oct 2019 15:36:55 +0200 Subject: [PATCH 9/9] Quick start --- doc/00_quick_start.md | 389 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 388 insertions(+), 1 deletion(-) diff --git a/doc/00_quick_start.md b/doc/00_quick_start.md index 3aebe4c..258e3a7 100644 --- a/doc/00_quick_start.md +++ b/doc/00_quick_start.md @@ -59,9 +59,396 @@ Next step is to configure your first entity. This entity is for e-shop product. --- -## 3. +## 3. Create entity class +```php +id; +} + + +public function entityVariables(): array +{ + return \get_object_vars($this); +} +``` + +### Factory +````php +class SimpleProductFactory implements \Spameri\Elastic\Factory\IEntityFactory +{ + + public function create(\Spameri\ElasticQuery\Response\Result\Hit $hit) : \Generator + { + yield new \App\ProductModule\Entity\SimpleProduct( + new \Spameri\Elastic\Entity\Property\ElasticId($hit->id()), + $hit->getValue('databaseId'), + $hit->getValue('name'), + $hit->getValue('content'), + $hit->getValue('alias'), + $hit->getValue('image'), + $hit->getValue('price'), + $hit->getValue('availability'), + $hit->getValue('tags'), + $hit->getValue('categories') + ); + } + +} +```` + +### CollectionFactory +````php +class SimpleProductCollectionFactory implements \Spameri\Elastic\Factory\ICollectionFactory +{ + + public function create( + \Spameri\Elastic\Model\IService $service + , array $elasticIds = [] + , \Spameri\Elastic\Entity\IElasticEntity ... $entityCollection + ) : \Spameri\Elastic\Entity\IElasticEntityCollection + { + return new \App\ProductModule\Entity\ProductCollection($service, $elasticIds, ... $entityCollection); + } + +} +```` + +## 4. Index Configuring +````php +class SimpleProductConfig implements \Spameri\Elastic\Settings\IndexConfigInterface +{ + public function __construct( + string $indexName + ) + { + $this->indexName = $indexName; + } +} +```` + +`public function provide(): \Spameri\ElasticQuery\Mapping\Settings` + +````php +$settings = new \Spameri\ElasticQuery\Mapping\Settings($this->indexName); +$czechDictionary = new \Spameri\ElasticQuery\Mapping\Analyzer\Custom\CzechDictionary(); +$settings->addAnalyzer($czechDictionary); + +$lowerCase = new \Spameri\ElasticQuery\Mapping\Analyzer\Custom\Lowercase(); +$settings->addAnalyzer($lowerCase); +```` + +````php +$settings->addMappingField( + new \Spameri\ElasticQuery\Mapping\Settings\Mapping\Field( + 'databaseId', + \Spameri\Elastic\Model\ValidateMapping\AllowedValues::TYPE_KEYWORD + ) +); +$settings->addMappingField( + new \Spameri\ElasticQuery\Mapping\Settings\Mapping\Field( + 'name', + \Spameri\Elastic\Model\ValidateMapping\AllowedValues::TYPE_TEXT, + $czechDictionary + ) +); +$settings->addMappingField( + new \Spameri\ElasticQuery\Mapping\Settings\Mapping\Field( + 'content', + \Spameri\Elastic\Model\ValidateMapping\AllowedValues::TYPE_TEXT, + $czechDictionary + ) +); +```` + + +````php +$settings->addMappingField( + new \Spameri\ElasticQuery\Mapping\Settings\Mapping\Field( + 'tags', + \Spameri\Elastic\Model\ValidateMapping\AllowedValues::TYPE_TEXT, + $lowerCase + ) +); +```` + + +## 5. Export data to ElasticSearch + +````php +class ExportToElastic extends \Spameri\Elastic\Import\Run +{ + + public function __construct( + string $logDir = 'log', + \Symfony\Component\Console\Output\ConsoleOutput $output, + \Spameri\Elastic\Import\Run\NullLoggerHandler $loggerHandler, + \Spameri\Elastic\Import\Lock\NullLock $lock, + \Spameri\Elastic\Import\RunHandler\NullHandler $runHandler, + + \App\ProductModule\Model\ExportToElastic\DataProvider $dataProvider, + \App\ProductModule\Model\ExportToElastic\PrepareImportData $prepareImportData, + \App\ProductModule\Model\ExportToElastic\DataImport $dataImport, + + \Spameri\Elastic\Import\AfterImport\NullAfterImport $afterImport + ) + { + parent::__construct($logDir, $output, $loggerHandler, $lock, $runHandler, $dataProvider, $prepareImportData, $dataImport, $afterImport); + } + +} +```` + + +````php +class DataProvider implements \Spameri\Elastic\Import\DataProviderInterface +{ + public function provide(\Spameri\Elastic\Import\Run\Options $options): \Generator + { + $query = $this->connection->select('*')->from('table'); + + while ($hasResults) { + $items = $query->fetchAll($offset, $limit); + + yield from $items; + + if ( ! \count($items)) { + $hasResults = FALSE; + + } else { + $offset += $limit; + } + } + } +} +```` +````php + +class PrepareImportData implements \Spameri\Elastic\Import\PrepareImportDataInterface +{ + + public function prepare($entityData): \Spameri\Elastic\Entity\AbstractImport + { + $imageSrc = '//via.placeholder.com/150x150'; + $elasticId = NULL; + $tags = []; + $categories = []; + return new \App\ProductModule\Entity\SimpleProduct( + $elasticId, + $entityData['id'], + $entityData['name'], + $entityData['content_description'], + $entityData['alias'], + $imageSrc, + $entityData['amount'], + $entityData['availability_id'] === 1 ? 'Skladem' : 'Nedostupné', + $tags, + $categories + ); + } + +} +```` + +````php +class DataImport implements \Spameri\Elastic\Import\DataImportInterface +{ + + /** + * @param \App\ProductModule\Entity\SimpleProduct $entity + */ + public function import( + \Spameri\Elastic\Entity\AbstractImport $entity + ): \Spameri\Elastic\Import\ResponseInterface + { + $id = $this->productService->insert($entity); + + return new \Spameri\Elastic\Import\Response\SimpleResponse( + $id, + $entity + ); + } + +} +```` + +````php +$options = new \Spameri\Elastic\Import\Run\Options(600); + +// Clear index +try { + $this->delete->execute($this->simpleProductConfig->provide()->indexName()); +} catch (\Spameri\Elastic\Exception\ElasticSearchException $exception) {} + +// Create index +$this->create->execute( + $this->simpleProductConfig->provide()->indexName(), + $this->simpleProductConfig->provide()->toArray() +); + +// Export +$this->exportToElastic->execute($options); +```` + +## 6. Presenter, Form, Template +````php +class SimpleProductListPresenter extends \App\Presenter\BasePresenter +{ + + public function renderDefault($queryString): void + { + $query = $this->buildQuery($queryString); + + try { + $products = $this->productService->getAllBy($query); + + } catch (\Spameri\Elastic\Exception\ElasticSearchException $exception) { + $products = []; + } + + $this->getTemplate()->add( + 'products', + $products + ); + $this->getTemplate()->add( + 'queryString', + $queryString + ); + } + +} +```` + +````php +public function createComponentSearchForm() :\Nette\Application\UI\Form +{ + $form = new \Nette\Application\UI\Form(); + $form->addText('queryString', 'query') + ->setAttribute('class', 'inp-text suggest') + ; + + $form->addSubmit('search', 'Search'); + + $form->onSuccess[] = function () use ($form) { + $this->redirect( + 301, + ':Product:SimpleProductList:default', + [ + 'queryString' => $form->getValues()->queryString, + ] + ); + }; + + return $form; +} +```` + +````php +{control searchForm} +

You have searched: {$queryString}

+ +