Skip to content
This repository has been archived by the owner on Apr 22, 2022. It is now read-only.

Commit

Permalink
Support for Entity hydration, Pagination
Browse files Browse the repository at this point in the history
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
MrHash committed Apr 29, 2013
1 parent cf274a9 commit aadbf8c
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 55 deletions.
41 changes: 41 additions & 0 deletions lib/Doctrine/Search/Configuration.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Search\Mapping\ClassMetadataFactory; use Doctrine\Search\Mapping\ClassMetadataFactory;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Search\SerializerInterface;
use Doctrine\Search\Serializer\CallbackSerializer;
use Doctrine\ORM\EntityManager;


/** /**
* Configuration SearchManager * Configuration SearchManager
Expand Down Expand Up @@ -134,4 +137,42 @@ public function getClassMetadataFactory()
return $this->attributes['classMetadataFactory']; 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'];
}
} }
9 changes: 5 additions & 4 deletions lib/Doctrine/Search/ElasticSearch/Client.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


use Doctrine\Search\SearchClientInterface; use Doctrine\Search\SearchClientInterface;
use Doctrine\Search\Mapping\ClassMetadata; use Doctrine\Search\Mapping\ClassMetadata;
use Elastica\Client as Elastica_Client; use Elastica\Client as ElasticaClient;
use Elastica\Type\Mapping; use Elastica\Type\Mapping;
use Elastica\Document; use Elastica\Document;
use Elastica\Index; use Elastica\Index;
Expand All @@ -44,9 +44,9 @@ class Client implements SearchClientInterface
private $client; private $client;


/** /**
* @param Elastica_Client $client * @param ElasticaClient $client
*/ */
public function __construct(Elastica_Client $client) public function __construct(ElasticaClient $client)
{ {
$this->client = $client; $this->client = $client;
} }
Expand Down Expand Up @@ -90,7 +90,8 @@ public function removeAll($index, $type)
*/ */
public function find($index, $type, $query) public function find($index, $type, $query)
{ {

$type = $this->getIndex($index)->getType($type);
return $type->search($query);
} }


/** /**
Expand Down
40 changes: 40 additions & 0 deletions lib/Doctrine/Search/ElasticSearch/PaginationAdapter.php
Original file line number Original file line Diff line number Diff line change
@@ -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();
}
}
187 changes: 187 additions & 0 deletions lib/Doctrine/Search/Query.php
Original file line number Original file line Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit aadbf8c

Please sign in to comment.