blob: 1b6890b82e5aa0c0bd882becb1c01ec6e7c952e4 [file] [log] [blame]
<?php
/**
* File containing the ezcSearchSolrHandler class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Search
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Solr backend implementation
*
* @package Search
* @version //autogentag//
*/
class ezcSearchSolrHandler implements ezcSearchHandler, ezcSearchIndexHandler
{
/**
* Holds the connection to Solr
*
* @var resource(stream)
*/
public $connection;
/**
* Hosts the hostname of the solr server
*
* @var string
*/
private $host;
/**
* Hosts the port number of the solr server
*
* @var int
*/
private $port;
/**
* Hosts the location of the interface on the solr server
*
* @var string
*/
private $location;
/**
* Stores the transaction nesting depth.
*
* @var integer
*/
private $inTransaction;
/**
* Creates a new Solr handler connection
*
* @param string $host
* @param int $port
* @param string $location
*/
public function __construct( $host = 'localhost', $port = 8983, $location = '/solr' )
{
$this->host = $host;
$this->port = $port;
$this->location = $location;
$this->inTransaction = 0;
$this->connect();
}
/**
* Connects to Solr
*
* @throws ezcSearchCanNotConnectException if a connection can not be established.
*/
protected function connect()
{
$this->connection = @stream_socket_client( "tcp://{$this->host}:{$this->port}" );
if ( !$this->connection )
{
throw new ezcSearchCanNotConnectException( 'solr', "http://{$this->host}:{$this->port}{$this->location}" );
}
}
/**
* Closes the connection, and re-connects to Solr
*
* @throws ezcSearchCanNotConnectException if a connection can not be established.
*/
public function reConnect()
{
// Added the @ here because the connection could be in a broken state,
// or already be closed. There is no way to check for that properly, so
// we'll have to use the shut-up operator.
@fclose( $this->connection );
$this->connect();
}
/**
* Starts a transaction for indexing.
*
* When using a transaction, the amount of processing that solr does
* decreases, increasing indexing performance. Without this, the component
* sends a commit after every document that is indexed. Transactions can be
* nested, when commit() is called the same number of times as
* beginTransaction(), the component sends a commit.
*/
public function beginTransaction()
{
$this->inTransaction++;
}
/**
* Ends a transaction and calls commit.
*
* As transactions can be nested, this method will only call commit when
* all the nested transactions have been ended.
*
* @throws ezcSearchTransactionException if no transaction is active.
*/
public function commit()
{
if ( $this->inTransaction < 1 )
{
throw new ezcSearchTransactionException( 'Cannot commit without a transaction.' );
}
$this->inTransaction--;
if ( $this->inTransaction == 0 )
{
$this->runCommit();
}
}
/**
* Returns a line with a maximum length of $maxLength from the connection
*
* @param int $maxLength
* @return string
*/
private function getLine( $maxLength = false )
{
$line = ''; $data = '';
while ( strpos( $line, "\n" ) === false )
{
$line = fgets( $this->connection, $maxLength ? $maxLength + 1: 512 );
/* If solr aborts the connection, fgets() will
* return false. We need to throw an exception here to prevent
* the calling code from looping indefinitely. */
if ( $line === false )
{
$this->connection = null;
throw new ezcSearchNetworkException( 'Could not read from the stream. It was probably terminated by the host.' );
}
$data .= $line;
if ( strlen( $data ) >= $maxLength )
{
break;
}
}
return $data;
}
/**
* Sends the raw command $type to Solr
*
* @param string $type
* @param array(string=>string) $queryString
* @return string
* @access private
*/
public function sendRawGetCommand( $type, $queryString = array() )
{
$statusCode = 0;
$queryPart = '';
if ( count( $queryString ) )
{
$queryPart = '/?'. http_build_query( $queryString );
}
$cmd = "GET {$this->location}/{$type}{$queryPart} HTTP/1.1\n";
$cmd .= "Host: {$this->host}:{$this->port}\n";
$cmd .= "User-Agent: eZ Components Search\n";
$cmd .= "Content-Type: text/xml; charset=utf-8\n";
$cmd .= "\n";
fwrite( $this->connection, $cmd );
// read http header
$line = '';
$chunked = false;
while ( $line != "\r\n" )
{
$line = $this->getLine();
if ( preg_match( '@HTTP[^ ]+ +([0-9]{3}) .*@', $line, $m ) )
{
$statusCode = $m[1];
$statusMessage = trim( $line );
}
if ( preg_match( '@Content-Length: (\d+)@', $line, $m ) )
{
$expectedLength = $m[1];
}
if ( preg_match( '@Transfer-Encoding: chunked@', $line ) )
{
$chunked = true;
}
}
$data = '';
$chunkLength = -1;
// read http content with chunked encoding
if ( $chunked )
{
while ( $chunkLength !== 0 )
{
// fetch chunk length
$line = $this->getLine();
$chunkLength = hexdec( $line );
$size = 1;
while ( $size < $chunkLength )
{
$line = $this->getLine( $chunkLength );
$size += strlen( $line );
$data .= $line;
}
$line = $this->getLine();
}
}
else // without chunked encoding
{
$size = 1;
while ( $size < $expectedLength )
{
$line = $this->getLine( $expectedLength );
$size += strlen( $line );
$data .= $line;
}
}
// check http status code
if ( $statusCode >= 400 )
{
// Something went wrong.
throw new ezcSearchNetworkException( "The HTTP server reported: $statusMessage", $data );
}
return $data;
}
/**
* Sends a post command $type to Solr and reads the result
*
* @param string $type
* @param array(string=>string) $queryString
* @param string $data
* @return string
* @access private
*/
public function sendRawPostCommand( $type, $queryString, $data )
{
$statusCode = 0;
$reConnect = false;
$queryPart = '';
if ( count( $queryString ) )
{
$queryPart = '/?'. http_build_query( $queryString );
}
$length = strlen( $data );
$cmd = "POST {$this->location}/{$type}{$queryPart} HTTP/1.1\n";
$cmd .= "Host: {$this->host}:{$this->port}\n";
$cmd .= "User-Agent: eZ Components Search\n";
$cmd .= "Content-Type: text/xml; charset=utf-8\n";
$cmd .= "Content-Length: $length\n";
$cmd .= "\n";
$cmd .= $data;
fwrite( $this->connection, $cmd );
// read http header
$line = '';
while ( $line != "\r\n" )
{
$line = $this->getLine();
if ( preg_match( '@HTTP[^ ]+ +([0-9]{3}) .*@', $line, $m ) )
{
$statusCode = $m[1];
$statusMessage = trim( $line );
}
if ( preg_match( '@Content-Length: (\d+)@', $line, $m ) )
{
$expectedLength = $m[1];
}
if ( preg_match( '@Connection: close@', $line ) )
{
$reConnect = true;
}
}
// read http content
$size = 1;
$data = '';
while ( $size < $expectedLength )
{
$line = $this->getLine( $expectedLength );
$size += strlen( $line );
$data .= $line;
}
// reconnect if necessary
if ( $reConnect )
{
fclose( $this->connection );
$this->connect();
}
// check http status code
if ( $statusCode >= 400 )
{
// Something went wrong.
throw new ezcSearchNetworkException( "The HTTP server reported: $statusMessage", $data );
}
return $data;
}
/**
* Builds query parameters from the different query fields
*
* @param string $queryWord
* @param string $defaultField
* @param array(string=>string) $searchFieldList
* @param array(string=>string) $returnFieldList
* @param array(string=>string) $highlightFieldList
* @param array(string=>string) $facetFieldList
* @param int $limit
* @param int $offset
* @param array(string=>string) $order
* @return array
*/
private function buildQuery( $queryWord, $defaultField, $searchFieldList = array(), $returnFieldList = array(), $highlightFieldList = array(), $facetFieldList = array(), $limit = null, $offset = false, $order = array() )
{
if ( count( $searchFieldList ) > 0 )
{
$queryString = '';
foreach ( $searchFieldList as $searchField )
{
$queryString .= "$searchField:$queryWord ";
}
}
else
{
$queryString = $queryWord;
}
$queryFlags = array( 'q' => $queryString, 'wt' => 'json', 'df' => $defaultField );
$returnFieldList[] = 'score';
$queryFlags['fl'] = join( ' ', $returnFieldList );
if ( count( $highlightFieldList ) )
{
$queryFlags['hl'] = 'true';
$queryFlags['hl.snippets'] = 3;
$queryFlags['hl.fl'] = join( ' ', $highlightFieldList );
$queryFlags['hl.simple.pre'] = '<b>';
$queryFlags['hl.simple.post'] = '</b>';
}
if ( count( $facetFieldList ) )
{
$queryFlags['facet'] = 'true';
$queryFlags['facet.mincount'] = 1;
$queryFlags['facet.sort'] = 'false';
$queryFlags['facet.field'] = join( ' ', $facetFieldList );
}
if ( count( $order ) )
{
$sortFlags = array();
foreach ( $order as $column => $type )
{
if ( $type == ezcSearchQueryTools::ASC )
{
$sortFlags[] = "$column asc";
}
else
{
$sortFlags[] = "$column desc";
}
}
$queryFlags['sort'] = join( ', ', $sortFlags );
}
$queryFlags['start'] = $offset;
$queryFlags['rows'] = $limit === null ? 999999 : $limit;
return $queryFlags;
}
private function createDataForHit( $document, $def )
{
$className = $def->documentType;
$obj = new $className;
$attr = array();
foreach ( $def->fields as $field )
{
$fieldName = $this->mapFieldType( $field->field, $field->type );
if ( $field->inResult && isset( $document->$fieldName ) )
{
$attr[$field->field] = $this->mapFieldValuesForReturn( $field, $document->$fieldName );
}
}
$obj->setState( $attr );
return new ezcSearchResultDocument( $document->score, $obj );
}
/**
* Converts a raw solr result into a document using the definition $def
*
* @param ezcSearchDocumentDefinition $def
* @param mixed $response
* @return ezcSearchResult
*/
private function createResponseFromData( ezcSearchDocumentDefinition $def, $response )
{
if ( is_string( $response ) )
{
// try to find the error message and return that
$s = new ezcSearchResult();
$dom = new DomDocument();
@$dom->loadHtml( $response );
$tbody = $dom->getElementsByTagName( 'body' )->item( 0 );
$xpath = new DOMXPath($dom);
$tocElem = $xpath->evaluate( '//pre', $tbody )->item( 0 );
$error = $tocElem->nodeValue;
$s->error = $error;
return $s;
}
$s = new ezcSearchResult();
$s->status = $response->responseHeader->status;
$s->queryTime = $response->responseHeader->QTime;
$s->resultCount = $response->response->numFound;
$s->start = $response->response->start;
foreach ( $response->response->docs as $document )
{
$resultDocument = $this->createDataForHit( $document, $def );
$idProperty = $def->idProperty;
$s->documents[$resultDocument->document->$idProperty] = $resultDocument;
}
// process highlighting
if ( isset( $response->highlighting ) && count( $s->documents ) )
{
foreach ( $s->documents as $id => $document )
{
$document->highlight = array();
if ( isset( $response->highlighting->$id ) )
{
foreach ( $def->fields as $field )
{
$fieldName = $this->mapFieldType( $field->field, $field->type );
if ( $field->highlight && isset( $response->highlighting->$id->$fieldName ) )
{
$document->highlight[$field->field] = $response->highlighting->$id->$fieldName;
}
}
}
}
}
// process facets
if ( isset( $response->facet_counts ) && isset( $response->facet_counts->facet_fields ) )
{
$facets = $response->facet_counts->facet_fields;
foreach ( $def->fields as $field )
{
$fieldName = $this->mapFieldType( $field->field, $field->type );
if ( isset( $facets->$fieldName ) )
{
// sigh, stupid array format needs fixing
$facetValues = array();
$facet = $facets->$fieldName;
for ( $i = 0; $i < count( $facet ); $i += 2 )
{
$facetValues[$facet[$i]] = $facet[$i+1];
}
$s->facets[$field->field] = $facetValues;
}
}
}
return $s;
}
/**
* Executes a search by building and sending a query and returns the raw result
*
* @param string $queryWord
* @param string $defaultField
* @param array(string=>string) $searchFieldList
* @param array(string=>string) $returnFieldList
* @param array(string=>string) $highlightFieldList
* @param array(string=>string) $facetFieldList
* @param int $limit
* @param int $offset
* @param array(string=>string) $order
* @return stdClass
*/
public function search( $queryWord, $defaultField, $searchFieldList = array(), $returnFieldList = array(), $highlightFieldList = array(), $facetFieldList = array(), $limit = null, $offset = 0, $order = array() )
{
$result = $this->sendRawGetCommand( 'select', $this->buildQuery( $queryWord, $defaultField, $searchFieldList, $returnFieldList, $highlightFieldList, $facetFieldList, $limit, $offset, $order ) );
if ( ( $data = json_decode( $result ) ) === null )
{
throw new ezcSearchInvalidResultException( $result );
}
return $data;
}
/**
* Returns 'solr'.
*
* @return string
*/
static public function getName()
{
return 'solr';
}
/**
* Creates a search query object with the fields from the definition filled in.
*
* @param string $type
* @param ezcSearchDocumentDefinition $definition
* @return ezcSearchFindQuery
*/
public function createFindQuery( $type, ezcSearchDocumentDefinition $definition )
{
$query = new ezcSearchQuerySolr( $this, $definition );
$query->select( 'score' );
if ( $type )
{
$selectFieldNames = array();
foreach ( $definition->getSelectFieldNames() as $docProp )
{
$selectFieldNames[] = $this->mapFieldType( $docProp, $definition->fields[$docProp]->type );
}
$highlightFieldNames = array();
foreach ( $definition->getHighlightFieldNames() as $docProp )
{
$highlightFieldNames[] = $this->mapFieldType( $docProp, $definition->fields[$docProp]->type );
}
$query->select( $selectFieldNames );
$query->where( $query->eq( 'ezcsearch_type', $type ) );
$query->highlight( $highlightFieldNames );
}
return $query;
}
/**
* Builds the search query and returns the parsed response
*
* @param ezcSearchFindQuery $query
* @return ezcSearchResult
*/
public function find( ezcSearchFindQuery $query )
{
$queryWord = join( ' AND ', $query->whereClauses );
$resultFieldList = $query->resultFields;
$highlightFieldList = $query->highlightFields;
$facetFieldList = $query->facets;
$limit = $query->limit;
$offset = $query->offset;
$order = $query->orderByClauses;
$res = $this->search( $queryWord, '', array(), $resultFieldList, $highlightFieldList, $facetFieldList, $limit, $offset, $order );
return $this->createResponseFromData( $query->getDefinition(), $res );
}
/**
* Returns the query as a string for debugging purposes
*
* @param ezcSearchQuerySolr $query
* @return string
* @ignore
*/
public function getQuery( ezcSearchQuerySolr $query )
{
$queryWord = join( ' AND ', $query->whereClauses );
$resultFieldList = $query->resultFields;
$highlightFieldList = $query->highlightFields;
$facetFieldList = $query->facets;
$limit = $query->limit;
$offset = $query->offset;
$order = $query->orderByClauses;
return http_build_query( $this->buildQuery( $queryWord, '', array(), $resultFieldList, $highlightFieldList, $facetFieldList, $limit, $offset, $order ) );
}
/**
* Returns the query as a string for debugging purposes
*
* @param ezcSearchQuerySolr $query
* @return string
* @ignore
*/
public function getDeleteQuery( ezcSearchDeleteQuerySolr $query )
{
$queryWord = join( ' AND ', $query->whereClauses );
$query = "<delete><query>{$queryWord}</query></delete>";
return $query;
}
/**
* Returns the field name as used by solr created from the field $name and $type.
*
* @param string $name
* @param string $type
* @return string
*/
public function mapFieldType( $name, $type )
{
$map = array(
ezcSearchDocumentDefinition::STRING => '_s',
ezcSearchDocumentDefinition::TEXT => '_t',
ezcSearchDocumentDefinition::HTML => '_t',
ezcSearchDocumentDefinition::DATE => '_l',
ezcSearchDocumentDefinition::INT => '_l',
ezcSearchDocumentDefinition::FLOAT => '_d',
ezcSearchDocumentDefinition::BOOLEAN => '_b',
);
return $name . $map[$type];
}
/**
* This method prepares a $value before it is passed to the indexer.
*
* Depending on the $fieldType the $value is modified so that the indexer understands the value.
*
* @param string $fieldType
* @param mixed $value
* @return mixed
*/
public function mapFieldValueForIndex( $fieldType, $value )
{
switch ( $fieldType )
{
case ezcSearchDocumentDefinition::DATE:
if ( is_numeric( $value ) )
{
$d = new DateTime( "@$value" );
$value = $d->format( 'U' );
}
else
{
try
{
$d = new DateTime( $value );
}
catch ( Exception $e )
{
throw new ezcSearchInvalidValueException( $type, $value );
}
$value = $d->format( 'U' );
}
break;
case ezcSearchDocumentDefinition::BOOLEAN:
$value = $value ? 'true' : 'false';
break;
case ezcSearchDocumentDefinition::STRING:
case ezcSearchDocumentDefinition::TEXT:
case ezcSearchDocumentDefinition::HTML:
$value = preg_replace( '/[\x00-\x09\x0B\x0C\x1E\x1F]/', '', $value );
break;
}
return $value;
}
/**
* This method prepares a $value before it is passed to the search handler.
*
* Depending on the $fieldType the $value is modified so that the search
* handler understands the value.
*
* @param string $fieldType
* @param mixed $value
* @return mixed
*/
public function mapFieldValueForSearch( $fieldType, $value )
{
switch ( $fieldType )
{
case ezcSearchDocumentDefinition::STRING:
case ezcSearchDocumentDefinition::TEXT:
case ezcSearchDocumentDefinition::HTML:
$value = trim( $value );
if ( strpbrk( $value, ' ":' ) !== false )
{
$value = '"' . str_replace( '"', '\"', $value ) . '"';
}
break;
case ezcSearchDocumentDefinition::INT:
case ezcSearchDocumentDefinition::FLOAT:
$value = '"' . $value . '"';
break;
case ezcSearchDocumentDefinition::DATE:
if ( is_numeric( $value ) )
{
$d = new DateTime( "@$value" );
$value = $d->format( 'U' );
}
else
{
try
{
$d = new DateTime( $value );
}
catch ( Exception $e )
{
throw new ezcSearchInvalidValueException( $type, $value );
}
$value = $d->format( 'U' );
}
break;
case ezcSearchDocumentDefinition::BOOLEAN:
$value = ($value ? 'true' : 'false');
break;
}
return $value;
}
/**
* This method prepares a $value before it is passed to the search handler.
*
* Depending on the $fieldType the $value is modified so that the search
* handler understands the value.
*
* @param string $fieldType
* @param mixed $value
* @return mixed
*/
public function mapFieldValueForReturn( $fieldType, $value )
{
switch ( $fieldType )
{
case ezcSearchDocumentDefinition::DATE:
$value = new DateTime( "@$value" );
break;
}
return $value;
}
/**
* This method prepares a value or an array of $values before it is passed to the search handler.
*
* Depending on the $field the $values is modified so that the search
* handler understands the value. It will also correctly deal with
* multi-data fields in the search index.
*
* @throws ezcSearchInvalidValueException if an array of values is
* submitted, but the field has not been defined as a multi-value field.
*
* @param ezcSearchDocumentDefinitionField $field
* @param mixed $values
* @return array(mixed)
*/
public function mapFieldValuesForSearch( $field, $values )
{
if ( is_array( $values ) && $field->multi == false )
{
throw new ezcSearchInvalidValueException( $field->type, $values, 'multi' );
}
if ( !is_array( $values ) )
{
$values = array( $values );
}
foreach ( $values as &$value )
{
$value = $this->mapFieldValueForSearch( $field->type, $value );
}
return $values;
}
/**
* This method prepares a value or an array of $values before it is passed to the indexer.
*
* Depending on the $field the $values is modified so that the search
* handler understands the value. It will also correctly deal with
* multi-data fields in the search index.
*
* @throws ezcSearchInvalidValueException if an array of values is
* submitted, but the field has not been defined as a multi-value field.
*
* @param ezcSearchDocumentDefinitionField $field
* @param mixed $values
* @return array(mixed)
*/
public function mapFieldValuesForIndex( $field, $values )
{
if ( is_array( $values ) && $field->multi == false )
{
throw new ezcSearchInvalidValueException( $field->type, $values, 'multi' );
}
if ( !is_array( $values ) )
{
$values = array( $values );
}
foreach ( $values as &$value )
{
$value = $this->mapFieldValueForIndex( $field->type, $value );
}
return $values;
}
/**
* This method prepares a value or an array of $values after it has been returned by search handler.
*
* Depending on the $field the $values is modified. It will also correctly
* deal with multi-data fields in the search index.
*
* @param ezcSearchDocumentDefinitionField $field
* @param mixed $values
* @return mixed|array(mixed)
*/
public function mapFieldValuesForReturn( $field, $values )
{
if ( $field->multi )
{
foreach ( $values as &$value )
{
$value = $this->mapFieldValueForReturn( $field->type, $value );
}
}
else
{
$values = $this->mapFieldValueForReturn( $field->type, $values[0] );
}
return $values;
}
/**
* Runs a commit command to tell solr we're done indexing.
*/
protected function runCommit()
{
$r = $this->sendRawPostCommand( 'update', array( 'wt' => 'json' ), '<commit/>' );
}
/**
* Indexes the document $document using definition $definition
*
* @param ezcSearchDocumentDefinition $definition
* @param mixed $document
*/
public function index( ezcSearchDocumentDefinition $definition, $document )
{
$xml = new XmlWriter();
$xml->openMemory();
$xml->startElement( 'add' );
$xml->startElement( 'doc' );
$xml->startElement( 'field' );
$xml->writeAttribute( 'name', 'ezcsearch_type_s' );
$xml->text( $definition->documentType );
$xml->endElement();
$xml->startElement( 'field' );
$xml->writeAttribute( 'name', 'id' );
$xml->text( $document[$definition->idProperty] );
$xml->endElement();
foreach ( $definition->fields as $field )
{
$value = $this->mapFieldValuesForIndex( $field, $document[$field->field] );
foreach ( $value as $fieldValue )
{
$xml->startElement( 'field' );
$xml->writeAttribute( 'name', $this->mapFieldType( $field->field, $field->type ) );
$xml->text( $fieldValue );
$xml->endElement();
}
}
$xml->endElement();
$xml->endElement();
$doc = $xml->outputMemory( true );
$r = $this->sendRawPostCommand( 'update', array( 'wt' => 'json' ), $doc );
if ( $this->inTransaction == 0 )
{
$this->runCommit();
}
}
/**
* Creates a delete query object with the fields from the definition filled in.
*
* @param string $type
* @param ezcSearchDocumentDefinition $definition
* @return ezcSearchDeleteQuerySolr
*/
public function createDeleteQuery( $type, ezcSearchDocumentDefinition $definition )
{
$query = new ezcSearchDeleteQuerySolr( $this, $definition );
if ( $type )
{
$selectFieldNames = array();
$query->where( $query->eq( 'ezcsearch_type', $type ) );
}
return $query;
}
/**
* Deletes a document by the document's $id
*
* If the document with ID $id does not exist, no warning is returned.
*
* @param mixed $id
* @param ezcSearchDocumentDefinition $definition
*/
public function deleteById( $id, ezcSearchDocumentDefinition $definition )
{
$queryString = "<delete><id>{$id}</id></delete>";
$this->sendRawPostCommand( 'update', null, $queryString );
}
/**
* Deletes documents using the query $query.
*
* The $query should be created using {@link createDeleteQuery()}.
*
* @throws ezcSearchQueryException
* if the delete query failed.
*
* @param ezcSearchDeleteQuery $query
*/
public function delete( ezcSearchDeleteQuery $query )
{
$result = $this->sendRawPostCommand( 'update', null, $query->getQuery() );
$result = json_decode( $result );
}
/**
* Finds a document by the document's $id
*
* @throws ezcSearchIdNotFoundException
* if the document with ID $id did not exist.
*
* @param mixed $id
* @param ezcSearchDocumentDefinition $definition
* @return ezcSearchResult
*/
public function findById( $id, ezcSearchDocumentDefinition $definition )
{
$idProperty = $definition->idProperty;
$fieldName = $this->mapFieldType( $definition->fields[$idProperty]->field, $definition->fields[$idProperty]->type );
$res = $this->search( "{$fieldName}:$id", $fieldName )->response->docs;
if ( count( $res ) != 1 )
{
throw new ezcSearchIdNotFoundException( $id );
}
return $this->createDataForHit( $res[0], $definition );
}
}
?>