<?php
/*
* This file is part of the ONGR package.
*
* (c) NFQ Technologies UAB <info@nfq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ONGR\ElasticsearchBundle\Service;
use ONGR\ElasticsearchBundle\Result\ArrayIterator;
use ONGR\ElasticsearchBundle\Result\RawIterator;
use ONGR\ElasticsearchDSL\Query\FullText\QueryStringQuery;
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
use ONGR\ElasticsearchDSL\Search;
use ONGR\ElasticsearchDSL\Sort\FieldSort;
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
/**
* Document repository class.
*/
class Repository
{
/**
* @var Manager
*/
private $manager;
/**
* @var string Fully qualified class name
*/
private $className;
/**
* @var string Elasticsearch type name
*/
private $type;
/**
* Constructor.
*
* @param Manager $manager
* @param string $className
*/
public function __construct($manager, $className)
{
if (!is_string($className)) {
throw new \InvalidArgumentException('Class name must be a string.');
}
if (!class_exists($className)) {
throw new \InvalidArgumentException(
sprintf('Cannot create repository for non-existing class "%s".', $className)
);
}
$this->manager = $manager;
$this->className = $className;
$this->type = $this->resolveType($className);
}
/**
* Returns elasticsearch manager used in the repository.
*
* @return Manager
*/
public function getManager()
{
return $this->manager;
}
/**
* @return array
*/
public function getType()
{
return $this->type;
}
/**
* Returns a single document data by ID or null if document is not found.
*
* @param string $id Document ID to find
* @param string $routing Custom routing for the document
*
* @return object
*/
public function find($id, $routing = null)
{
return $this->manager->find($this->type, $id, $routing);
}
/**
* Returns documents by a set of ids
*
* @param array $ids
*
* @return DocumentIterator The objects.
*/
public function findByIds(array $ids)
{
$args = [];
$manager = $this->getManager();
$args['body']['docs'] = [];
$args['index'] = $manager->getIndexName();
$args['type'] = $this->getType();
foreach ($ids as $id) {
$args['body']['docs'][] = [
'_id' => $id
];
}
$mgetResponse = $manager->getClient()->mget($args);
$return = [
'hits' => [
'hits' => [],
'total' => 0,
]
];
foreach ($mgetResponse['docs'] as $item) {
if ($item['found']) {
$return['hits']['hits'][] = $item;
}
}
$return['hits']['total'] = count($return['hits']['hits']);
return new DocumentIterator($return, $manager);
}
/**
* Finds documents by a set of criteria.
*
* @param array $criteria Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
* @param array|null $orderBy Example: ['name' => 'ASC', 'surname' => 'DESC'].
* @param int|null $limit Example: 5.
* @param int|null $offset Example: 30.
*
* @return array|DocumentIterator The objects.
*/
public function findBy(
array $criteria,
array $orderBy = [],
$limit = null,
$offset = null
) {
$search = $this->createSearch();
if ($limit !== null) {
$search->setSize($limit);
}
if ($offset !== null) {
$search->setFrom($offset);
}
foreach ($criteria as $field => $value) {
if (preg_match('/^!(.+)$/', $field)) {
$boolType = BoolQuery::MUST_NOT;
$field = preg_replace('/^!/', '', $field);
} else {
$boolType = BoolQuery::MUST;
}
$search->addQuery(
new QueryStringQuery(is_array($value) ? implode(' OR ', $value) : $value, ['default_field' => $field]),
$boolType
);
}
foreach ($orderBy as $field => $direction) {
$search->addSort(new FieldSort($field, $direction));
}
return $this->findDocuments($search);
}
/**
* Finds a single document by a set of criteria.
*
* @param array $criteria Example: ['group' => ['best', 'worst'], 'job' => 'medic'].
* @param array|null $orderBy Example: ['name' => 'ASC', 'surname' => 'DESC'].
*
* @return object|null The object.
*/
public function findOneBy(array $criteria, array $orderBy = [])
{
return $this->findBy($criteria, $orderBy, 1, null)->current();
}
/**
* Returns search instance.
*
* @return Search
*/
public function createSearch()
{
return new Search();
}
/**
* Parses scroll configuration from raw response.
*
* @param array $raw
* @param string $scrollDuration
*
* @return array
*/
public function getScrollConfiguration($raw, $scrollDuration)
{
$scrollConfig = [];
if (isset($raw['_scroll_id'])) {
$scrollConfig['_scroll_id'] = $raw['_scroll_id'];
$scrollConfig['duration'] = $scrollDuration;
}
return $scrollConfig;
}
/**
* Returns DocumentIterator with composed Document objects from array response.
*
* @param Search $search
*
* @return DocumentIterator
*/
public function findDocuments(Search $search)
{
$results = $this->executeSearch($search);
return new DocumentIterator(
$results,
$this->getManager(),
$this->getScrollConfiguration($results, $search->getScroll())
);
}
/**
* Returns ArrayIterator with access to unmodified documents directly.
*
* @param Search $search
*
* @return ArrayIterator
*/
public function findArray(Search $search)
{
$results = $this->executeSearch($search);
return new ArrayIterator(
$results,
$this->getManager(),
$this->getScrollConfiguration($results, $search->getScroll())
);
}
/**
* Returns RawIterator with access to node with all returned values included.
*
* @param Search $search
*
* @return RawIterator
*/
public function findRaw(Search $search)
{
$results = $this->executeSearch($search);
return new RawIterator(
$results,
$this->getManager(),
$this->getScrollConfiguration($results, $search->getScroll())
);
}
/**
* Executes search to the elasticsearch and returns raw response.
*
* @param Search $search
*
* @return array
*/
private function executeSearch(Search $search)
{
return $this->getManager()->search([$this->getType()], $search->toArray(), $search->getUriParams());
}
/**
* Counts documents by given search.
*
* @param Search $search
* @param array $params
* @param bool $returnRaw If set true returns raw response gotten from client.
*
* @return int|array
*/
public function count(Search $search, array $params = [], $returnRaw = false)
{
$body = array_merge(
[
'index' => $this->getManager()->getIndexName(),
'type' => $this->type,
'body' => $search->toArray(),
],
$params
);
$results = $this
->getManager()
->getClient()->count($body);
if ($returnRaw) {
return $results;
} else {
return $results['count'];
}
}
/**
* Removes a single document data by ID.
*
* @param string $id Document ID to remove
* @param string $routing Custom routing for the document
*
* @return array
*
* @throws \LogicException
*/
public function remove($id, $routing = null)
{
$params = [
'index' => $this->getManager()->getIndexName(),
'type' => $this->type,
'id' => $id,
];
if ($routing) {
$params['routing'] = $routing;
}
$response = $this->getManager()->getClient()->delete($params);
return $response;
}
/**
* Partial document update.
*
* @param string $id Document id to update.
* @param array $fields Fields array to update.
* @param string $script Groovy script to update fields.
* @param array $params Additional parameters to pass to the client.
*
* @return array
*/
public function update($id, array $fields = [], $script = null, array $params = [])
{
$body = array_filter(
[
'doc' => $fields,
'script' => $script,
]
);
$params = array_merge(
[
'id' => $id,
'index' => $this->getManager()->getIndexName(),
'type' => $this->type,
'body' => $body,
],
$params
);
return $this->getManager()->getClient()->update($params);
}
/**
* Resolves elasticsearch type by class name.
*
* @param string $className
*
* @return array
*/
private function resolveType($className)
{
return $this->getManager()->getMetadataCollector()->getDocumentType($className);
}
/**
* Returns fully qualified class name.
*
* @return string
*/
public function getClassName()
{
return $this->className;
}
}