Permalink
Browse files

Support for Entity hydration, Pagination

Search manager now supports querying through a magic wrapper class.
Hydration is provided by default when an entity manager is provided
through configuration or injection, or directly through a custom
Doctrine Query if provided. Hydration can be bypassed to return search
results directly. Currently only functional for ElasticSearch.
  • Loading branch information...
1 parent cf274a9 commit aadbf8c9b2027c72681cece837131e8979b998a9 @MrHash MrHash committed Apr 29, 2013
@@ -23,6 +23,9 @@
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Search\Mapping\ClassMetadataFactory;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
+use Doctrine\Search\SerializerInterface;
+use Doctrine\Search\Serializer\CallbackSerializer;
+use Doctrine\ORM\EntityManager;
/**
* Configuration SearchManager
@@ -134,4 +137,42 @@ public function getClassMetadataFactory()
return $this->attributes['classMetadataFactory'];
}
+ /**
+ * Sets an entity serializer
+ *
+ * @param SerializerInterface $serializer
+ */
+ public function setEntitySerializer(SerializerInterface $serializer)
+ {
+ $this->attributes['serializer'] = $serializer;
+ }
+
+ /**
+ * Gets the entity serializer or provides a default if not set
+ *
+ * @return SerializerInterface
+ */
+ public function getEntitySerializer()
+ {
+ if(isset($this->attributes['serializer'])) {
+ return $this->attributes['serializer'];
+ }
+ return new CallbackSerializer();
+ }
+
+ /**
+ * @param EntityManager $entityManager
+ */
+ public function setEntityManager(EntityManager $entityManager)
+ {
+ $this->attributes['entityManager'] = $serializer;
+ }
+
+ /**
+ * @return EntityManager
+ */
+ public function getEntityManager()
+ {
+ if(isset($this->attributes['entityManager'])) return $this->attributes['entityManager'];
+ }
}
@@ -23,7 +23,7 @@
use Doctrine\Search\SearchClientInterface;
use Doctrine\Search\Mapping\ClassMetadata;
-use Elastica\Client as Elastica_Client;
+use Elastica\Client as ElasticaClient;
use Elastica\Type\Mapping;
use Elastica\Document;
use Elastica\Index;
@@ -44,9 +44,9 @@ class Client implements SearchClientInterface
private $client;
/**
- * @param Elastica_Client $client
+ * @param ElasticaClient $client
*/
- public function __construct(Elastica_Client $client)
+ public function __construct(ElasticaClient $client)
{
$this->client = $client;
}
@@ -90,7 +90,8 @@ public function removeAll($index, $type)
*/
public function find($index, $type, $query)
{
-
+ $type = $this->getIndex($index)->getType($type);
+ return $type->search($query);
}
/**
@@ -0,0 +1,40 @@
+<?php
+
+namespace Doctrine\Search\ElasticSearch;
+
+use Zend\Paginator\Adapter\AdapterInterface;
+use Doctrine\Search\Query;
+
+class PaginationAdapter implements AdapterInterface
+{
+ /** @var Query */
+ protected $query;
+
+ /** @var integer */
+ protected $hits;
+
+ public function __construct(Query $query)
+ {
+ $this->query = $query;
+ }
+
+ public function getItems($iOffset, $iItemCountPerPage)
+ {
+ $this->query->setFrom($iOffset);
+ $this->query->setLimit($iItemCountPerPage);
+
+ $resultSet = $this->query->getResult();
+
+ // Return Elastica\Results if hydration is bypassed
+ if($resultSet instanceof \Elastica\ResultSet) {
+ return $resultSet->getResults();
+ }
+
+ return $resultSet;
+ }
+
+ public function count()
+ {
+ return $this->query->count();
+ }
+}
@@ -0,0 +1,187 @@
+<?php
+
+namespace Doctrine\Search;
+
+use Doctrine\ORM\Query as DoctrineQuery;
+use Doctrine\Search\Exception\DoctrineSearchException;
+
+class Query
+{
+ const HYDRATE_BYPASS = -1;
+
+ const HYDRATION_PARAMETER = 'ids';
+
+ /** @var SearchManager */
+ protected $_sm;
+
+ /** @var object */
+ protected $query;
+
+ /** @var DoctrineQuery */
+ protected $doctrineQuery;
+
+ /** @var string */
+ protected $hydrationParameter = self::HYDRATION_PARAMETER;
+
+ /** @var string */
+ protected $entityClass;
+
+ /** @var integer **/
+ protected $hydrationMode = DoctrineQuery::HYDRATE_OBJECT;
+
+ /** @var boolean */
+ protected $useResultCache;
+
+ /** @var integer */
+ protected $cacheLifetime;
+
+ /** @var integer **/
+ protected $count;
+
+ public function __construct(SearchManager $sm)
+ {
+ $this->_sm = $sm;
+ }
+
+ /**
+ * Magic method to pass query building to the underlying query
+ * object, saving the need to abstract.
+ *
+ * @param string $method
+ * @param array $arguments
+ */
+ public function __call($method, $arguments)
+ {
+ call_user_func_array(array($this->query, $method), $arguments);
+ return $this;
+ }
+
+ /**
+ * Specifies the searchable entity class to search against.
+ *
+ * @param string $entityClass
+ */
+ public function from($entityClass)
+ {
+ $this->entityClass = $entityClass;
+ return $this;
+ }
+
+ /**
+ * Set the query object to be executed on the search engine
+ *
+ * @param mixes $query
+ */
+ public function searchWith($query)
+ {
+ $this->query = $query;
+ return $this;
+ }
+
+ protected function getSearchManager()
+ {
+ return $this->_sm;
+ }
+
+ /**
+ * Set the hydration mode from the Doctrine\ORM\Query modes
+ * or bypass and return search result directly from the client
+ *
+ * @param integer $mode
+ */
+ public function setHydrationMode($mode)
+ {
+ $this->hydrationMode = $mode;
+ return $this;
+ }
+
+ /**
+ * If hydrating with Doctrine then you can use the result cache
+ * on the default or provided query
+ *
+ * @param boolean $useCache
+ * @param integer $cacheLifetime
+ */
+ public function useResultCache($useCache, $cacheLifetime = null)
+ {
+ $this->useCache = $useCache;
+ $this->cacheLifetime = $cacheLifetime;
+ return $this;
+ }
+
+ /**
+ * Return the total hit count for the given query as provided by
+ * the search engine.
+ */
+ public function count()
+ {
+ return $this->count;
+ }
+
+ /**
+ * Set a custom Doctrine Query to execute in order to hydrate the search
+ * engine results into Doctrine entities. The assumption is made the the
+ * search engine result id is correlated to the Doctrine entity id. An
+ * optional query parameter can be specified.
+ *
+ * @param DoctrineQuery $doctrineQuery
+ * @param string $parameter
+ */
+ public function hydrateWith(DoctrineQuery $doctrineQuery, $parameter = null)
+ {
+ $this->doctrineQuery = $doctrineQuery;
+ if($parameter) $this->hydrationParameter = $parameter;
+ return $this;
+ }
+
+ /**
+ * Return a provided Doctrine Query or a default.
+ *
+ * @return DoctrineQuery
+ */
+ protected function getHydrationQuery()
+ {
+ if($this->doctrineQuery) return $this->doctrineQuery;
+
+ $em = $this->getSearchManager()->getEntityManager();
+ if(!$em) throw new DoctrineSearchException('Doctrine EntityManager is required when hydrating results to entities without having provided a custom query.');
+
+ return $em->createQueryBuilder()
+ ->select('e')
+ ->from($this->entityClass, 'e')
+ ->where('e.id IN (:'.self::HYDRATION_PARAMETER.')')
+ ->getQuery();
+ }
+
+ /**
+ * Execute search and hydrate results if required.
+ *
+ * @param integer $hydrationMode
+ * @throws \Exception
+ * @return mixed
+ */
+ public function getResult($hydrationMode = null)
+ {
+ if($hydrationMode) $this->hydrationMode = $hydrationMode;
+
+ $classMetadata = $this->getSearchManager()->getClassMetadata($this->entityClass);
+ $resultSet = $this->getSearchManager()->find($classMetadata->index, $classMetadata->type, $this->query);
+
+ switch(get_class($resultSet)) {
+ case 'Elastica\ResultSet':
+ $this->count = $resultSet->getTotalHits();
+ $results = $resultSet->getResults();
+ break;
+ default:
+ throw new \Exception('Unknown result set class');
+ }
+
+ if($this->hydrationMode == self::HYDRATE_BYPASS) return $resultSet;
+
+ $ids = array_map(function($result) { return $result->getId(); }, $results);
+ return $this->getHydrationQuery()
+ ->setParameter($this->hydrationParameter, $ids)
+ ->useResultCache($this->useResultCache, $this->cacheLifetime)
+ ->getResult($this->hydrationMode);
+ }
+}
Oops, something went wrong.

0 comments on commit aadbf8c

Please sign in to comment.