diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index ecb5388..1d66887 100644 --- a/extensions/sphinx/Query.php +++ b/extensions/sphinx/Query.php @@ -108,20 +108,76 @@ class Query extends Component * row data. For more details, see [[indexBy()]]. This property is only used by [[all()]]. */ public $indexBy; + /** + * @var callback PHP callback, which should be used to fetch source data for the snippets. + * Such callback will receive array of query result rows as an argument and must return the + * array of snippet source strings in the order, which match one of incoming rows. + * For example: + * ```php + * $query = new Query; + * $query->from('idx_item') + * ->match('pencil') + * ->snippetCallback(function ($rows) { + * $result = []; + * foreach ($rows as $row) { + * $result[] = file_get_contents('/path/to/index/files/' . $row['id'] . '.txt'); + * } + * return $result; + * }) + * ->all(); + * ``` + */ + public $snippetCallback; + /** + * @var array query options for the call snippet. + */ + public $snippetOptions; + /** + * @var Connection the Sphinx connection used to generate the SQL statements. + */ + private $_connection; + + /** + * @param Connection $connection Sphinx connection instance + * @return static the query object itself + */ + public function setConnection($connection) + { + $this->_connection = $connection; + return $this; + } + + /** + * @return Connection Sphinx connection instance + */ + public function getConnection() + { + if ($this->_connection === null) { + $this->_connection = $this->defaultConnection(); + } + return $this->_connection; + } + + /** + * @return Connection default connection value. + */ + protected function defaultConnection() + { + return Yii::$app->getComponent('sphinx'); + } /** * Creates a Sphinx command that can be used to execute this query. - * @param Connection $sphinxConnection the Sphinx connection used to generate the SQL statement. + * @param Connection $connection the Sphinx connection used to generate the SQL statement. * If this parameter is not given, the `sphinx` application component will be used. * @return Command the created Sphinx command instance. */ - public function createCommand($sphinxConnection = null) + public function createCommand($connection = null) { - if ($sphinxConnection === null) { - $sphinxConnection = Yii::$app->getComponent('sphinx'); - } - list ($sql, $params) = $sphinxConnection->getQueryBuilder()->build($this); - return $sphinxConnection->createCommand($sql, $params); + $this->setConnection($connection); + $connection = $this->getConnection(); + list ($sql, $params) = $connection->getQueryBuilder()->build($this); + return $connection->createCommand($sql, $params); } /** @@ -154,6 +210,7 @@ class Query extends Component public function all($db = null) { $rows = $this->createCommand($db)->queryAll(); + $rows = $this->fillUpSnippets($rows); if ($this->indexBy === null) { return $rows; } @@ -178,7 +235,11 @@ class Query extends Component */ public function one($db = null) { - return $this->createCommand($db)->queryOne(); + $result = $this->createCommand($db)->queryOne(); + if ($result) { + list ($result) = $this->fillUpSnippets([$result]); + } + return $result; } /** @@ -686,4 +747,58 @@ class Query extends Component } return $this; } + + /** + * @param callback $callback + * @return static the query object itself + */ + public function snippetCallback($callback) + { + $this->snippetCallback = $callback; + return $this; + } + + /** + * @param array $options + * @return static the query object itself + */ + public function snippetOptions($options) + { + $this->snippetOptions = $options; + return $this; + } + + /** + * Fills the query result rows with the snippets built from source determined by + * [[snippetCallback]] result. + * @param array $rows raw query result rows. + * @return array query result rows with filled up snippets. + */ + protected function fillUpSnippets($rows) + { + if ($this->snippetCallback === null) { + return $rows; + } + $snippetSources = call_user_func($this->snippetCallback, $rows); + $snippets = $this->callSnippets($snippetSources); + $snippetKey = 0; + foreach ($rows as $key => $row) { + $rows[$key]['snippet'] = $snippets[$snippetKey]; + $snippetKey++; + } + return $rows; + } + + /** + * Builds a snippets from provided source data. + * @param array $source the source data to extract a snippet from. + * @return array snippets list + */ + protected function callSnippets(array $source) + { + $connection = $this->getConnection(); + return $connection->createCommand() + ->callSnippets($this->from[0], $source, $this->match, $this->snippetOptions) + ->queryColumn(); + } } \ No newline at end of file diff --git a/tests/unit/extensions/sphinx/QueryTest.php b/tests/unit/extensions/sphinx/QueryTest.php index cc3e27a..f95e35d 100644 --- a/tests/unit/extensions/sphinx/QueryTest.php +++ b/tests/unit/extensions/sphinx/QueryTest.php @@ -151,4 +151,37 @@ class QueryTest extends SphinxTestCase ->all($connection); $this->assertNotEmpty($rows); } + + /** + * @depends testRun + */ + public function testSnippet() + { + $connection = $this->getConnection(); + + $match = 'about'; + $snippetPrefix = 'snippet#'; + $snippetCallback = function() use ($match, $snippetPrefix) { + return [ + $snippetPrefix . '1: ' . $match, + $snippetPrefix . '2: ' . $match, + ]; + }; + $snippetOptions = [ + 'before_match' => '[', + 'after_match' => ']', + ]; + + $query = new Query; + $rows = $query->from('yii2_test_article_index') + ->match($match) + ->snippetCallback($snippetCallback) + ->snippetOptions($snippetOptions) + ->all($connection); + $this->assertNotEmpty($rows); + foreach ($rows as $row) { + $this->assertContains($snippetPrefix, $row['snippet'], 'Snippet source not present!'); + $this->assertContains($snippetOptions['before_match'] . $match, $row['snippet'] . $snippetOptions['after_match'], 'Options not applied!'); + } + } } \ No newline at end of file