43 changed files with 4469 additions and 667 deletions
			
			
		@ -0,0 +1,199 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\db\ActiveQueryInterface; | 
				
			||||
use yii\db\ActiveQueryTrait; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * ActiveQuery represents a [[Query]] associated with an [[ActiveRecord]] class. | 
				
			||||
 * | 
				
			||||
 * ActiveQuery instances are usually created by [[ActiveRecord::find()]]. | 
				
			||||
 * | 
				
			||||
 * ActiveQuery mainly provides the following methods to retrieve the query results: | 
				
			||||
 * | 
				
			||||
 * - [[one()]]: returns a single record populated with the first row of data. | 
				
			||||
 * - [[all()]]: returns all records based on the query results. | 
				
			||||
 * - [[count()]]: returns the number of records. | 
				
			||||
 * - [[scalar()]]: returns the value of the first column in the first row of the query result. | 
				
			||||
 * - [[column()]]: returns the value of the first column in the query result. | 
				
			||||
 * - [[exists()]]: returns a value indicating whether the query result has data or not. | 
				
			||||
 * | 
				
			||||
 * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], | 
				
			||||
 * [[orderBy()]] to customize the query options. | 
				
			||||
 * | 
				
			||||
 * ActiveQuery also provides the following additional query options: | 
				
			||||
 * | 
				
			||||
 * - [[with()]]: list of relations that this query should be performed with. | 
				
			||||
 * - [[indexBy()]]: the name of the column by which the query result should be indexed. | 
				
			||||
 * - [[asArray()]]: whether to return each record as an array. | 
				
			||||
 * | 
				
			||||
 * These options can be configured using methods of the same name. For example: | 
				
			||||
 * | 
				
			||||
 * ~~~ | 
				
			||||
 * $customers = Customer::find()->with('orders')->asArray()->all(); | 
				
			||||
 * ~~~ | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class ActiveQuery extends Query implements ActiveQueryInterface | 
				
			||||
{ | 
				
			||||
	use ActiveQueryTrait; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Creates a DB command that can be used to execute this query. | 
				
			||||
	 * @param Connection $db the DB connection used to create the DB command. | 
				
			||||
	 * If null, the DB connection returned by [[modelClass]] will be used. | 
				
			||||
	 * @return Command the created DB command instance. | 
				
			||||
	 */ | 
				
			||||
	public function createCommand($db = null) | 
				
			||||
	{ | 
				
			||||
		/** @var ActiveRecord $modelClass */ | 
				
			||||
		$modelClass = $this->modelClass; | 
				
			||||
		if ($db === null) { | 
				
			||||
			$db = $modelClass::getDb(); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if ($this->type === null) { | 
				
			||||
			$this->type = $modelClass::type(); | 
				
			||||
		} | 
				
			||||
		if ($this->index === null) { | 
				
			||||
			$this->index = $modelClass::index(); | 
				
			||||
			$this->type = $modelClass::type(); | 
				
			||||
		} | 
				
			||||
		$commandConfig = $db->getQueryBuilder()->build($this); | 
				
			||||
		return $db->createCommand($commandConfig); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes query and returns all results as an array. | 
				
			||||
	 * @param Connection $db the DB connection used to create the DB command. | 
				
			||||
	 * If null, the DB connection returned by [[modelClass]] will be used. | 
				
			||||
	 * @return array the query results. If the query results in nothing, an empty array will be returned. | 
				
			||||
	 */ | 
				
			||||
	public function all($db = null) | 
				
			||||
	{ | 
				
			||||
		$result = $this->createCommand($db)->search(); | 
				
			||||
		if (empty($result['hits']['hits'])) { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
		if ($this->fields !== null) { | 
				
			||||
			foreach ($result['hits']['hits'] as &$row) { | 
				
			||||
				$row['_source'] = isset($row['fields']) ? $row['fields'] : []; | 
				
			||||
				unset($row['fields']); | 
				
			||||
			} | 
				
			||||
			unset($row); | 
				
			||||
		} | 
				
			||||
		if ($this->asArray && $this->indexBy) { | 
				
			||||
			foreach ($result['hits']['hits'] as &$row) { | 
				
			||||
				$row['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $row['_id']; | 
				
			||||
				$row = $row['_source']; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		$models = $this->createModels($result['hits']['hits']); | 
				
			||||
		if ($this->asArray && !$this->indexBy) { | 
				
			||||
			foreach($models as $key => $model) { | 
				
			||||
				$model['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; | 
				
			||||
				$models[$key] = $model['_source']; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		if (!empty($this->with)) { | 
				
			||||
			$this->findWith($this->with, $models); | 
				
			||||
		} | 
				
			||||
		return $models; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes query and returns a single row of result. | 
				
			||||
	 * @param Connection $db the DB connection used to create the DB command. | 
				
			||||
	 * If null, the DB connection returned by [[modelClass]] will be used. | 
				
			||||
	 * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], | 
				
			||||
	 * the query result may be either an array or an ActiveRecord object. Null will be returned | 
				
			||||
	 * if the query results in nothing. | 
				
			||||
	 */ | 
				
			||||
	public function one($db = null) | 
				
			||||
	{ | 
				
			||||
		if (($result = parent::one($db)) === false) { | 
				
			||||
			return null; | 
				
			||||
		} | 
				
			||||
		if ($this->asArray) { | 
				
			||||
			$model = $result['_source']; | 
				
			||||
			$model[ActiveRecord::PRIMARY_KEY_NAME] = $result['_id']; | 
				
			||||
		} else { | 
				
			||||
			/** @var ActiveRecord $class */ | 
				
			||||
			$class = $this->modelClass; | 
				
			||||
			$model = $class::create($result); | 
				
			||||
		} | 
				
			||||
		if (!empty($this->with)) { | 
				
			||||
			$models = [$model]; | 
				
			||||
			$this->findWith($this->with, $models); | 
				
			||||
			$model = $models[0]; | 
				
			||||
		} | 
				
			||||
		return $model; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDocs | 
				
			||||
	 */ | 
				
			||||
	public function search($db = null, $options = []) | 
				
			||||
	{ | 
				
			||||
		$result = $this->createCommand($db)->search($options); | 
				
			||||
		if (!empty($result['hits']['hits'])) { | 
				
			||||
			$models = $this->createModels($result['hits']['hits']); | 
				
			||||
			if ($this->asArray) { | 
				
			||||
				foreach($models as $key => $model) { | 
				
			||||
					$model['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; | 
				
			||||
					$models[$key] = $model['_source']; | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
			if (!empty($this->with)) { | 
				
			||||
				$this->findWith($this->with, $models); | 
				
			||||
			} | 
				
			||||
			$result['hits']['hits'] = $models; | 
				
			||||
		} | 
				
			||||
		return $result; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDocs | 
				
			||||
	 */ | 
				
			||||
	public function scalar($field, $db = null) | 
				
			||||
	{ | 
				
			||||
		$record = parent::one($db); | 
				
			||||
		if ($record !== false) { | 
				
			||||
			if ($field == ActiveRecord::PRIMARY_KEY_NAME) { | 
				
			||||
				return $record['_id']; | 
				
			||||
			} elseif (isset($record['_source'][$field])) { | 
				
			||||
				return $record['_source'][$field]; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return null; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDocs | 
				
			||||
	 */ | 
				
			||||
	public function column($field, $db = null) | 
				
			||||
	{ | 
				
			||||
		if ($field == ActiveRecord::PRIMARY_KEY_NAME) { | 
				
			||||
			$command = $this->createCommand($db); | 
				
			||||
			$command->queryParts['fields'] = []; | 
				
			||||
			$result = $command->search(); | 
				
			||||
			if (empty($result['hits']['hits'])) { | 
				
			||||
				return []; | 
				
			||||
			} | 
				
			||||
			$column = []; | 
				
			||||
			foreach ($result['hits']['hits'] as $row) { | 
				
			||||
				$column[] = $row['_id']; | 
				
			||||
			} | 
				
			||||
			return $column; | 
				
			||||
		} | 
				
			||||
		return parent::column($field, $db); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,474 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\base\InvalidCallException; | 
				
			||||
use yii\base\InvalidConfigException; | 
				
			||||
use yii\base\NotSupportedException; | 
				
			||||
use yii\helpers\Inflector; | 
				
			||||
use yii\helpers\Json; | 
				
			||||
use yii\helpers\StringHelper; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * ActiveRecord is the base class for classes representing relational data in terms of objects. | 
				
			||||
 * | 
				
			||||
 * This class implements the ActiveRecord pattern for the fulltext search and data storage | 
				
			||||
 * [elasticsearch](http://www.elasticsearch.org/). | 
				
			||||
 * | 
				
			||||
 * For defining a record a subclass should at least implement the [[attributes()]] method to define | 
				
			||||
 * attributes. | 
				
			||||
 * The primary key (the `_id` field in elasticsearch terms) is represented by `getId()` and `setId()`. | 
				
			||||
 * The primary key is not part of the attributes. | 
				
			||||
 * | 
				
			||||
 * The following is an example model called `Customer`: | 
				
			||||
 * | 
				
			||||
 * ```php | 
				
			||||
 * class Customer extends \yii\elasticsearch\ActiveRecord | 
				
			||||
 * { | 
				
			||||
 *     public function attributes() | 
				
			||||
 *     { | 
				
			||||
 *         return ['id', 'name', 'address', 'registration_date']; | 
				
			||||
 *     } | 
				
			||||
 * } | 
				
			||||
 * ``` | 
				
			||||
 * | 
				
			||||
 * You may override [[index()]] and [[type()]] to define the index and type this record represents. | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class ActiveRecord extends \yii\db\ActiveRecord | 
				
			||||
{ | 
				
			||||
	const PRIMARY_KEY_NAME = 'id'; | 
				
			||||
 | 
				
			||||
	private $_id; | 
				
			||||
	private $_version; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns the database connection used by this AR class. | 
				
			||||
	 * By default, the "elasticsearch" application component is used as the database connection. | 
				
			||||
	 * You may override this method if you want to use a different database connection. | 
				
			||||
	 * @return Connection the database connection used by this AR class. | 
				
			||||
	 */ | 
				
			||||
	public static function getDb() | 
				
			||||
	{ | 
				
			||||
		return \Yii::$app->getComponent('elasticsearch'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDoc | 
				
			||||
	 */ | 
				
			||||
	public static function find($q = null) | 
				
			||||
	{ | 
				
			||||
		$query = static::createQuery(); | 
				
			||||
		if (is_array($q)) { | 
				
			||||
			if (count($q) == 1 && (array_key_exists(ActiveRecord::PRIMARY_KEY_NAME, $q))) { | 
				
			||||
				$pk = $q[ActiveRecord::PRIMARY_KEY_NAME]; | 
				
			||||
				if (is_array($pk)) { | 
				
			||||
					return  static::mget($pk); | 
				
			||||
				} else { | 
				
			||||
					return static::get($pk); | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
			return $query->where($q)->one(); | 
				
			||||
		} elseif ($q !== null) { | 
				
			||||
			return static::get($q); | 
				
			||||
		} | 
				
			||||
		return $query; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Gets a record by its primary key. | 
				
			||||
	 * | 
				
			||||
	 * @param mixed $primaryKey the primaryKey value | 
				
			||||
	 * @param array $options options given in this parameter are passed to elasticsearch | 
				
			||||
	 * as request URI parameters. | 
				
			||||
	 * Please refer to the [elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html) | 
				
			||||
	 * for more details on these options. | 
				
			||||
	 * @return static|null The record instance or null if it was not found. | 
				
			||||
	 */ | 
				
			||||
	public static function get($primaryKey, $options = []) | 
				
			||||
	{ | 
				
			||||
		if ($primaryKey === null) { | 
				
			||||
			return null; | 
				
			||||
		} | 
				
			||||
		$command = static::getDb()->createCommand(); | 
				
			||||
		$result = $command->get(static::index(), static::type(), $primaryKey, $options); | 
				
			||||
		if ($result['exists']) { | 
				
			||||
			return static::create($result); | 
				
			||||
		} | 
				
			||||
		return null; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Gets a list of records by its primary keys. | 
				
			||||
	 * | 
				
			||||
	 * @param array $primaryKeys an array of primaryKey values | 
				
			||||
	 * @param array $options options given in this parameter are passed to elasticsearch | 
				
			||||
	 * as request URI parameters. | 
				
			||||
	 * | 
				
			||||
	 * Please refer to the [elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html) | 
				
			||||
	 * for more details on these options. | 
				
			||||
	 * @return static|null The record instance or null if it was not found. | 
				
			||||
	 */ | 
				
			||||
 | 
				
			||||
	public static function mget($primaryKeys, $options = []) | 
				
			||||
	{ | 
				
			||||
		if (empty($primaryKeys)) { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
		$command = static::getDb()->createCommand(); | 
				
			||||
		$result = $command->mget(static::index(), static::type(), $primaryKeys, $options); | 
				
			||||
		$models = []; | 
				
			||||
		foreach($result['docs'] as $doc) { | 
				
			||||
			if ($doc['exists']) { | 
				
			||||
				$models[] = static::create($doc); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return $models; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO add more like this feature http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-more-like-this.html | 
				
			||||
 | 
				
			||||
	// TODO add percolate functionality http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-percolate.html | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDoc | 
				
			||||
	 */ | 
				
			||||
	public static function createQuery() | 
				
			||||
	{ | 
				
			||||
		return new ActiveQuery(['modelClass' => get_called_class()]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDoc | 
				
			||||
	 */ | 
				
			||||
	public static function createActiveRelation($config = []) | 
				
			||||
	{ | 
				
			||||
		return new ActiveRelation($config); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO implement copy and move as pk change is not possible | 
				
			||||
 | 
				
			||||
	public function getId() | 
				
			||||
	{ | 
				
			||||
		return $this->_id; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Sets the primary key | 
				
			||||
	 * @param mixed $value | 
				
			||||
	 * @throws \yii\base\InvalidCallException when record is not new | 
				
			||||
	 */ | 
				
			||||
	public function setId($value) | 
				
			||||
	{ | 
				
			||||
		if ($this->isNewRecord) { | 
				
			||||
			$this->_id = $value; | 
				
			||||
		} else { | 
				
			||||
			throw new InvalidCallException('Changing the primaryKey of an already saved record is not allowed.'); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDoc | 
				
			||||
	 */ | 
				
			||||
	public function getPrimaryKey($asArray = false) | 
				
			||||
	{ | 
				
			||||
		if ($asArray) { | 
				
			||||
			return [ActiveRecord::PRIMARY_KEY_NAME => $this->_id]; | 
				
			||||
		} else { | 
				
			||||
			return $this->_id; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDoc | 
				
			||||
	 */ | 
				
			||||
	public function getOldPrimaryKey($asArray = false) | 
				
			||||
	{ | 
				
			||||
		$id = $this->isNewRecord ? null : $this->_id; | 
				
			||||
		if ($asArray) { | 
				
			||||
			return [ActiveRecord::PRIMARY_KEY_NAME => $id]; | 
				
			||||
		} else { | 
				
			||||
			return $this->_id; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method defines the primary. | 
				
			||||
	 * | 
				
			||||
	 * The primaryKey for elasticsearch documents is always `primaryKey`. It can not be changed. | 
				
			||||
	 * | 
				
			||||
	 * @return string[] the primary keys of this record. | 
				
			||||
	 */ | 
				
			||||
	public static function primaryKey() | 
				
			||||
	{ | 
				
			||||
		return [ActiveRecord::PRIMARY_KEY_NAME]; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns the list of all attribute names of the model. | 
				
			||||
	 * This method must be overridden by child classes to define available attributes. | 
				
			||||
	 * @return array list of attribute names. | 
				
			||||
	 */ | 
				
			||||
	public static function attributes() | 
				
			||||
	{ | 
				
			||||
		throw new InvalidConfigException('The attributes() method of elasticsearch ActiveRecord has to be implemented by child classes.'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @return string the name of the index this record is stored in. | 
				
			||||
	 */ | 
				
			||||
	public static function index() | 
				
			||||
	{ | 
				
			||||
		return Inflector::pluralize(Inflector::camel2id(StringHelper::basename(get_called_class()), '-')); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @return string the name of the type of this record. | 
				
			||||
	 */ | 
				
			||||
	public static function type() | 
				
			||||
	{ | 
				
			||||
		return Inflector::camel2id(StringHelper::basename(get_called_class()), '-'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Creates an active record object using a row of data. | 
				
			||||
	 * This method is called by [[ActiveQuery]] to populate the query results | 
				
			||||
	 * into Active Records. It is not meant to be used to create new records. | 
				
			||||
	 * @param array $row attribute values (name => value) | 
				
			||||
	 * @return ActiveRecord the newly created active record. | 
				
			||||
	 */ | 
				
			||||
	public static function create($row) | 
				
			||||
	{ | 
				
			||||
		$row['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $row['_id']; | 
				
			||||
		$record = parent::create($row['_source']); | 
				
			||||
		return $record; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Inserts a document into the associated index using the attribute values of this record. | 
				
			||||
	 * | 
				
			||||
	 * This method performs the following steps in order: | 
				
			||||
	 * | 
				
			||||
	 * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation | 
				
			||||
	 *    fails, it will skip the rest of the steps; | 
				
			||||
	 * 2. call [[afterValidate()]] when `$runValidation` is true. | 
				
			||||
	 * 3. call [[beforeSave()]]. If the method returns false, it will skip the | 
				
			||||
	 *    rest of the steps; | 
				
			||||
	 * 4. insert the record into database. If this fails, it will skip the rest of the steps; | 
				
			||||
	 * 5. call [[afterSave()]]; | 
				
			||||
	 * | 
				
			||||
	 * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], | 
				
			||||
	 * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]] | 
				
			||||
	 * will be raised by the corresponding methods. | 
				
			||||
	 * | 
				
			||||
	 * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database. | 
				
			||||
	 * | 
				
			||||
	 * If the [[primaryKey|primary key]] is not set (null) during insertion, | 
				
			||||
	 * it will be populated with a | 
				
			||||
	 * [randomly generated value](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html#_automatic_id_generation) | 
				
			||||
	 * after insertion. | 
				
			||||
	 * | 
				
			||||
	 * For example, to insert a customer record: | 
				
			||||
	 * | 
				
			||||
	 * ~~~ | 
				
			||||
	 * $customer = new Customer; | 
				
			||||
	 * $customer->name = $name; | 
				
			||||
	 * $customer->email = $email; | 
				
			||||
	 * $customer->insert(); | 
				
			||||
	 * ~~~ | 
				
			||||
	 * | 
				
			||||
	 * @param boolean $runValidation whether to perform validation before saving the record. | 
				
			||||
	 * If the validation fails, the record will not be inserted into the database. | 
				
			||||
	 * @param array $attributes list of attributes that need to be saved. Defaults to null, | 
				
			||||
	 * meaning all attributes will be saved. | 
				
			||||
	 * @param array $options options given in this parameter are passed to elasticsearch | 
				
			||||
	 * as request URI parameters. These are among others: | 
				
			||||
	 * | 
				
			||||
	 * - `routing` define shard placement of this record. | 
				
			||||
	 * - `parent` by giving the primaryKey of another record this defines a parent-child relation | 
				
			||||
	 * - `timestamp` specifies the timestamp to store along with the document. Default is indexing time. | 
				
			||||
	 * | 
				
			||||
	 * Please refer to the [elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html) | 
				
			||||
	 * for more details on these options. | 
				
			||||
	 * | 
				
			||||
	 * By default the `op_type` is set to `create`. | 
				
			||||
	 * @return boolean whether the attributes are valid and the record is inserted successfully. | 
				
			||||
	 */ | 
				
			||||
	public function insert($runValidation = true, $attributes = null, $options = ['op_type' => 'create']) | 
				
			||||
	{ | 
				
			||||
		if ($runValidation && !$this->validate($attributes)) { | 
				
			||||
			return false; | 
				
			||||
		} | 
				
			||||
		if ($this->beforeSave(true)) { | 
				
			||||
			$values = $this->getDirtyAttributes($attributes); | 
				
			||||
 | 
				
			||||
			$response = static::getDb()->createCommand()->insert( | 
				
			||||
				static::index(), | 
				
			||||
				static::type(), | 
				
			||||
				$values, | 
				
			||||
				$this->getPrimaryKey(), | 
				
			||||
				$options | 
				
			||||
			); | 
				
			||||
 | 
				
			||||
			if (!$response['ok']) { | 
				
			||||
				return false; | 
				
			||||
			} | 
				
			||||
			$this->_id = $response['_id']; | 
				
			||||
			$this->_version = $response['_version']; | 
				
			||||
			$this->setOldAttributes($values); | 
				
			||||
			$this->afterSave(true); | 
				
			||||
			return true; | 
				
			||||
		} | 
				
			||||
		return false; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Updates all records whos primary keys are given. | 
				
			||||
	 * For example, to change the status to be 1 for all customers whose status is 2: | 
				
			||||
	 * | 
				
			||||
	 * ~~~ | 
				
			||||
	 * Customer::updateAll(array('status' => 1), array(2, 3, 4)); | 
				
			||||
	 * ~~~ | 
				
			||||
	 * | 
				
			||||
	 * @param array $attributes attribute values (name-value pairs) to be saved into the table | 
				
			||||
	 * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. | 
				
			||||
	 * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. | 
				
			||||
	 * @param array $params this parameter is ignored in elasticsearch implementation. | 
				
			||||
	 * @return integer the number of rows updated | 
				
			||||
	 */ | 
				
			||||
	public static function updateAll($attributes, $condition = [], $params = []) | 
				
			||||
	{ | 
				
			||||
		if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { | 
				
			||||
			$primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; | 
				
			||||
		} else { | 
				
			||||
			$primaryKeys = static::find()->where($condition)->column(ActiveRecord::PRIMARY_KEY_NAME); | 
				
			||||
		} | 
				
			||||
		if (empty($primaryKeys)) { | 
				
			||||
			return 0; | 
				
			||||
		} | 
				
			||||
		$bulk = ''; | 
				
			||||
		foreach((array) $primaryKeys as $pk) { | 
				
			||||
			$action = Json::encode([ | 
				
			||||
				"update" => [ | 
				
			||||
					"_id" => $pk, | 
				
			||||
					"_type" => static::type(), | 
				
			||||
					"_index" => static::index(), | 
				
			||||
				], | 
				
			||||
			]); | 
				
			||||
			$data = Json::encode(array( | 
				
			||||
				"doc" => $attributes | 
				
			||||
			)); | 
				
			||||
			$bulk .= $action . "\n" . $data . "\n"; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		// TODO do this via command | 
				
			||||
		$url = [static::index(), static::type(), '_bulk']; | 
				
			||||
		$response = static::getDb()->post($url, [], $bulk); | 
				
			||||
		$n=0; | 
				
			||||
		foreach($response['items'] as $item) { | 
				
			||||
			if ($item['update']['ok']) { | 
				
			||||
				$n++; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return $n; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Deletes rows in the table using the provided conditions. | 
				
			||||
	 * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. | 
				
			||||
	 * | 
				
			||||
	 * For example, to delete all customers whose status is 3: | 
				
			||||
	 * | 
				
			||||
	 * ~~~ | 
				
			||||
	 * Customer::deleteAll('status = 3'); | 
				
			||||
	 * ~~~ | 
				
			||||
	 * | 
				
			||||
	 * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL. | 
				
			||||
	 * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. | 
				
			||||
	 * @param array $params this parameter is ignored in elasticsearch implementation. | 
				
			||||
	 * @return integer the number of rows deleted | 
				
			||||
	 */ | 
				
			||||
	public static function deleteAll($condition = [], $params = []) | 
				
			||||
	{ | 
				
			||||
		if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { | 
				
			||||
			$primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; | 
				
			||||
		} else { | 
				
			||||
			$primaryKeys = static::find()->where($condition)->column(ActiveRecord::PRIMARY_KEY_NAME); | 
				
			||||
		} | 
				
			||||
		if (empty($primaryKeys)) { | 
				
			||||
			return 0; | 
				
			||||
		} | 
				
			||||
		$bulk = ''; | 
				
			||||
		foreach((array) $primaryKeys as $pk) { | 
				
			||||
			$bulk .= Json::encode([ | 
				
			||||
				"delete" => [ | 
				
			||||
					"_id" => $pk, | 
				
			||||
					"_type" => static::type(), | 
				
			||||
					"_index" => static::index(), | 
				
			||||
				], | 
				
			||||
			]) . "\n"; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		// TODO do this via command | 
				
			||||
		$url = [static::index(), static::type(), '_bulk']; | 
				
			||||
		$response = static::getDb()->post($url, [], $bulk); | 
				
			||||
		$n=0; | 
				
			||||
		foreach($response['items'] as $item) { | 
				
			||||
			if ($item['delete']['found'] && $item['delete']['ok']) { | 
				
			||||
				$n++; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return $n; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritdoc | 
				
			||||
	 */ | 
				
			||||
	public static function updateAllCounters($counters, $condition = null, $params = []) | 
				
			||||
	{ | 
				
			||||
		throw new NotSupportedException('Update Counters is not supported by elasticsearch ActiveRecord.'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritdoc | 
				
			||||
	 */ | 
				
			||||
	public static function getTableSchema() | 
				
			||||
	{ | 
				
			||||
		throw new NotSupportedException('getTableSchema() is not supported by elasticsearch ActiveRecord.'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritDoc | 
				
			||||
	 */ | 
				
			||||
	public static function tableName() | 
				
			||||
	{ | 
				
			||||
		return static::index() . '/' . static::type(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @inheritdoc | 
				
			||||
	 */ | 
				
			||||
	public static function findBySql($sql, $params = []) | 
				
			||||
	{ | 
				
			||||
		throw new NotSupportedException('findBySql() is not supported by elasticsearch ActiveRecord.'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. | 
				
			||||
	 * This method will always return false as transactional operations are not supported by elasticsearch. | 
				
			||||
	 * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. | 
				
			||||
	 * @return boolean whether the specified operation is transactional in the current [[scenario]]. | 
				
			||||
	 */ | 
				
			||||
	public function isTransactional($operation) | 
				
			||||
	{ | 
				
			||||
		return false; | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,61 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\db\ActiveRelationInterface; | 
				
			||||
use yii\db\ActiveRelationTrait; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * ActiveRelation represents a relation between two Active Record classes. | 
				
			||||
 * | 
				
			||||
 * ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and | 
				
			||||
 * [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining | 
				
			||||
 * a getter method which calls one of the above methods and returns the created ActiveRelation object. | 
				
			||||
 * | 
				
			||||
 * A relation is specified by [[link]] which represents the association between columns | 
				
			||||
 * of different tables; and the multiplicity of the relation is indicated by [[multiple]]. | 
				
			||||
 * | 
				
			||||
 * If a relation involves a pivot table, it may be specified by [[via()]] method. | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface | 
				
			||||
{ | 
				
			||||
	use ActiveRelationTrait; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Creates a DB command that can be used to execute this query. | 
				
			||||
	 * @param Connection $db the DB connection used to create the DB command. | 
				
			||||
	 * If null, the DB connection returned by [[modelClass]] will be used. | 
				
			||||
	 * @return Command the created DB command instance. | 
				
			||||
	 */ | 
				
			||||
	public function createCommand($db = null) | 
				
			||||
	{ | 
				
			||||
		if ($this->primaryModel !== null) { | 
				
			||||
			// lazy loading | 
				
			||||
			if (is_array($this->via)) { | 
				
			||||
				// via relation | 
				
			||||
				/** @var ActiveRelation $viaQuery */ | 
				
			||||
				list($viaName, $viaQuery) = $this->via; | 
				
			||||
				if ($viaQuery->multiple) { | 
				
			||||
					$viaModels = $viaQuery->all(); | 
				
			||||
					$this->primaryModel->populateRelation($viaName, $viaModels); | 
				
			||||
				} else { | 
				
			||||
					$model = $viaQuery->one(); | 
				
			||||
					$this->primaryModel->populateRelation($viaName, $model); | 
				
			||||
					$viaModels = $model === null ? [] : [$model]; | 
				
			||||
				} | 
				
			||||
				$this->filterByModels($viaModels); | 
				
			||||
			} else { | 
				
			||||
				$this->filterByModels([$this->primaryModel]); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return parent::createCommand($db); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,403 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\base\Component; | 
				
			||||
use yii\helpers\Json; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * The Command class implements the API for accessing the elasticsearch REST API. | 
				
			||||
 * | 
				
			||||
 * Check the [elasticsearch guide](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/index.html) | 
				
			||||
 * for details on these commands. | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class Command extends Component | 
				
			||||
{ | 
				
			||||
	/** | 
				
			||||
	 * @var Connection | 
				
			||||
	 */ | 
				
			||||
	public $db; | 
				
			||||
	/** | 
				
			||||
	 * @var string|array the indexes to execute the query on. Defaults to null meaning all indexes | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-multi-index | 
				
			||||
	 */ | 
				
			||||
	public $index; | 
				
			||||
	/** | 
				
			||||
	 * @var string|array the types to execute the query on. Defaults to null meaning all types | 
				
			||||
	 */ | 
				
			||||
	public $type; | 
				
			||||
	/** | 
				
			||||
	 * @var array list of arrays or json strings that become parts of a query | 
				
			||||
	 */ | 
				
			||||
	public $queryParts; | 
				
			||||
 | 
				
			||||
	public $options = []; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param array $options | 
				
			||||
	 * @return mixed | 
				
			||||
	 */ | 
				
			||||
	public function search($options = []) | 
				
			||||
	{ | 
				
			||||
		$query = $this->queryParts; | 
				
			||||
		if (empty($query)) { | 
				
			||||
			$query = '{}'; | 
				
			||||
		} | 
				
			||||
		if (is_array($query)) { | 
				
			||||
			$query = Json::encode($query); | 
				
			||||
		} | 
				
			||||
		$url = [ | 
				
			||||
			$this->index !== null ? $this->index : '_all', | 
				
			||||
			$this->type !== null ? $this->type : '_all', | 
				
			||||
			'_search' | 
				
			||||
		]; | 
				
			||||
		return $this->db->get($url, array_merge($this->options, $options), $query); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Inserts a document into an index | 
				
			||||
	 * @param string $index | 
				
			||||
	 * @param string $type | 
				
			||||
	 * @param string|array $data json string or array of data to store | 
				
			||||
	 * @param null $id the documents id. If not specified Id will be automatically choosen | 
				
			||||
	 * @param array $options | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html | 
				
			||||
	 */ | 
				
			||||
	public function insert($index, $type, $data, $id = null, $options = []) | 
				
			||||
	{ | 
				
			||||
		$body = is_array($data) ? Json::encode($data) : $data; | 
				
			||||
 | 
				
			||||
		if ($id !== null) { | 
				
			||||
			return $this->db->put([$index, $type, $id], $options, $body); | 
				
			||||
		} else { | 
				
			||||
			return $this->db->post([$index, $type], $options, $body); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * gets a document from the index | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $id | 
				
			||||
	 * @param array $options | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html | 
				
			||||
	 */ | 
				
			||||
	public function get($index, $type, $id, $options = []) | 
				
			||||
	{ | 
				
			||||
		return $this->db->get([$index, $type, $id], $options, null, [200, 404]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * gets multiple documents from the index | 
				
			||||
	 * | 
				
			||||
	 * TODO allow specifying type and index + fields | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $ids | 
				
			||||
	 * @param array $options | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-multi-get.html | 
				
			||||
	 */ | 
				
			||||
	public function mget($index, $type, $ids, $options = []) | 
				
			||||
	{ | 
				
			||||
		$body = Json::encode(['ids' => array_values($ids)]); | 
				
			||||
		return $this->db->get([$index, $type, '_mget'], $options, $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * gets a documents _source from the index (>=v0.90.1) | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $id | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html#_source | 
				
			||||
	 */ | 
				
			||||
	public function getSource($index, $type, $id) | 
				
			||||
	{ | 
				
			||||
		return $this->db->get([$index, $type, $id]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * gets a document from the index | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $id | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html | 
				
			||||
	 */ | 
				
			||||
	public function exists($index, $type, $id) | 
				
			||||
	{ | 
				
			||||
		return $this->db->head([$index, $type, $id]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * deletes a document from the index | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $id | 
				
			||||
	 * @param array $options | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete.html | 
				
			||||
	 */ | 
				
			||||
	public function delete($index, $type, $id, $options = []) | 
				
			||||
	{ | 
				
			||||
		return $this->db->delete([$index, $type, $id], $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * updates a document | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $id | 
				
			||||
	 * @param array $options | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-update.html | 
				
			||||
	 */ | 
				
			||||
//	public function update($index, $type, $id, $data, $options = []) | 
				
			||||
//	{ | 
				
			||||
//		// TODO implement | 
				
			||||
////		return $this->db->delete([$index, $type, $id], $options); | 
				
			||||
//	} | 
				
			||||
 | 
				
			||||
	// TODO bulk http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-bulk.html | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * creates an index | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param array $configuration | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-create-index.html | 
				
			||||
	 */ | 
				
			||||
	public function createIndex($index, $configuration = null) | 
				
			||||
	{ | 
				
			||||
		$body = $configuration !== null ? Json::encode($configuration) : null; | 
				
			||||
		return $this->db->put([$index], $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * deletes an index | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-delete-index.html | 
				
			||||
	 */ | 
				
			||||
	public function deleteIndex($index) | 
				
			||||
	{ | 
				
			||||
		return $this->db->delete([$index]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * deletes all indexes | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-delete-index.html | 
				
			||||
	 */ | 
				
			||||
	public function deleteAllIndexes() | 
				
			||||
	{ | 
				
			||||
		return $this->db->delete(['_all']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * checks whether an index exists | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-exists.html | 
				
			||||
	 */ | 
				
			||||
	public function indexExists($index) | 
				
			||||
	{ | 
				
			||||
		return $this->db->head([$index]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-types-exists.html | 
				
			||||
	 */ | 
				
			||||
	public function typeExists($index, $type) | 
				
			||||
	{ | 
				
			||||
		return $this->db->head([$index, $type]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-aliases.html | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-update-settings.html | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-settings.html | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-warmers.html | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html | 
				
			||||
	 */ | 
				
			||||
	public function openIndex($index) | 
				
			||||
	{ | 
				
			||||
		return $this->db->post([$index, '_open']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-open-close.html | 
				
			||||
	 */ | 
				
			||||
	public function closeIndex($index) | 
				
			||||
	{ | 
				
			||||
		return $this->db->post([$index, '_close']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-status.html | 
				
			||||
	 */ | 
				
			||||
	public function getIndexStatus($index = '_all') | 
				
			||||
	{ | 
				
			||||
		return $this->db->get([$index, '_status']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-stats.html | 
				
			||||
	// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-segments.html | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-clearcache.html | 
				
			||||
	 */ | 
				
			||||
	public function clearIndexCache($index) | 
				
			||||
	{ | 
				
			||||
		return $this->db->post([$index, '_cache', 'clear']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-flush.html | 
				
			||||
	 */ | 
				
			||||
	public function flushIndex($index = '_all') | 
				
			||||
	{ | 
				
			||||
		return $this->db->post([$index, '_flush']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-refresh.html | 
				
			||||
	 */ | 
				
			||||
	public function refreshIndex($index) | 
				
			||||
	{ | 
				
			||||
		return $this->db->post([$index, '_refresh']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-optimize.html | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-gateway-snapshot.html | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @param $mapping | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-put-mapping.html | 
				
			||||
	 */ | 
				
			||||
	public function setMapping($index, $type, $mapping) | 
				
			||||
	{ | 
				
			||||
		$body = $mapping !== null ? Json::encode($mapping) : null; | 
				
			||||
		return $this->db->put([$index, $type, '_mapping'], $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param string $index | 
				
			||||
	 * @param string $type | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-mapping.html | 
				
			||||
	 */ | 
				
			||||
	public function getMapping($index = '_all', $type = '_all') | 
				
			||||
	{ | 
				
			||||
		return $this->db->get([$index, $type, '_mapping']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param $type | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-put-mapping.html | 
				
			||||
	 */ | 
				
			||||
	public function deleteMapping($index, $type) | 
				
			||||
	{ | 
				
			||||
		return $this->db->delete([$index, $type]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $index | 
				
			||||
	 * @param string $type | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html | 
				
			||||
	 */ | 
				
			||||
	public function getFieldMapping($index, $type = '_all') | 
				
			||||
	{ | 
				
			||||
		return $this->db->put([$index, $type, '_mapping']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $options | 
				
			||||
	 * @param $index | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-analyze.html | 
				
			||||
	 */ | 
				
			||||
//	public function analyze($options, $index = null) | 
				
			||||
//	{ | 
				
			||||
//		// TODO implement | 
				
			||||
////		return $this->db->put([$index]); | 
				
			||||
//	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $name | 
				
			||||
	 * @param $pattern | 
				
			||||
	 * @param $settings | 
				
			||||
	 * @param $mappings | 
				
			||||
	 * @param int $order | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html | 
				
			||||
	 */ | 
				
			||||
	public function createTemplate($name, $pattern, $settings, $mappings, $order = 0) | 
				
			||||
	{ | 
				
			||||
		$body = Json::encode([ | 
				
			||||
			'template' => $pattern, | 
				
			||||
			'order' => $order, | 
				
			||||
			'settings' => (object) $settings, | 
				
			||||
			'mappings' => (object) $mappings, | 
				
			||||
		]); | 
				
			||||
		return $this->db->put(['_template', $name], $body); | 
				
			||||
 | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $name | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html | 
				
			||||
	 */ | 
				
			||||
	public function deleteTemplate($name) | 
				
			||||
	{ | 
				
			||||
		return $this->db->delete(['_template', $name]); | 
				
			||||
 | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param $name | 
				
			||||
	 * @return mixed | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html | 
				
			||||
	 */ | 
				
			||||
	public function getTemplate($name) | 
				
			||||
	{ | 
				
			||||
		return $this->db->get(['_template', $name]); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,346 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use Yii; | 
				
			||||
use yii\base\Component; | 
				
			||||
use yii\base\InvalidConfigException; | 
				
			||||
use yii\helpers\Json; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * elasticsearch Connection is used to connect to an elasticsearch cluster version 0.20 or higher | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class Connection extends Component | 
				
			||||
{ | 
				
			||||
	/** | 
				
			||||
	 * @event Event an event that is triggered after a DB connection is established | 
				
			||||
	 */ | 
				
			||||
	const EVENT_AFTER_OPEN = 'afterOpen'; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @var bool whether to autodetect available cluster nodes on [[open()]] | 
				
			||||
	 */ | 
				
			||||
	public $autodetectCluster = true; | 
				
			||||
	/** | 
				
			||||
	 * @var array cluster nodes | 
				
			||||
	 * This is populated with the result of a cluster nodes request when [[autodetectCluster]] is true. | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/cluster-nodes-info.html#cluster-nodes-info | 
				
			||||
	 */ | 
				
			||||
	public $nodes = [ | 
				
			||||
		['http_address' => 'inet[/127.0.0.1:9200]'], | 
				
			||||
	]; | 
				
			||||
	/** | 
				
			||||
	 * @var array the active node. key of [[nodes]]. Will be randomly selected on [[open()]]. | 
				
			||||
	 */ | 
				
			||||
	public $activeNode; | 
				
			||||
 | 
				
			||||
	// TODO http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/_configuration.html#_example_configuring_http_basic_auth | 
				
			||||
	public $auth = []; | 
				
			||||
	/** | 
				
			||||
	 * @var float timeout to use for connecting to an elasticsearch node. | 
				
			||||
	 * This value will be used to configure the curl `CURLOPT_CONNECTTIMEOUT` option. | 
				
			||||
	 * If not set, no explicit timeout will be set for curl. | 
				
			||||
	 */ | 
				
			||||
	public $connectionTimeout = null; | 
				
			||||
	/** | 
				
			||||
	 * @var float timeout to use when reading the response from an elasticsearch node. | 
				
			||||
	 * This value will be used to configure the curl `CURLOPT_TIMEOUT` option. | 
				
			||||
	 * If not set, no explicit timeout will be set for curl. | 
				
			||||
	 */ | 
				
			||||
	public $dataTimeout = null; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
	public function init() | 
				
			||||
	{ | 
				
			||||
		foreach($this->nodes as $node) { | 
				
			||||
			if (!isset($node['http_address'])) { | 
				
			||||
				throw new InvalidConfigException('Elasticsearch node needs at least a http_address configured.'); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Closes the connection when this component is being serialized. | 
				
			||||
	 * @return array | 
				
			||||
	 */ | 
				
			||||
	public function __sleep() | 
				
			||||
	{ | 
				
			||||
		$this->close(); | 
				
			||||
		return array_keys(get_object_vars($this)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns a value indicating whether the DB connection is established. | 
				
			||||
	 * @return boolean whether the DB connection is established | 
				
			||||
	 */ | 
				
			||||
	public function getIsActive() | 
				
			||||
	{ | 
				
			||||
		return $this->activeNode !== null; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Establishes a DB connection. | 
				
			||||
	 * It does nothing if a DB connection has already been established. | 
				
			||||
	 * @throws Exception if connection fails | 
				
			||||
	 */ | 
				
			||||
	public function open() | 
				
			||||
	{ | 
				
			||||
		if ($this->activeNode !== null) { | 
				
			||||
			return; | 
				
			||||
		} | 
				
			||||
		if (empty($this->nodes)) { | 
				
			||||
			throw new InvalidConfigException('elasticsearch needs at least one node to operate.'); | 
				
			||||
		} | 
				
			||||
		if ($this->autodetectCluster) { | 
				
			||||
			$node = reset($this->nodes); | 
				
			||||
			$host = $node['http_address']; | 
				
			||||
			if (strncmp($host, 'inet[/', 6) == 0) { | 
				
			||||
				$host = substr($host, 6, -1); | 
				
			||||
			} | 
				
			||||
			$response = $this->httpRequest('GET', 'http://' . $host . '/_cluster/nodes'); | 
				
			||||
			$this->nodes = $response['nodes']; | 
				
			||||
			if (empty($this->nodes)) { | 
				
			||||
				throw new Exception('cluster autodetection did not find any active node.'); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		$this->selectActiveNode(); | 
				
			||||
		Yii::trace('Opening connection to elasticsearch. Nodes in cluster: ' . count($this->nodes) | 
				
			||||
			. ', active node: ' . $this->nodes[$this->activeNode]['http_address'], __CLASS__); | 
				
			||||
		$this->initConnection(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * select active node randomly | 
				
			||||
	 */ | 
				
			||||
	protected function selectActiveNode() | 
				
			||||
	{ | 
				
			||||
		$keys = array_keys($this->nodes); | 
				
			||||
		$this->activeNode = $keys[rand(0, count($keys) - 1)]; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Closes the currently active DB connection. | 
				
			||||
	 * It does nothing if the connection is already closed. | 
				
			||||
	 */ | 
				
			||||
	public function close() | 
				
			||||
	{ | 
				
			||||
		Yii::trace('Closing connection to elasticsearch. Active node was: ' | 
				
			||||
			. $this->nodes[$this->activeNode]['http_address'], __CLASS__); | 
				
			||||
		$this->activeNode = null; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Initializes the DB connection. | 
				
			||||
	 * This method is invoked right after the DB connection is established. | 
				
			||||
	 * The default implementation triggers an [[EVENT_AFTER_OPEN]] event. | 
				
			||||
	 */ | 
				
			||||
	protected function initConnection() | 
				
			||||
	{ | 
				
			||||
		$this->trigger(self::EVENT_AFTER_OPEN); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns the name of the DB driver for the current [[dsn]]. | 
				
			||||
	 * @return string name of the DB driver | 
				
			||||
	 */ | 
				
			||||
	public function getDriverName() | 
				
			||||
	{ | 
				
			||||
		return 'elasticsearch'; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Creates a command for execution. | 
				
			||||
	 * @param array $config the configuration for the Command class | 
				
			||||
	 * @return Command the DB command | 
				
			||||
	 */ | 
				
			||||
	public function createCommand($config = []) | 
				
			||||
	{ | 
				
			||||
		$this->open(); | 
				
			||||
		$config['db'] = $this; | 
				
			||||
		$command = new Command($config); | 
				
			||||
		return $command; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getQueryBuilder() | 
				
			||||
	{ | 
				
			||||
		return new QueryBuilder($this); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function get($url, $options = [], $body = null) | 
				
			||||
	{ | 
				
			||||
		$this->open(); | 
				
			||||
		return $this->httpRequest('GET', $this->createUrl($url, $options), $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function head($url, $options = [], $body = null) | 
				
			||||
	{ | 
				
			||||
		$this->open(); | 
				
			||||
		return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function post($url, $options = [], $body = null) | 
				
			||||
	{ | 
				
			||||
		$this->open(); | 
				
			||||
		return $this->httpRequest('POST', $this->createUrl($url, $options), $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function put($url, $options = [], $body = null) | 
				
			||||
	{ | 
				
			||||
		$this->open(); | 
				
			||||
		return $this->httpRequest('PUT', $this->createUrl($url, $options), $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function delete($url, $options = [], $body = null) | 
				
			||||
	{ | 
				
			||||
		$this->open(); | 
				
			||||
		return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private function createUrl($path, $options = []) | 
				
			||||
	{ | 
				
			||||
		$url = implode('/', array_map(function($a) { | 
				
			||||
			return urlencode(is_array($a) ? implode(',', $a) : $a); | 
				
			||||
		}, $path)); | 
				
			||||
 | 
				
			||||
		if (!empty($options)) { | 
				
			||||
			$url .= '?' . http_build_query($options); | 
				
			||||
		} | 
				
			||||
		return [$this->nodes[$this->activeNode]['http_address'], $url]; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	protected function httpRequest($method, $url, $requestBody = null) | 
				
			||||
	{ | 
				
			||||
		$method = strtoupper($method); | 
				
			||||
 | 
				
			||||
		// response body and headers | 
				
			||||
		$headers = []; | 
				
			||||
		$body = ''; | 
				
			||||
 | 
				
			||||
		$options = [ | 
				
			||||
			CURLOPT_USERAGENT      => 'Yii2 Framework ' . __CLASS__, | 
				
			||||
			CURLOPT_RETURNTRANSFER => false, | 
				
			||||
			CURLOPT_HEADER         => false, | 
				
			||||
			// http://www.php.net/manual/en/function.curl-setopt.php#82418 | 
				
			||||
			CURLOPT_HTTPHEADER     => ['Expect:'], | 
				
			||||
 | 
				
			||||
			CURLOPT_WRITEFUNCTION  => function($curl, $data) use (&$body) { | 
				
			||||
				$body .= $data; | 
				
			||||
				return mb_strlen($data, '8bit'); | 
				
			||||
			}, | 
				
			||||
			CURLOPT_HEADERFUNCTION => function($curl, $data) use (&$headers) { | 
				
			||||
				foreach(explode("\r\n", $data) as $row) { | 
				
			||||
					if (($pos = strpos($row, ':')) !== false) { | 
				
			||||
						$headers[strtolower(substr($row, 0, $pos))] = trim(substr($row, $pos + 1)); | 
				
			||||
					} | 
				
			||||
				} | 
				
			||||
				return mb_strlen($data, '8bit'); | 
				
			||||
			}, | 
				
			||||
			CURLOPT_CUSTOMREQUEST  => $method, | 
				
			||||
		]; | 
				
			||||
		if ($this->connectionTimeout !== null) { | 
				
			||||
			$options[CURLOPT_CONNECTTIMEOUT] = $this->connectionTimeout; | 
				
			||||
		} | 
				
			||||
		if ($this->dataTimeout !== null) { | 
				
			||||
			$options[CURLOPT_TIMEOUT] = $this->dataTimeout; | 
				
			||||
		} | 
				
			||||
		if ($requestBody !== null) { | 
				
			||||
			$options[CURLOPT_POSTFIELDS] = $requestBody; | 
				
			||||
		} | 
				
			||||
		if ($method == 'HEAD') { | 
				
			||||
			$options[CURLOPT_NOBODY] = true; | 
				
			||||
			unset($options[CURLOPT_WRITEFUNCTION]); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (is_array($url)) { | 
				
			||||
			list($host, $q) = $url; | 
				
			||||
			if (strncmp($host, 'inet[/', 6) == 0) { | 
				
			||||
				$host = substr($host, 6, -1); | 
				
			||||
			} | 
				
			||||
			$profile = $q . $requestBody; | 
				
			||||
			$url = 'http://' . $host . '/' . $q; | 
				
			||||
		} else { | 
				
			||||
			$profile = false; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		Yii::trace("Sending request to elasticsearch node: $url\n$requestBody", __METHOD__); | 
				
			||||
		if ($profile !== false) { | 
				
			||||
			Yii::beginProfile($profile, __METHOD__); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$curl = curl_init($url); | 
				
			||||
		curl_setopt_array($curl, $options); | 
				
			||||
		if (curl_exec($curl) === false) { | 
				
			||||
			throw new Exception('Elasticsearch request failed: ' . curl_errno($curl) . ' - ' . curl_error($curl), [ | 
				
			||||
				'requestMethod' => $method, | 
				
			||||
				'requestUrl' => $url, | 
				
			||||
				'requestBody' => $requestBody, | 
				
			||||
				'responseHeaders' => $headers, | 
				
			||||
				'responseBody' => $body, | 
				
			||||
			]); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); | 
				
			||||
		curl_close($curl); | 
				
			||||
 | 
				
			||||
		if ($profile !== false) { | 
				
			||||
			Yii::endProfile($profile, __METHOD__); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if ($responseCode >= 200 && $responseCode < 300) { | 
				
			||||
			if ($method == 'HEAD') { | 
				
			||||
				return true; | 
				
			||||
			} else { | 
				
			||||
				if (isset($headers['content-length']) && ($len = mb_strlen($body, '8bit')) < $headers['content-length']) { | 
				
			||||
					throw new Exception("Incomplete data received from elasticsearch: $len < {$headers['content-length']}", [ | 
				
			||||
						'requestMethod' => $method, | 
				
			||||
						'requestUrl' => $url, | 
				
			||||
						'requestBody' => $requestBody, | 
				
			||||
						'responseCode' => $responseCode, | 
				
			||||
						'responseHeaders' => $headers, | 
				
			||||
						'responseBody' => $body, | 
				
			||||
					]); | 
				
			||||
				} | 
				
			||||
				if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) { | 
				
			||||
					return Json::decode($body); | 
				
			||||
				} | 
				
			||||
				throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [ | 
				
			||||
					'requestMethod' => $method, | 
				
			||||
					'requestUrl' => $url, | 
				
			||||
					'requestBody' => $requestBody, | 
				
			||||
					'responseCode' => $responseCode, | 
				
			||||
					'responseHeaders' => $headers, | 
				
			||||
					'responseBody' => $body, | 
				
			||||
				]); | 
				
			||||
			} | 
				
			||||
		} elseif ($responseCode == 404) { | 
				
			||||
			return false; | 
				
			||||
		} else { | 
				
			||||
			throw new Exception("Elasticsearch request failed with code $responseCode.", [ | 
				
			||||
				'requestMethod' => $method, | 
				
			||||
				'requestUrl' => $url, | 
				
			||||
				'requestBody' => $requestBody, | 
				
			||||
				'responseCode' => $responseCode, | 
				
			||||
				'responseHeaders' => $headers, | 
				
			||||
				'responseBody' => $body, | 
				
			||||
			]); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getNodeInfo() | 
				
			||||
	{ | 
				
			||||
		return $this->get([]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getClusterState() | 
				
			||||
	{ | 
				
			||||
		return $this->get(['_cluster', 'state']); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,43 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Exception represents an exception that is caused by elasticsearch-related operations. | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class Exception extends \yii\db\Exception | 
				
			||||
{ | 
				
			||||
	/** | 
				
			||||
	 * @var array additional information about the http request that caused the error. | 
				
			||||
	 */ | 
				
			||||
	public $errorInfo = []; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Constructor. | 
				
			||||
	 * @param string $message error message | 
				
			||||
	 * @param array $errorInfo error info | 
				
			||||
	 * @param integer $code error code | 
				
			||||
	 * @param \Exception $previous The previous exception used for the exception chaining. | 
				
			||||
	 */ | 
				
			||||
	public function __construct($message, $errorInfo = [], $code = 0, \Exception $previous = null) | 
				
			||||
	{ | 
				
			||||
		$this->errorInfo = $errorInfo; | 
				
			||||
		parent::__construct($message, $code, $previous); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @return string the user-friendly name of this exception | 
				
			||||
	 */ | 
				
			||||
	public function getName() | 
				
			||||
	{ | 
				
			||||
		return \Yii::t('yii', 'Elasticsearch Database Exception'); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,32 @@
					 | 
				
			||||
The Yii framework is free software. It is released under the terms of | 
				
			||||
the following BSD License. | 
				
			||||
 | 
				
			||||
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) | 
				
			||||
All rights reserved. | 
				
			||||
 | 
				
			||||
Redistribution and use in source and binary forms, with or without | 
				
			||||
modification, are permitted provided that the following conditions | 
				
			||||
are met: | 
				
			||||
 | 
				
			||||
 * Redistributions of source code must retain the above copyright | 
				
			||||
   notice, this list of conditions and the following disclaimer. | 
				
			||||
 * Redistributions in binary form must reproduce the above copyright | 
				
			||||
   notice, this list of conditions and the following disclaimer in | 
				
			||||
   the documentation and/or other materials provided with the | 
				
			||||
   distribution. | 
				
			||||
 * Neither the name of Yii Software LLC nor the names of its | 
				
			||||
   contributors may be used to endorse or promote products derived | 
				
			||||
   from this software without specific prior written permission. | 
				
			||||
 | 
				
			||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
				
			||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
				
			||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | 
				
			||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | 
				
			||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | 
				
			||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | 
				
			||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 
				
			||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
				
			||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
				
			||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | 
				
			||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
				
			||||
POSSIBILITY OF SUCH DAMAGE. | 
				
			||||
@ -0,0 +1,506 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use Yii; | 
				
			||||
use yii\base\Component; | 
				
			||||
use yii\base\NotSupportedException; | 
				
			||||
use yii\db\QueryInterface; | 
				
			||||
use yii\db\QueryTrait; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Query represents a query to the search API of elasticsearch. | 
				
			||||
 * | 
				
			||||
 * Query provides a set of methods to facilitate the specification of different parameters of the query. | 
				
			||||
 * These methods can be chained together. | 
				
			||||
 * | 
				
			||||
 * By calling [[createCommand()]], we can get a [[Command]] instance which can be further | 
				
			||||
 * used to perform/execute the DB query against a database. | 
				
			||||
 * | 
				
			||||
 * For example, | 
				
			||||
 * | 
				
			||||
 * ~~~ | 
				
			||||
 * $query = new Query; | 
				
			||||
 * $query->fields('id, name') | 
				
			||||
 *     ->from('myindex', 'users') | 
				
			||||
 *     ->limit(10); | 
				
			||||
 * // build and execute the query | 
				
			||||
 * $command = $query->createCommand(); | 
				
			||||
 * $rows = $command->search(); // this way you get the raw output of elasticsearch. | 
				
			||||
 * ~~~ | 
				
			||||
 * | 
				
			||||
 * You would normally call `$query->search()` instead of creating a command as this method | 
				
			||||
 * adds the `indexBy()` feature and also removes some inconsistencies from the response. | 
				
			||||
 * | 
				
			||||
 * Query also provides some methods to easier get some parts of the result only: | 
				
			||||
 * | 
				
			||||
 * - [[one()]]: returns a single record populated with the first row of data. | 
				
			||||
 * - [[all()]]: returns all records based on the query results. | 
				
			||||
 * - [[count()]]: returns the number of records. | 
				
			||||
 * - [[scalar()]]: returns the value of the first column in the first row of the query result. | 
				
			||||
 * - [[column()]]: returns the value of the first column in the query result. | 
				
			||||
 * - [[exists()]]: returns a value indicating whether the query result has data or not. | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class Query extends Component implements QueryInterface | 
				
			||||
{ | 
				
			||||
	use QueryTrait; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @var array the fields being retrieved from the documents. For example, `['id', 'name']`. | 
				
			||||
	 * If not set, it means retrieving all fields. An empty array will result in no fields being | 
				
			||||
	 * retrieved. This means that only the primaryKey of a record will be available in the result. | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html#search-request-fields | 
				
			||||
	 * @see fields() | 
				
			||||
	 */ | 
				
			||||
	public $fields; | 
				
			||||
	/** | 
				
			||||
	 * @var string|array The index to retrieve data from. This can be a string representing a single index | 
				
			||||
	 * or a an array of multiple indexes. If this is not set, indexes are being queried. | 
				
			||||
	 * @see from() | 
				
			||||
	 */ | 
				
			||||
	public $index; | 
				
			||||
	/** | 
				
			||||
	 * @var string|array The type to retrieve data from. This can be a string representing a single type | 
				
			||||
	 * or a an array of multiple types. If this is not set, all types are being queried. | 
				
			||||
	 * @see from() | 
				
			||||
	 */ | 
				
			||||
	public $type; | 
				
			||||
	/** | 
				
			||||
	 * @var integer A search timeout, bounding the search request to be executed within the specified time value | 
				
			||||
	 * and bail with the hits accumulated up to that point when expired. Defaults to no timeout. | 
				
			||||
	 * @see timeout() | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 | 
				
			||||
	 */ | 
				
			||||
	public $timeout; | 
				
			||||
	/** | 
				
			||||
	 * @var array|string The query part of this search query. This is an array or json string that follows the format of | 
				
			||||
	 * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). | 
				
			||||
	 */ | 
				
			||||
	public $query; | 
				
			||||
	/** | 
				
			||||
	 * @var array|string The filter part of this search query. This is an array or json string that follows the format of | 
				
			||||
	 * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). | 
				
			||||
	 */ | 
				
			||||
	public $filter; | 
				
			||||
 | 
				
			||||
	public $facets = []; | 
				
			||||
 | 
				
			||||
	public function init() | 
				
			||||
	{ | 
				
			||||
		parent::init(); | 
				
			||||
		// setting the default limit according to elasticsearch defaults | 
				
			||||
		// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 | 
				
			||||
		if ($this->limit === null) { | 
				
			||||
			$this->limit = 10; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Creates a DB command that can be used to execute this query. | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return Command the created DB command instance. | 
				
			||||
	 */ | 
				
			||||
	public function createCommand($db = null) | 
				
			||||
	{ | 
				
			||||
		if ($db === null) { | 
				
			||||
			$db = Yii::$app->getComponent('elasticsearch'); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$commandConfig = $db->getQueryBuilder()->build($this); | 
				
			||||
		return $db->createCommand($commandConfig); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes the query and returns all results as an array. | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return array the query results. If the query results in nothing, an empty array will be returned. | 
				
			||||
	 */ | 
				
			||||
	public function all($db = null) | 
				
			||||
	{ | 
				
			||||
		$result = $this->createCommand($db)->search(); | 
				
			||||
		if (empty($result['hits']['hits'])) { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
		$rows = $result['hits']['hits']; | 
				
			||||
		if ($this->indexBy === null && $this->fields === null) { | 
				
			||||
			return $rows; | 
				
			||||
		} | 
				
			||||
		$models = []; | 
				
			||||
		foreach ($rows as $key => $row) { | 
				
			||||
			if ($this->fields !== null) { | 
				
			||||
				$row['_source'] = isset($row['fields']) ? $row['fields'] : []; | 
				
			||||
				unset($row['fields']); | 
				
			||||
			} | 
				
			||||
			if ($this->indexBy !== null) { | 
				
			||||
				if (is_string($this->indexBy)) { | 
				
			||||
					$key = $row['_source'][$this->indexBy]; | 
				
			||||
				} else { | 
				
			||||
					$key = call_user_func($this->indexBy, $row); | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
			$models[$key] = $row; | 
				
			||||
		} | 
				
			||||
		return $models; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes the query and returns a single row of result. | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query | 
				
			||||
	 * results in nothing. | 
				
			||||
	 */ | 
				
			||||
	public function one($db = null) | 
				
			||||
	{ | 
				
			||||
		$options['size'] = 1; | 
				
			||||
		$result = $this->createCommand($db)->search($options); | 
				
			||||
		if (empty($result['hits']['hits'])) { | 
				
			||||
			return false; | 
				
			||||
		} | 
				
			||||
		$record = reset($result['hits']['hits']); | 
				
			||||
		if ($this->fields !== null) { | 
				
			||||
			$record['_source'] = isset($record['fields']) ? $record['fields'] : []; | 
				
			||||
			unset($record['fields']); | 
				
			||||
		} | 
				
			||||
		return $record; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes the query and returns the complete search result including e.g. hits, facets, totalCount. | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @param array $options The options given with this query. Possible options are: | 
				
			||||
	 * - [routing](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-routing) | 
				
			||||
	 * - [search_type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html) | 
				
			||||
	 * @return array the query results. | 
				
			||||
	 */ | 
				
			||||
	public function search($db = null, $options = []) | 
				
			||||
	{ | 
				
			||||
		$result = $this->createCommand($db)->search($options); | 
				
			||||
		if (!empty($result['hits']['hits']) && ($this->indexBy === null || $this->fields === null)) { | 
				
			||||
			$rows = []; | 
				
			||||
			foreach ($result['hits']['hits'] as $key => $row) { | 
				
			||||
				if ($this->fields !== null) { | 
				
			||||
					$row['_source'] = isset($row['fields']) ? $row['fields'] : []; | 
				
			||||
					unset($row['fields']); | 
				
			||||
				} | 
				
			||||
				if ($this->indexBy !== null) { | 
				
			||||
					if (is_string($this->indexBy)) { | 
				
			||||
						$key = $row['_source'][$this->indexBy]; | 
				
			||||
					} else { | 
				
			||||
						$key = call_user_func($this->indexBy, $row); | 
				
			||||
					} | 
				
			||||
				} | 
				
			||||
				$rows[$key] = $row; | 
				
			||||
			} | 
				
			||||
			$result['hits']['hits'] = $rows; | 
				
			||||
		} | 
				
			||||
		return $result; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO add query stats http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#stats-groups | 
				
			||||
 | 
				
			||||
	// TODO add scroll/scan http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html#scan | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes the query and deletes all matching documents. | 
				
			||||
	 * | 
				
			||||
	 * This will not run facet queries. | 
				
			||||
	 * | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return array the query results. If the query results in nothing, an empty array will be returned. | 
				
			||||
	 */ | 
				
			||||
	public function delete($db = null) | 
				
			||||
	{ | 
				
			||||
		// TODO implement http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete-by-query.html | 
				
			||||
		throw new NotSupportedException('Delete by query is not implemented yet.'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns the query result as a scalar value. | 
				
			||||
	 * The value returned will be the specified field in the first document of the query results. | 
				
			||||
	 * @param string $field name of the attribute to select | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return string the value of the specified attribute in the first record of the query result. | 
				
			||||
	 * Null is returned if the query result is empty or the field does not exist. | 
				
			||||
	 */ | 
				
			||||
	public function scalar($field, $db = null) | 
				
			||||
	{ | 
				
			||||
		$record = self::one($db); // TODO limit fields to the one required | 
				
			||||
		if ($record !== false && isset($record['_source'][$field])) { | 
				
			||||
			return $record['_source'][$field]; | 
				
			||||
		} else { | 
				
			||||
			return null; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Executes the query and returns the first column of the result. | 
				
			||||
	 * @param string $field the field to query over | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return array the first column of the query result. An empty array is returned if the query results in nothing. | 
				
			||||
	 */ | 
				
			||||
	public function column($field, $db = null) | 
				
			||||
	{ | 
				
			||||
		$command = $this->createCommand($db); | 
				
			||||
		$command->queryParts['fields'] = [$field]; | 
				
			||||
		$result = $command->search(); | 
				
			||||
		if (empty($result['hits']['hits'])) { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
		$column = []; | 
				
			||||
		foreach ($result['hits']['hits'] as $row) { | 
				
			||||
			$column[] = isset($row['fields'][$field]) ? $row['fields'][$field] : null; | 
				
			||||
		} | 
				
			||||
		return $column; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns the number of records. | 
				
			||||
	 * @param string $q the COUNT expression. This parameter is ignored by this implementation. | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return integer number of records | 
				
			||||
	 */ | 
				
			||||
	public function count($q = '*', $db = null) | 
				
			||||
	{ | 
				
			||||
		// TODO consider sending to _count api instead of _search for performance | 
				
			||||
		// only when no facety are registerted. | 
				
			||||
		// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-count.html | 
				
			||||
 | 
				
			||||
		$options = []; | 
				
			||||
		$options['search_type'] = 'count'; | 
				
			||||
		$count = $this->createCommand($db)->search($options)['hits']['total']; | 
				
			||||
		if ($this->limit === null && $this->offset === null) { | 
				
			||||
			return $count; | 
				
			||||
		} elseif ($this->offset !== null) { | 
				
			||||
			$count = $this->offset < $count ? $count - $this->offset : 0; | 
				
			||||
		} | 
				
			||||
		return $this->limit === null ? $count : ($this->limit > $count ? $count : $this->limit); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Returns a value indicating whether the query result contains any row of data. | 
				
			||||
	 * @param Connection $db the database connection used to execute the query. | 
				
			||||
	 * If this parameter is not given, the `elasticsearch` application component will be used. | 
				
			||||
	 * @return boolean whether the query result contains any row of data. | 
				
			||||
	 */ | 
				
			||||
	public function exists($db = null) | 
				
			||||
	{ | 
				
			||||
		return self::one($db) !== false; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Adds a facet search to this query. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param string $type the facet type. e.g. `terms`, `range`, `histogram`... | 
				
			||||
	 * @param string|array $options the configuration options for this facet. Can be an array or a json string. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-query-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addFacet($name, $type, $options) | 
				
			||||
	{ | 
				
			||||
		$this->facets[$name] = [$type => $options]; | 
				
			||||
		return $this; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * The `terms facet` allow to specify field facets that return the N most frequent terms. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addTermFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'terms', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Range facet allows to specify a set of ranges and get both the number of docs (count) that fall | 
				
			||||
	 * within each range, and aggregated data either based on the field, or using another field. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-range-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addRangeFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'range', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * The histogram facet works with numeric data by building a histogram across intervals of the field values. | 
				
			||||
	 * Each value is "rounded" into an interval (or placed in a bucket), and statistics are provided per | 
				
			||||
	 * interval/bucket (count and total). | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-histogram-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addHistogramFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'histogram', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * A specific histogram facet that can work with date field types enhancing it over the regular histogram facet. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-date-histogram-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addDateHistogramFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'date_histogram', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * A filter facet (not to be confused with a facet filter) allows you to return a count of the hits matching the filter. | 
				
			||||
	 * The filter itself can be expressed using the Query DSL. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param string $filter the query in Query DSL | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-filter-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addFilterFacet($name, $filter) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'filter', $filter); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * A facet query allows to return a count of the hits matching the facet query. | 
				
			||||
	 * The query itself can be expressed using the Query DSL. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param string $query the query in Query DSL | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-query-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addQueryFacet($name, $query) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'query', $query); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Statistical facet allows to compute statistical data on a numeric fields. The statistical data include count, | 
				
			||||
	 * total, sum of squares, mean (average), minimum, maximum, variance, and standard deviation. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-statistical-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addStatisticalFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'statistical', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * The `terms_stats` facet combines both the terms and statistical allowing to compute stats computed on a field, | 
				
			||||
	 * per term value driven by another field. | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-stats-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addTermsStatsFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'terms_stats', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * The `geo_distance` facet is a facet providing information for ranges of distances from a provided `geo_point` | 
				
			||||
	 * including count of the number of hits that fall within each range, and aggregation information (like `total`). | 
				
			||||
	 * @param string $name the name of this facet | 
				
			||||
	 * @param array $options additional option. Please refer to the elasticsearch documentation for details. | 
				
			||||
	 * @return static | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-geo-distance-facet.html | 
				
			||||
	 */ | 
				
			||||
	public function addGeoDistanceFacet($name, $options) | 
				
			||||
	{ | 
				
			||||
		return $this->addFacet($name, 'geo_distance', $options); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO add suggesters http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html | 
				
			||||
 | 
				
			||||
	// TODO add validate query http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-validate.html | 
				
			||||
 | 
				
			||||
	// TODO support multi query via static method http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Sets the querypart of this search query. | 
				
			||||
	 * @param string $query | 
				
			||||
	 * @return static | 
				
			||||
	 */ | 
				
			||||
	public function query($query) | 
				
			||||
	{ | 
				
			||||
		$this->query = $query; | 
				
			||||
		return $this; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Sets the filter part of this search query. | 
				
			||||
	 * @param string $filter | 
				
			||||
	 * @return static | 
				
			||||
	 */ | 
				
			||||
	public function filter($filter) | 
				
			||||
	{ | 
				
			||||
		$this->filter = $filter; | 
				
			||||
		return $this; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Sets the index and type to retrieve documents from. | 
				
			||||
	 * @param string|array $index The index to retrieve data from. This can be a string representing a single index | 
				
			||||
	 * or a an array of multiple indexes. If this is `null` it means that all indexes are being queried. | 
				
			||||
	 * @param string|array $type The type to retrieve data from. This can be a string representing a single type | 
				
			||||
	 * or a an array of multiple types. If this is `null` it means that all types are being queried. | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-search.html#search-multi-index-type | 
				
			||||
	 */ | 
				
			||||
	public function from($index, $type = null) | 
				
			||||
	{ | 
				
			||||
		$this->index = $index; | 
				
			||||
		$this->type = $type; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Sets the fields to retrieve from the documents. | 
				
			||||
	 * @param array $fields the fields to be selected. | 
				
			||||
	 * @return static the query object itself | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html | 
				
			||||
	 */ | 
				
			||||
	public function fields($fields) | 
				
			||||
	{ | 
				
			||||
		if (is_array($fields) || $fields === null) { | 
				
			||||
			$this->fields = $fields; | 
				
			||||
		} else { | 
				
			||||
			$this->fields = func_get_args(); | 
				
			||||
		} | 
				
			||||
		return $this; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Sets the search timeout. | 
				
			||||
	 * @param integer $timeout A search timeout, bounding the search request to be executed within the specified time value | 
				
			||||
	 * and bail with the hits accumulated up to that point when expired. Defaults to no timeout. | 
				
			||||
	 * @return static the query object itself | 
				
			||||
	 * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 | 
				
			||||
	 */ | 
				
			||||
	public function timeout($timeout) | 
				
			||||
	{ | 
				
			||||
		$this->timeout = $timeout; | 
				
			||||
		return $this; | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,349 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yii\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\base\InvalidParamException; | 
				
			||||
use yii\base\NotSupportedException; | 
				
			||||
use yii\helpers\Json; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * QueryBuilder builds an elasticsearch query based on the specification given as a [[Query]] object. | 
				
			||||
 * | 
				
			||||
 * | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class QueryBuilder extends \yii\base\Object | 
				
			||||
{ | 
				
			||||
	/** | 
				
			||||
	 * @var Connection the database connection. | 
				
			||||
	 */ | 
				
			||||
	public $db; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Constructor. | 
				
			||||
	 * @param Connection $connection the database connection. | 
				
			||||
	 * @param array $config name-value pairs that will be used to initialize the object properties | 
				
			||||
	 */ | 
				
			||||
	public function __construct($connection, $config = []) | 
				
			||||
	{ | 
				
			||||
		$this->db = $connection; | 
				
			||||
		parent::__construct($config); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Generates query from a [[Query]] object. | 
				
			||||
	 * @param Query $query the [[Query]] object from which the query will be generated | 
				
			||||
	 * @return array the generated SQL statement (the first array element) and the corresponding | 
				
			||||
	 * parameters to be bound to the SQL statement (the second array element). | 
				
			||||
	 */ | 
				
			||||
	public function build($query) | 
				
			||||
	{ | 
				
			||||
		$parts = []; | 
				
			||||
 | 
				
			||||
		if ($query->fields !== null) { | 
				
			||||
			$parts['fields'] = (array) $query->fields; | 
				
			||||
		} | 
				
			||||
		if ($query->limit !== null && $query->limit >= 0) { | 
				
			||||
			$parts['size'] = $query->limit; | 
				
			||||
		} | 
				
			||||
		if ($query->offset > 0) { | 
				
			||||
			$parts['from'] = (int) $query->offset; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (empty($parts['query'])) { | 
				
			||||
			$parts['query'] = ["match_all" => (object)[]]; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$whereFilter = $this->buildCondition($query->where); | 
				
			||||
		if (is_string($query->filter)) { | 
				
			||||
			if (empty($whereFilter)) { | 
				
			||||
				$parts['filter'] = $query->filter; | 
				
			||||
			} else { | 
				
			||||
				$parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}'; | 
				
			||||
			} | 
				
			||||
		} elseif ($query->filter !== null) { | 
				
			||||
			if (empty($whereFilter)) { | 
				
			||||
				$parts['filter'] = $query->filter; | 
				
			||||
			} else { | 
				
			||||
				$parts['filter'] = ['and' => [$query->filter, $whereFilter]]; | 
				
			||||
			} | 
				
			||||
		} elseif (!empty($whereFilter)) { | 
				
			||||
			$parts['filter'] = $whereFilter; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$sort = $this->buildOrderBy($query->orderBy); | 
				
			||||
		if (!empty($sort)) { | 
				
			||||
			$parts['sort'] = $sort; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (!empty($query->facets)) { | 
				
			||||
			$parts['facets'] = $query->facets; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$options = []; | 
				
			||||
		if ($query->timeout !== null) { | 
				
			||||
			$options['timeout'] = $query->timeout; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		return [ | 
				
			||||
			'queryParts' => $parts, | 
				
			||||
			'index' => $query->index, | 
				
			||||
			'type' => $query->type, | 
				
			||||
			'options' => $options, | 
				
			||||
		]; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * adds order by condition to the query | 
				
			||||
	 */ | 
				
			||||
	public function buildOrderBy($columns) | 
				
			||||
	{ | 
				
			||||
		if (empty($columns)) { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
		$orders = []; | 
				
			||||
		foreach ($columns as $name => $direction) { | 
				
			||||
			if (is_string($direction)) { | 
				
			||||
				$column = $direction; | 
				
			||||
				$direction = SORT_ASC; | 
				
			||||
			} else { | 
				
			||||
				$column = $name; | 
				
			||||
			} | 
				
			||||
			if ($column == ActiveRecord::PRIMARY_KEY_NAME) { | 
				
			||||
				$column = '_id'; | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			// allow elasticsearch extended syntax as described in http://www.elasticsearch.org/guide/reference/api/search/sort/ | 
				
			||||
			if (is_array($direction)) { | 
				
			||||
				$orders[] = [$column => $direction]; | 
				
			||||
			} else { | 
				
			||||
				$orders[] = [$column => ($direction === SORT_DESC ? 'desc' : 'asc')]; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return $orders; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Parses the condition specification and generates the corresponding SQL expression. | 
				
			||||
	 * @param string|array $condition the condition specification. Please refer to [[Query::where()]] | 
				
			||||
	 * on how to specify a condition. | 
				
			||||
	 * @param array $params the binding parameters to be populated | 
				
			||||
	 * @return string the generated SQL expression | 
				
			||||
	 * @throws \yii\db\Exception if the condition is in bad format | 
				
			||||
	 */ | 
				
			||||
	public function buildCondition($condition) | 
				
			||||
	{ | 
				
			||||
		static $builders = array( | 
				
			||||
			'and' => 'buildAndCondition', | 
				
			||||
			'or' => 'buildAndCondition', | 
				
			||||
			'between' => 'buildBetweenCondition', | 
				
			||||
			'not between' => 'buildBetweenCondition', | 
				
			||||
			'in' => 'buildInCondition', | 
				
			||||
			'not in' => 'buildInCondition', | 
				
			||||
			'like' => 'buildLikeCondition', | 
				
			||||
			'not like' => 'buildLikeCondition', | 
				
			||||
			'or like' => 'buildLikeCondition', | 
				
			||||
			'or not like' => 'buildLikeCondition', | 
				
			||||
		); | 
				
			||||
 | 
				
			||||
		if (empty($condition)) { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
		if (!is_array($condition)) { | 
				
			||||
			throw new NotSupportedException('String conditions in where() are not supported by elasticsearch.'); | 
				
			||||
		} | 
				
			||||
		if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... | 
				
			||||
			$operator = strtolower($condition[0]); | 
				
			||||
			if (isset($builders[$operator])) { | 
				
			||||
				$method = $builders[$operator]; | 
				
			||||
				array_shift($condition); | 
				
			||||
				return $this->$method($operator, $condition); | 
				
			||||
			} else { | 
				
			||||
				throw new InvalidParamException('Found unknown operator in query: ' . $operator); | 
				
			||||
			} | 
				
			||||
		} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... | 
				
			||||
			return $this->buildHashCondition($condition); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private function buildHashCondition($condition) | 
				
			||||
	{ | 
				
			||||
		$parts = []; | 
				
			||||
		foreach($condition as $attribute => $value) { | 
				
			||||
			if ($attribute == ActiveRecord::PRIMARY_KEY_NAME) { | 
				
			||||
				if ($value == null) { // there is no null pk | 
				
			||||
					$parts[] = ['script' => ['script' => '0==1']]; | 
				
			||||
				} else { | 
				
			||||
					$parts[] = ['ids' => ['values' => is_array($value) ? $value : [$value]]]; | 
				
			||||
				} | 
				
			||||
			} else { | 
				
			||||
				if (is_array($value)) { // IN condition | 
				
			||||
					$parts[] = ['in' => [$attribute => $value]]; | 
				
			||||
				} else { | 
				
			||||
					if ($value === null) { | 
				
			||||
						$parts[] = ['missing' => ['field' => $attribute, 'existence' => true, 'null_value' => true]]; | 
				
			||||
					} else { | 
				
			||||
						$parts[] = ['term' => [$attribute => $value]]; | 
				
			||||
					} | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return count($parts) === 1 ? $parts[0] : ['and' => $parts]; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private function buildAndCondition($operator, $operands) | 
				
			||||
	{ | 
				
			||||
		$parts = []; | 
				
			||||
		foreach ($operands as $operand) { | 
				
			||||
			if (is_array($operand)) { | 
				
			||||
				$operand = $this->buildCondition($operand); | 
				
			||||
			} | 
				
			||||
			if (!empty($operand)) { | 
				
			||||
				$parts[] = $operand; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		if (!empty($parts)) { | 
				
			||||
			return [$operator => $parts]; | 
				
			||||
		} else { | 
				
			||||
			return []; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private function buildBetweenCondition($operator, $operands) | 
				
			||||
	{ | 
				
			||||
		if (!isset($operands[0], $operands[1], $operands[2])) { | 
				
			||||
			throw new InvalidParamException("Operator '$operator' requires three operands."); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		list($column, $value1, $value2) = $operands; | 
				
			||||
		if ($column == ActiveRecord::PRIMARY_KEY_NAME) { | 
				
			||||
			throw new NotSupportedException('Between condition is not supported for primaryKey.'); | 
				
			||||
		} | 
				
			||||
		$filter = ['range' => [$column => ['gte' => $value1, 'lte' => $value2]]]; | 
				
			||||
		if ($operator == 'not between') { | 
				
			||||
			$filter = ['not' => $filter]; | 
				
			||||
		} | 
				
			||||
		return $filter; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private function buildInCondition($operator, $operands) | 
				
			||||
	{ | 
				
			||||
		if (!isset($operands[0], $operands[1])) { | 
				
			||||
			throw new InvalidParamException("Operator '$operator' requires two operands."); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		list($column, $values) = $operands; | 
				
			||||
 | 
				
			||||
		$values = (array)$values; | 
				
			||||
 | 
				
			||||
		if (empty($values) || $column === []) { | 
				
			||||
			return $operator === 'in' ? ['script' => ['script' => '0==1']] : []; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (count($column) > 1) { | 
				
			||||
			return $this->buildCompositeInCondition($operator, $column, $values, $params); | 
				
			||||
		} elseif (is_array($column)) { | 
				
			||||
			$column = reset($column); | 
				
			||||
		} | 
				
			||||
		$canBeNull = false; | 
				
			||||
		foreach ($values as $i => $value) { | 
				
			||||
			if (is_array($value)) { | 
				
			||||
				$values[$i] = $value = isset($value[$column]) ? $value[$column] : null; | 
				
			||||
			} | 
				
			||||
			if ($value === null) { | 
				
			||||
				$canBeNull = true; | 
				
			||||
				unset($values[$i]); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		if ($column == ActiveRecord::PRIMARY_KEY_NAME) { | 
				
			||||
			if (empty($values) && $canBeNull) { // there is no null pk | 
				
			||||
				$filter = ['script' => ['script' => '0==1']]; | 
				
			||||
			} else { | 
				
			||||
				$filter = ['ids' => ['values' => array_values($values)]]; | 
				
			||||
				if ($canBeNull) { | 
				
			||||
					$filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]]; | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
		} else { | 
				
			||||
			if (empty($values) && $canBeNull) { | 
				
			||||
				$filter = ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]; | 
				
			||||
			} else { | 
				
			||||
				$filter = ['in' => [$column => array_values($values)]]; | 
				
			||||
				if ($canBeNull) { | 
				
			||||
					$filter = ['or' => [$filter, ['missing' => ['field' => $column, 'existence' => true, 'null_value' => true]]]]; | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		if ($operator == 'not in') { | 
				
			||||
			$filter = ['not' => $filter]; | 
				
			||||
		} | 
				
			||||
		return $filter; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	protected function buildCompositeInCondition($operator, $columns, $values) | 
				
			||||
	{ | 
				
			||||
		throw new NotSupportedException('composite in is not supported by elasticsearch.'); | 
				
			||||
		$vss = array(); | 
				
			||||
		foreach ($values as $value) { | 
				
			||||
			$vs = array(); | 
				
			||||
			foreach ($columns as $column) { | 
				
			||||
				if (isset($value[$column])) { | 
				
			||||
					$phName = self::PARAM_PREFIX . count($params); | 
				
			||||
					$params[$phName] = $value[$column]; | 
				
			||||
					$vs[] = $phName; | 
				
			||||
				} else { | 
				
			||||
					$vs[] = 'NULL'; | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
			$vss[] = '(' . implode(', ', $vs) . ')'; | 
				
			||||
		} | 
				
			||||
		foreach ($columns as $i => $column) { | 
				
			||||
			if (strpos($column, '(') === false) { | 
				
			||||
				$columns[$i] = $this->db->quoteColumnName($column); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private function buildLikeCondition($operator, $operands) | 
				
			||||
	{ | 
				
			||||
		throw new NotSupportedException('like conditions is not supported by elasticsearch.'); | 
				
			||||
		if (!isset($operands[0], $operands[1])) { | 
				
			||||
			throw new Exception("Operator '$operator' requires two operands."); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		list($column, $values) = $operands; | 
				
			||||
 | 
				
			||||
		$values = (array)$values; | 
				
			||||
 | 
				
			||||
		if (empty($values)) { | 
				
			||||
			return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0==1' : ''; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if ($operator === 'LIKE' || $operator === 'NOT LIKE') { | 
				
			||||
			$andor = ' AND '; | 
				
			||||
		} else { | 
				
			||||
			$andor = ' OR '; | 
				
			||||
			$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (strpos($column, '(') === false) { | 
				
			||||
			$column = $this->db->quoteColumnName($column); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$parts = array(); | 
				
			||||
		foreach ($values as $value) { | 
				
			||||
			$phName = self::PARAM_PREFIX . count($params); | 
				
			||||
			$params[$phName] = $value; | 
				
			||||
			$parts[] = "$column $operator $phName"; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		return implode($andor, $parts); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,127 @@
					 | 
				
			||||
Elasticsearch Query and ActiveRecord for Yii 2 | 
				
			||||
============================================== | 
				
			||||
 | 
				
			||||
This extension provides the [elasticsearch](http://www.elasticsearch.org/) integration for the Yii2 framework. | 
				
			||||
It includes basic querying/search support and also implements the `ActiveRecord` pattern that allows you to store active | 
				
			||||
records in elasticsearch. | 
				
			||||
 | 
				
			||||
To use this extension, you have to configure the Connection class in your application configuration: | 
				
			||||
 | 
				
			||||
```php | 
				
			||||
return [ | 
				
			||||
	//.... | 
				
			||||
	'components' => [ | 
				
			||||
        'elasticsearch' => [ | 
				
			||||
            'class' => 'yii\elasticsearch\Connection', | 
				
			||||
            'hosts' => [ | 
				
			||||
                ['http_address' => '127.0.0.1:9200'], | 
				
			||||
                // configure more hosts if you have a cluster | 
				
			||||
            ], | 
				
			||||
        ], | 
				
			||||
	] | 
				
			||||
]; | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Installation | 
				
			||||
------------ | 
				
			||||
 | 
				
			||||
The preferred way to install this extension is through [composer](http://getcomposer.org/download/). | 
				
			||||
 | 
				
			||||
Either run | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
php composer.phar require yiisoft/yii2-elasticsearch "*" | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
or add | 
				
			||||
 | 
				
			||||
```json | 
				
			||||
"yiisoft/yii2-elasticsearch": "*" | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
to the require section of your composer.json. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Using the Query | 
				
			||||
--------------- | 
				
			||||
 | 
				
			||||
TBD | 
				
			||||
 | 
				
			||||
Using the ActiveRecord | 
				
			||||
---------------------- | 
				
			||||
 | 
				
			||||
For general information on how to use yii's ActiveRecord please refer to the [guide](https://github.com/yiisoft/yii2/blob/master/docs/guide/active-record.md). | 
				
			||||
 | 
				
			||||
For defining an elasticsearch ActiveRecord class your record class needs to extend from `yii\elasticsearch\ActiveRecord` and | 
				
			||||
implement at least the `attributes()` method to define the attributes of the record. | 
				
			||||
The primary key (the `_id` field in elasticsearch terms) is represented by `getId()` and `setId()` and can not be changed. | 
				
			||||
The primary key is not part of the attributes. | 
				
			||||
 | 
				
			||||
 primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified. | 
				
			||||
The primaryKey needs to be part of the attributes so make sure you have an `id` attribute defined if you do | 
				
			||||
not specify your own primary key. | 
				
			||||
 | 
				
			||||
The following is an example model called `Customer`: | 
				
			||||
 | 
				
			||||
```php | 
				
			||||
class Customer extends \yii\elasticsearch\ActiveRecord | 
				
			||||
{ | 
				
			||||
    /** | 
				
			||||
     * @return array the list of attributes for this record | 
				
			||||
     */ | 
				
			||||
    public function attributes() | 
				
			||||
    { | 
				
			||||
        return ['id', 'name', 'address', 'registration_date']; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * @return ActiveRelation defines a relation to the Order record (can be in other database, e.g. redis or sql) | 
				
			||||
     */ | 
				
			||||
    public function getOrders() | 
				
			||||
    { | 
				
			||||
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('id'); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    /** | 
				
			||||
     * Defines a scope that modifies the `$query` to return only active(status = 1) customers | 
				
			||||
     */ | 
				
			||||
    public static function active($query) | 
				
			||||
    { | 
				
			||||
        $query->andWhere(array('status' => 1)); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
You may override [[index()]] and [[type()]] to define the index and type this record represents. | 
				
			||||
 | 
				
			||||
The general usage of elasticsearch ActiveRecord is very similar to the database ActiveRecord as described in the | 
				
			||||
[guide](https://github.com/yiisoft/yii2/blob/master/docs/guide/active-record.md). | 
				
			||||
It supports the same interface and features except the following limitations and additions(*!*): | 
				
			||||
 | 
				
			||||
- As elasticsearch does not support SQL, the query API does not support `join()`, `groupBy()`, `having()` and `union()`. | 
				
			||||
  Sorting, limit, offset and conditional where are all supported. | 
				
			||||
- `from()` does not select the tables, but the [index](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/glossary.html#glossary-index) | 
				
			||||
  and [type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/glossary.html#glossary-type) to query against. | 
				
			||||
- `select()` has been replaced with `fields()` which basically does the same but `fields` is more elasticsearch terminology. | 
				
			||||
  It defines the fields to retrieve from a document. | 
				
			||||
- `via`-relations can not be defined via a table as there are not tables in elasticsearch. You can only define relations via other records. | 
				
			||||
- As elasticsearch is a data storage and search engine there is of course support added for search your records. | 
				
			||||
  TBD ... | 
				
			||||
- It is also possible to define relations from elasticsearch ActiveRecords to normal ActiveRecord classes and vice versa. | 
				
			||||
 | 
				
			||||
Elasticsearch separates primary key from attributes. You need to set the `id` property of the record to set its primary key. | 
				
			||||
 | 
				
			||||
Usage example: | 
				
			||||
 | 
				
			||||
```php | 
				
			||||
$customer = new Customer(); | 
				
			||||
$customer->id = 1; | 
				
			||||
$customer->attributes = ['name' => 'test']; | 
				
			||||
$customer->save(); | 
				
			||||
 | 
				
			||||
$customer = Customer::get(1); // get a record by pk | 
				
			||||
$customers = Customer::get([1,2,3]); // get a records multiple by pk | 
				
			||||
$customer = Customer::find()->where(['name' => 'test'])->one(); // find by query | 
				
			||||
$customer = Customer::find()->active()->all(); // find all by query (using the `active` scope) | 
				
			||||
``` | 
				
			||||
@ -0,0 +1,28 @@
					 | 
				
			||||
{ | 
				
			||||
	"name": "yiisoft/yii2-elasticsearch", | 
				
			||||
	"description": "Elasticsearch integration and ActiveRecord for the Yii framework", | 
				
			||||
	"keywords": ["yii", "elasticsearch", "active-record", "search", "fulltext"], | 
				
			||||
	"type": "yii2-extension", | 
				
			||||
	"license": "BSD-3-Clause", | 
				
			||||
	"support": { | 
				
			||||
		"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aelasticsearch", | 
				
			||||
		"forum": "http://www.yiiframework.com/forum/", | 
				
			||||
		"wiki": "http://www.yiiframework.com/wiki/", | 
				
			||||
		"irc": "irc://irc.freenode.net/yii", | 
				
			||||
		"source": "https://github.com/yiisoft/yii2" | 
				
			||||
	}, | 
				
			||||
	"authors": [ | 
				
			||||
		{ | 
				
			||||
			"name": "Carsten Brandt", | 
				
			||||
			"email": "mail@cebe.cc" | 
				
			||||
		} | 
				
			||||
	], | 
				
			||||
	"require": { | 
				
			||||
		"yiisoft/yii2": "*", | 
				
			||||
		"ext-curl": "*" | 
				
			||||
	}, | 
				
			||||
	"autoload": { | 
				
			||||
		"psr-0": { "yii\\elasticsearch\\": "" } | 
				
			||||
	}, | 
				
			||||
	"target-dir": "yii/elasticsearch" | 
				
			||||
} | 
				
			||||
@ -0,0 +1,32 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 * @link http://www.yiiframework.com/ | 
				
			||||
 * @copyright Copyright (c) 2008 Yii Software LLC | 
				
			||||
 * @license http://www.yiiframework.com/license/ | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yiiunit\data\ar\elasticsearch; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * ActiveRecord is ... | 
				
			||||
 * | 
				
			||||
 * @author Qiang Xue <qiang.xue@gmail.com> | 
				
			||||
 * @since 2.0 | 
				
			||||
 */ | 
				
			||||
class ActiveRecord extends \yii\elasticsearch\ActiveRecord | 
				
			||||
{ | 
				
			||||
	public static $db; | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @return \yii\elasticsearch\Connection | 
				
			||||
	 */ | 
				
			||||
	public static function getDb() | 
				
			||||
	{ | 
				
			||||
		return self::$db; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public static function index() | 
				
			||||
	{ | 
				
			||||
		return 'yiitest'; | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,43 @@
					 | 
				
			||||
<?php | 
				
			||||
namespace yiiunit\data\ar\elasticsearch; | 
				
			||||
 | 
				
			||||
use yiiunit\extensions\elasticsearch\ActiveRecordTest; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Class Customer | 
				
			||||
 * | 
				
			||||
 * @property integer $id | 
				
			||||
 * @property string $name | 
				
			||||
 * @property string $email | 
				
			||||
 * @property string $address | 
				
			||||
 * @property integer $status | 
				
			||||
 */ | 
				
			||||
class Customer extends ActiveRecord | 
				
			||||
{ | 
				
			||||
	const STATUS_ACTIVE = 1; | 
				
			||||
	const STATUS_INACTIVE = 2; | 
				
			||||
 | 
				
			||||
	public $status2; | 
				
			||||
 | 
				
			||||
	public static function attributes() | 
				
			||||
	{ | 
				
			||||
		return ['name', 'email', 'address', 'status']; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getOrders() | 
				
			||||
	{ | 
				
			||||
		return $this->hasMany(Order::className(), array('customer_id' => ActiveRecord::PRIMARY_KEY_NAME))->orderBy('create_time'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public static function active($query) | 
				
			||||
	{ | 
				
			||||
		$query->andWhere(array('status' => 1)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function afterSave($insert) | 
				
			||||
	{ | 
				
			||||
		ActiveRecordTest::$afterSaveInsert = $insert; | 
				
			||||
		ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; | 
				
			||||
		parent::afterSave($insert); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,18 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\data\ar\elasticsearch; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Class Item | 
				
			||||
 * | 
				
			||||
 * @property integer $id | 
				
			||||
 * @property string $name | 
				
			||||
 * @property integer $category_id | 
				
			||||
 */ | 
				
			||||
class Item extends ActiveRecord | 
				
			||||
{ | 
				
			||||
	public static function attributes() | 
				
			||||
	{ | 
				
			||||
		return ['name', 'category_id']; | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,68 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\data\ar\elasticsearch; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Class Order | 
				
			||||
 * | 
				
			||||
 * @property integer $id | 
				
			||||
 * @property integer $customer_id | 
				
			||||
 * @property integer $create_time | 
				
			||||
 * @property string $total | 
				
			||||
 */ | 
				
			||||
class Order extends ActiveRecord | 
				
			||||
{ | 
				
			||||
	public static function attributes() | 
				
			||||
	{ | 
				
			||||
		return ['customer_id', 'create_time', 'total']; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getCustomer() | 
				
			||||
	{ | 
				
			||||
		return $this->hasOne(Customer::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'customer_id']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getOrderItems() | 
				
			||||
	{ | 
				
			||||
		return $this->hasMany(OrderItem::className(), ['order_id' => ActiveRecord::PRIMARY_KEY_NAME]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getItems() | 
				
			||||
	{ | 
				
			||||
		return $this->hasMany(Item::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'item_id']) | 
				
			||||
			->via('orderItems')->orderBy('id'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getItemsInOrder1() | 
				
			||||
	{ | 
				
			||||
		return $this->hasMany(Item::className(), ['id' => 'item_id']) | 
				
			||||
			->via('orderItems', function ($q) { | 
				
			||||
				$q->orderBy(['subtotal' => SORT_ASC]); | 
				
			||||
			})->orderBy('name'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getItemsInOrder2() | 
				
			||||
	{ | 
				
			||||
		return $this->hasMany(Item::className(), ['id' => 'item_id']) | 
				
			||||
			->via('orderItems', function ($q) { | 
				
			||||
				$q->orderBy(['subtotal' => SORT_DESC]); | 
				
			||||
			})->orderBy('name'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
//	public function getBooks() | 
				
			||||
//	{ | 
				
			||||
//		return $this->hasMany('Item', [ActiveRecord::PRIMARY_KEY_NAME => 'item_id']) | 
				
			||||
//			->viaTable('tbl_order_item', ['order_id' => ActiveRecord::PRIMARY_KEY_NAME]) | 
				
			||||
//			->where(['category_id' => 1]); | 
				
			||||
//	} | 
				
			||||
 | 
				
			||||
	public function beforeSave($insert) | 
				
			||||
	{ | 
				
			||||
		if (parent::beforeSave($insert)) { | 
				
			||||
//			$this->create_time = time(); | 
				
			||||
			return true; | 
				
			||||
		} else { | 
				
			||||
			return false; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,29 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\data\ar\elasticsearch; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Class OrderItem | 
				
			||||
 * | 
				
			||||
 * @property integer $order_id | 
				
			||||
 * @property integer $item_id | 
				
			||||
 * @property integer $quantity | 
				
			||||
 * @property string $subtotal | 
				
			||||
 */ | 
				
			||||
class OrderItem extends ActiveRecord | 
				
			||||
{ | 
				
			||||
	public static function attributes() | 
				
			||||
	{ | 
				
			||||
		return ['order_id', 'item_id', 'quantity', 'subtotal']; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getOrder() | 
				
			||||
	{ | 
				
			||||
		return $this->hasOne(Order::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'order_id']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function getItem() | 
				
			||||
	{ | 
				
			||||
		return $this->hasOne(Item::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'item_id']); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,495 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\extensions\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\elasticsearch\Connection; | 
				
			||||
use yii\helpers\Json; | 
				
			||||
use yiiunit\framework\ar\ActiveRecordTestTrait; | 
				
			||||
use yiiunit\data\ar\elasticsearch\ActiveRecord; | 
				
			||||
use yiiunit\data\ar\elasticsearch\Customer; | 
				
			||||
use yiiunit\data\ar\elasticsearch\OrderItem; | 
				
			||||
use yiiunit\data\ar\elasticsearch\Order; | 
				
			||||
use yiiunit\data\ar\elasticsearch\Item; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @group elasticsearch | 
				
			||||
 */ | 
				
			||||
class ActiveRecordTest extends ElasticSearchTestCase | 
				
			||||
{ | 
				
			||||
	use ActiveRecordTestTrait; | 
				
			||||
 | 
				
			||||
	public function callCustomerFind($q = null)	 { return Customer::find($q); } | 
				
			||||
	public function callOrderFind($q = null)     { return Order::find($q); } | 
				
			||||
	public function callOrderItemFind($q = null) { return OrderItem::find($q); } | 
				
			||||
	public function callItemFind($q = null)      { return Item::find($q); } | 
				
			||||
 | 
				
			||||
	public function getCustomerClass() { return Customer::className(); } | 
				
			||||
	public function getItemClass() { return Item::className(); } | 
				
			||||
	public function getOrderClass() { return Order::className(); } | 
				
			||||
	public function getOrderItemClass() { return OrderItem::className(); } | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * can be overridden to do things after save() | 
				
			||||
	 */ | 
				
			||||
	public function afterSave() | 
				
			||||
	{ | 
				
			||||
		$this->getConnection()->createCommand()->flushIndex('yiitest'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function setUp() | 
				
			||||
	{ | 
				
			||||
		parent::setUp(); | 
				
			||||
 | 
				
			||||
		/** @var Connection $db */ | 
				
			||||
		$db = ActiveRecord::$db = $this->getConnection(); | 
				
			||||
 | 
				
			||||
		// delete index | 
				
			||||
		if ($db->createCommand()->indexExists('yiitest')) { | 
				
			||||
			$db->createCommand()->deleteIndex('yiitest'); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		$db->post(['yiitest'], [], Json::encode([ | 
				
			||||
			'mappings' => [ | 
				
			||||
				"item" => [ | 
				
			||||
		            "_source" => [ "enabled" => true ], | 
				
			||||
		            "properties" => [ | 
				
			||||
						// allow proper sorting by name | 
				
			||||
		                "name" => ["type" => "string", "index" => "not_analyzed"], | 
				
			||||
		            ] | 
				
			||||
		        ] | 
				
			||||
			], | 
				
			||||
		])); | 
				
			||||
 | 
				
			||||
		$customer = new Customer(); | 
				
			||||
		$customer->id = 1; | 
				
			||||
		$customer->setAttributes(['email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1], false); | 
				
			||||
		$customer->save(false); | 
				
			||||
		$customer = new Customer(); | 
				
			||||
		$customer->id = 2; | 
				
			||||
		$customer->setAttributes(['email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1], false); | 
				
			||||
		$customer->save(false); | 
				
			||||
		$customer = new Customer(); | 
				
			||||
		$customer->id = 3; | 
				
			||||
		$customer->setAttributes(['email' => 'user3@example.com', 'name' => 'user3', 'address' => 'address3', 'status' => 2], false); | 
				
			||||
		$customer->save(false); | 
				
			||||
 | 
				
			||||
//		INSERT INTO tbl_category (name) VALUES ('Books'); | 
				
			||||
//		INSERT INTO tbl_category (name) VALUES ('Movies'); | 
				
			||||
 | 
				
			||||
		$item = new Item(); | 
				
			||||
		$item->id = 1; | 
				
			||||
		$item->setAttributes(['name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1], false); | 
				
			||||
		$item->save(false); | 
				
			||||
		$item = new Item(); | 
				
			||||
		$item->id = 2; | 
				
			||||
		$item->setAttributes(['name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1], false); | 
				
			||||
		$item->save(false); | 
				
			||||
		$item = new Item(); | 
				
			||||
		$item->id = 3; | 
				
			||||
		$item->setAttributes(['name' => 'Ice Age', 'category_id' => 2], false); | 
				
			||||
		$item->save(false); | 
				
			||||
		$item = new Item(); | 
				
			||||
		$item->id = 4; | 
				
			||||
		$item->setAttributes(['name' => 'Toy Story', 'category_id' => 2], false); | 
				
			||||
		$item->save(false); | 
				
			||||
		$item = new Item(); | 
				
			||||
		$item->id = 5; | 
				
			||||
		$item->setAttributes(['name' => 'Cars', 'category_id' => 2], false); | 
				
			||||
		$item->save(false); | 
				
			||||
 | 
				
			||||
		$order = new Order(); | 
				
			||||
		$order->id = 1; | 
				
			||||
		$order->setAttributes(['customer_id' => 1, 'create_time' => 1325282384, 'total' => 110.0], false); | 
				
			||||
		$order->save(false); | 
				
			||||
		$order = new Order(); | 
				
			||||
		$order->id = 2; | 
				
			||||
		$order->setAttributes(['customer_id' => 2, 'create_time' => 1325334482, 'total' => 33.0], false); | 
				
			||||
		$order->save(false); | 
				
			||||
		$order = new Order(); | 
				
			||||
		$order->id = 3; | 
				
			||||
		$order->setAttributes(['customer_id' => 2, 'create_time' => 1325502201, 'total' => 40.0], false); | 
				
			||||
		$order->save(false); | 
				
			||||
 | 
				
			||||
		$orderItem = new OrderItem(); | 
				
			||||
		$orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false); | 
				
			||||
		$orderItem->save(false); | 
				
			||||
		$orderItem = new OrderItem(); | 
				
			||||
		$orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false); | 
				
			||||
		$orderItem->save(false); | 
				
			||||
		$orderItem = new OrderItem(); | 
				
			||||
		$orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false); | 
				
			||||
		$orderItem->save(false); | 
				
			||||
		$orderItem = new OrderItem(); | 
				
			||||
		$orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false); | 
				
			||||
		$orderItem->save(false); | 
				
			||||
		$orderItem = new OrderItem(); | 
				
			||||
		$orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false); | 
				
			||||
		$orderItem->save(false); | 
				
			||||
		$orderItem = new OrderItem(); | 
				
			||||
		$orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false); | 
				
			||||
		$orderItem->save(false); | 
				
			||||
 | 
				
			||||
		$db->createCommand()->flushIndex('yiitest'); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testSearch() | 
				
			||||
	{ | 
				
			||||
		$customers = $this->callCustomerFind()->search()['hits']; | 
				
			||||
		$this->assertEquals(3, $customers['total']); | 
				
			||||
		$this->assertEquals(3, count($customers['hits'])); | 
				
			||||
		$this->assertTrue($customers['hits'][0] instanceof Customer); | 
				
			||||
		$this->assertTrue($customers['hits'][1] instanceof Customer); | 
				
			||||
		$this->assertTrue($customers['hits'][2] instanceof Customer); | 
				
			||||
 | 
				
			||||
		// limit vs. totalcount | 
				
			||||
		$customers = $this->callCustomerFind()->limit(2)->search()['hits']; | 
				
			||||
		$this->assertEquals(3, $customers['total']); | 
				
			||||
		$this->assertEquals(2, count($customers['hits'])); | 
				
			||||
 | 
				
			||||
		// asArray | 
				
			||||
		$result = $this->callCustomerFind()->asArray()->search()['hits']; | 
				
			||||
		$this->assertEquals(3, $result['total']); | 
				
			||||
		$customers = $result['hits']; | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('email', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('address', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('status', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('email', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('address', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('status', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('email', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('address', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('status', $customers[2]); | 
				
			||||
 | 
				
			||||
		// TODO test asArray() + fields() + indexBy() | 
				
			||||
 | 
				
			||||
		// find by attributes | 
				
			||||
		$result = $this->callCustomerFind()->where(['name' => 'user2'])->search()['hits']; | 
				
			||||
		$customer = reset($result['hits']); | 
				
			||||
		$this->assertTrue($customer instanceof Customer); | 
				
			||||
		$this->assertEquals(2, $customer->id); | 
				
			||||
 | 
				
			||||
		// TODO test query() and filter() | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testSearchFacets() | 
				
			||||
	{ | 
				
			||||
		$result = $this->callCustomerFind()->addStatisticalFacet('status_stats', ['field' => 'status'])->search(); | 
				
			||||
		$this->assertArrayHasKey('facets', $result); | 
				
			||||
		$this->assertEquals(3, $result['facets']['status_stats']['count']); | 
				
			||||
		$this->assertEquals(4, $result['facets']['status_stats']['total']); // sum of values | 
				
			||||
		$this->assertEquals(1, $result['facets']['status_stats']['min']); | 
				
			||||
		$this->assertEquals(2, $result['facets']['status_stats']['max']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testGetDb() | 
				
			||||
	{ | 
				
			||||
		$this->mockApplication(['components' => ['elasticsearch' => Connection::className()]]); | 
				
			||||
		$this->assertInstanceOf(Connection::className(), ActiveRecord::getDb()); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testGet() | 
				
			||||
	{ | 
				
			||||
		$this->assertInstanceOf(Customer::className(), Customer::get(1)); | 
				
			||||
		$this->assertNull(Customer::get(5)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testMget() | 
				
			||||
	{ | 
				
			||||
		$this->assertEquals([], Customer::mget([])); | 
				
			||||
 | 
				
			||||
		$records = Customer::mget([1]); | 
				
			||||
		$this->assertEquals(1, count($records)); | 
				
			||||
		$this->assertInstanceOf(Customer::className(), reset($records)); | 
				
			||||
 | 
				
			||||
		$records = Customer::mget([5]); | 
				
			||||
		$this->assertEquals(0, count($records)); | 
				
			||||
 | 
				
			||||
		$records = Customer::mget([1,3,5]); | 
				
			||||
		$this->assertEquals(2, count($records)); | 
				
			||||
		$this->assertInstanceOf(Customer::className(), $records[0]); | 
				
			||||
		$this->assertInstanceOf(Customer::className(), $records[1]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindLazy() | 
				
			||||
	{ | 
				
			||||
		/** @var $customer Customer */ | 
				
			||||
		$customer = Customer::find(2); | 
				
			||||
		$orders = $customer->orders; | 
				
			||||
		$this->assertEquals(2, count($orders)); | 
				
			||||
 | 
				
			||||
		$orders = $customer->getOrders()->where(['between', 'create_time', 1325334000, 1325400000])->all(); | 
				
			||||
		$this->assertEquals(1, count($orders)); | 
				
			||||
		$this->assertEquals(2, $orders[0]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindEagerViaRelation() | 
				
			||||
	{ | 
				
			||||
		// this test is currently failing randomly because of https://github.com/yiisoft/yii2/issues/1310 | 
				
			||||
		$orders = Order::find()->with('items')->orderBy('create_time')->all(); | 
				
			||||
		$this->assertEquals(3, count($orders)); | 
				
			||||
		$order = $orders[0]; | 
				
			||||
		$this->assertEquals(1, $order->id); | 
				
			||||
		$this->assertEquals(2, count($order->items)); | 
				
			||||
		$this->assertEquals(1, $order->items[0]->id); | 
				
			||||
		$this->assertEquals(2, $order->items[1]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testInsertNoPk() | 
				
			||||
	{ | 
				
			||||
		$this->assertEquals([ActiveRecord::PRIMARY_KEY_NAME], Customer::primaryKey()); | 
				
			||||
		$pkName = ActiveRecord::PRIMARY_KEY_NAME; | 
				
			||||
 | 
				
			||||
		$customer = new Customer; | 
				
			||||
		$customer->email = 'user4@example.com'; | 
				
			||||
		$customer->name = 'user4'; | 
				
			||||
		$customer->address = 'address4'; | 
				
			||||
 | 
				
			||||
		$this->assertNull($customer->primaryKey); | 
				
			||||
		$this->assertNull($customer->oldPrimaryKey); | 
				
			||||
		$this->assertNull($customer->$pkName); | 
				
			||||
		$this->assertTrue($customer->isNewRecord); | 
				
			||||
 | 
				
			||||
		$customer->save(); | 
				
			||||
 | 
				
			||||
		$this->assertNotNull($customer->primaryKey); | 
				
			||||
		$this->assertNotNull($customer->oldPrimaryKey); | 
				
			||||
		$this->assertNotNull($customer->$pkName); | 
				
			||||
		$this->assertEquals($customer->primaryKey, $customer->oldPrimaryKey); | 
				
			||||
		$this->assertEquals($customer->primaryKey, $customer->$pkName); | 
				
			||||
		$this->assertFalse($customer->isNewRecord); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testInsertPk() | 
				
			||||
	{ | 
				
			||||
		$pkName = ActiveRecord::PRIMARY_KEY_NAME; | 
				
			||||
 | 
				
			||||
		$customer = new Customer; | 
				
			||||
		$customer->$pkName = 5; | 
				
			||||
		$customer->email = 'user5@example.com'; | 
				
			||||
		$customer->name = 'user5'; | 
				
			||||
		$customer->address = 'address5'; | 
				
			||||
 | 
				
			||||
		$this->assertTrue($customer->isNewRecord); | 
				
			||||
 | 
				
			||||
		$customer->save(); | 
				
			||||
 | 
				
			||||
		$this->assertEquals(5, $customer->primaryKey); | 
				
			||||
		$this->assertEquals(5, $customer->oldPrimaryKey); | 
				
			||||
		$this->assertEquals(5, $customer->$pkName); | 
				
			||||
		$this->assertFalse($customer->isNewRecord); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testUpdatePk() | 
				
			||||
	{ | 
				
			||||
		$pkName = ActiveRecord::PRIMARY_KEY_NAME; | 
				
			||||
 | 
				
			||||
		$pk = [$pkName => 2]; | 
				
			||||
		$orderItem = Order::find($pk); | 
				
			||||
		$this->assertEquals(2, $orderItem->primaryKey); | 
				
			||||
		$this->assertEquals(2, $orderItem->oldPrimaryKey); | 
				
			||||
		$this->assertEquals(2, $orderItem->$pkName); | 
				
			||||
 | 
				
			||||
		$this->setExpectedException('yii\base\InvalidCallException'); | 
				
			||||
		$orderItem->$pkName = 13; | 
				
			||||
		$orderItem->save(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindLazyVia2() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		/** @var Order $order */ | 
				
			||||
		$orderClass = $this->getOrderClass(); | 
				
			||||
		$pkName = ActiveRecord::PRIMARY_KEY_NAME; | 
				
			||||
 | 
				
			||||
		$order = new $orderClass(); | 
				
			||||
		$order->$pkName = 100; | 
				
			||||
		$this->assertEquals([], $order->items); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testUpdateCounters() | 
				
			||||
	{ | 
				
			||||
		// Update Counters is not supported by elasticsearch | 
				
			||||
//		$this->setExpectedException('yii\base\NotSupportedException'); | 
				
			||||
//		ActiveRecordTestTrait::testUpdateCounters(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Some PDO implementations(e.g. cubrid) do not support boolean values. | 
				
			||||
	 * Make sure this does not affect AR layer. | 
				
			||||
	 */ | 
				
			||||
	public function testBooleanAttribute() | 
				
			||||
	{ | 
				
			||||
		$db = $this->getConnection(); | 
				
			||||
		$db->createCommand()->deleteIndex('yiitest'); | 
				
			||||
		$db->post(['yiitest'], [], Json::encode([ | 
				
			||||
			'mappings' => [ | 
				
			||||
				"customer" => [ | 
				
			||||
		            "_source" => [ "enabled" => true ], | 
				
			||||
		            "properties" => [ | 
				
			||||
						// this is for the boolean test | 
				
			||||
		                "status" => ["type" => "boolean"], | 
				
			||||
		            ] | 
				
			||||
		        ] | 
				
			||||
			], | 
				
			||||
		])); | 
				
			||||
 | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		$customer = new $customerClass(); | 
				
			||||
		$customer->name = 'boolean customer'; | 
				
			||||
		$customer->email = 'mail@example.com'; | 
				
			||||
		$customer->status = true; | 
				
			||||
		$customer->save(false); | 
				
			||||
 | 
				
			||||
		$customer->refresh(); | 
				
			||||
		$this->assertEquals(true, $customer->status); | 
				
			||||
 | 
				
			||||
		$customer->status = false; | 
				
			||||
		$customer->save(false); | 
				
			||||
 | 
				
			||||
		$customer->refresh(); | 
				
			||||
		$this->assertEquals(false, $customer->status); | 
				
			||||
 | 
				
			||||
		$customer = new Customer(); | 
				
			||||
		$customer->setAttributes(['email' => 'user2b@example.com', 'name' => 'user2b', 'status' => true], false); | 
				
			||||
		$customer->save(false); | 
				
			||||
		$customer = new Customer(); | 
				
			||||
		$customer->setAttributes(['email' => 'user3b@example.com', 'name' => 'user3b', 'status' => false], false); | 
				
			||||
		$customer->save(false); | 
				
			||||
		$this->afterSave(); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->where(['status' => true])->all(); | 
				
			||||
		$this->assertEquals(1, count($customers)); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->where(['status' => false])->all(); | 
				
			||||
		$this->assertEquals(2, count($customers)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testfindAsArrayFields() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// indexBy + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->asArray()->fields(['id', 'name'])->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[0]); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers[0]); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers[0]); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[1]); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers[1]); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers[1]); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[2]); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers[2]); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers[2]); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers[2]); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testfindIndexByFields() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// indexBy + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy('name')->fields('id', 'name')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers['user1'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['user2'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['user3'] instanceof $customerClass); | 
				
			||||
		$this->assertNotNull($customers['user1']->id); | 
				
			||||
		$this->assertNotNull($customers['user1']->name); | 
				
			||||
		$this->assertNull($customers['user1']->email); | 
				
			||||
		$this->assertNull($customers['user1']->address); | 
				
			||||
		$this->assertNull($customers['user1']->status); | 
				
			||||
		$this->assertNotNull($customers['user2']->id); | 
				
			||||
		$this->assertNotNull($customers['user2']->name); | 
				
			||||
		$this->assertNull($customers['user2']->email); | 
				
			||||
		$this->assertNull($customers['user2']->address); | 
				
			||||
		$this->assertNull($customers['user2']->status); | 
				
			||||
		$this->assertNotNull($customers['user3']->id); | 
				
			||||
		$this->assertNotNull($customers['user3']->name); | 
				
			||||
		$this->assertNull($customers['user3']->email); | 
				
			||||
		$this->assertNull($customers['user3']->address); | 
				
			||||
		$this->assertNull($customers['user3']->status); | 
				
			||||
 | 
				
			||||
		// indexBy callable + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy(function ($customer) { | 
				
			||||
			return $customer->id . '-' . $customer->name; | 
				
			||||
		})->fields('id', 'name')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers['1-user1'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['2-user2'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['3-user3'] instanceof $customerClass); | 
				
			||||
		$this->assertNotNull($customers['1-user1']->id); | 
				
			||||
		$this->assertNotNull($customers['1-user1']->name); | 
				
			||||
		$this->assertNull($customers['1-user1']->email); | 
				
			||||
		$this->assertNull($customers['1-user1']->address); | 
				
			||||
		$this->assertNull($customers['1-user1']->status); | 
				
			||||
		$this->assertNotNull($customers['2-user2']->id); | 
				
			||||
		$this->assertNotNull($customers['2-user2']->name); | 
				
			||||
		$this->assertNull($customers['2-user2']->email); | 
				
			||||
		$this->assertNull($customers['2-user2']->address); | 
				
			||||
		$this->assertNull($customers['2-user2']->status); | 
				
			||||
		$this->assertNotNull($customers['3-user3']->id); | 
				
			||||
		$this->assertNotNull($customers['3-user3']->name); | 
				
			||||
		$this->assertNull($customers['3-user3']->email); | 
				
			||||
		$this->assertNull($customers['3-user3']->address); | 
				
			||||
		$this->assertNull($customers['3-user3']->status); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testfindIndexByAsArrayFields() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// indexBy + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy('name')->asArray()->fields('id', 'name')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['user1']); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers['user1']); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers['user1']); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['user2']); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers['user2']); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers['user2']); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['user3']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['user3']); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers['user3']); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers['user3']); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers['user3']); | 
				
			||||
 | 
				
			||||
		// indexBy callable + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy(function ($customer) { | 
				
			||||
			return $customer['id'] . '-' . $customer['name']; | 
				
			||||
		})->asArray()->fields('id', 'name')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['1-user1']); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers['1-user1']); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers['1-user1']); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['2-user2']); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers['2-user2']); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers['2-user2']); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['3-user3']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['3-user3']); | 
				
			||||
		$this->assertArrayNotHasKey('email', $customers['3-user3']); | 
				
			||||
		$this->assertArrayNotHasKey('address', $customers['3-user3']); | 
				
			||||
		$this->assertArrayNotHasKey('status', $customers['3-user3']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,28 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\extensions\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\elasticsearch\Connection; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @group elasticsearch | 
				
			||||
 */ | 
				
			||||
class ElasticSearchConnectionTest extends ElasticSearchTestCase | 
				
			||||
{ | 
				
			||||
	public function testOpen() | 
				
			||||
	{ | 
				
			||||
		$connection = new Connection(); | 
				
			||||
		$connection->autodetectCluster; | 
				
			||||
		$connection->nodes = [ | 
				
			||||
			['http_address' => 'inet[/127.0.0.1:9200]'], | 
				
			||||
		]; | 
				
			||||
		$this->assertNull($connection->activeNode); | 
				
			||||
		$connection->open(); | 
				
			||||
		$this->assertNotNull($connection->activeNode); | 
				
			||||
		$this->assertArrayHasKey('name', reset($connection->nodes)); | 
				
			||||
		$this->assertArrayHasKey('hostname', reset($connection->nodes)); | 
				
			||||
		$this->assertArrayHasKey('version', reset($connection->nodes)); | 
				
			||||
		$this->assertArrayHasKey('http_address', reset($connection->nodes)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,51 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\extensions\elasticsearch; | 
				
			||||
 | 
				
			||||
use Yii; | 
				
			||||
use yii\elasticsearch\Connection; | 
				
			||||
use yiiunit\TestCase; | 
				
			||||
 | 
				
			||||
Yii::setAlias('@yii/elasticsearch', __DIR__ . '/../../../../extensions/elasticsearch'); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * ElasticSearchTestCase is the base class for all elasticsearch related test cases | 
				
			||||
 */ | 
				
			||||
class ElasticSearchTestCase extends TestCase | 
				
			||||
{ | 
				
			||||
	protected function setUp() | 
				
			||||
	{ | 
				
			||||
		$this->mockApplication(); | 
				
			||||
 | 
				
			||||
		$databases = $this->getParam('databases'); | 
				
			||||
		$params = isset($databases['elasticsearch']) ? $databases['elasticsearch'] : null; | 
				
			||||
		if ($params === null || !isset($params['dsn'])) { | 
				
			||||
			$this->markTestSkipped('No elasticsearch server connection configured.'); | 
				
			||||
		} | 
				
			||||
		$dsn = explode('/', $params['dsn']); | 
				
			||||
		$host = $dsn[2]; | 
				
			||||
		if (strpos($host, ':')===false) { | 
				
			||||
			$host .= ':9200'; | 
				
			||||
		} | 
				
			||||
		if(!@stream_socket_client($host, $errorNumber, $errorDescription, 0.5)) { | 
				
			||||
			$this->markTestSkipped('No elasticsearch server running at ' . $params['dsn'] . ' : ' . $errorNumber . ' - ' . $errorDescription); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		parent::setUp(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * @param bool $reset whether to clean up the test database | 
				
			||||
	 * @return Connection | 
				
			||||
	 */ | 
				
			||||
	public function getConnection($reset = true) | 
				
			||||
	{ | 
				
			||||
		$databases = $this->getParam('databases'); | 
				
			||||
		$params = isset($databases['elasticsearch']) ? $databases['elasticsearch'] : array(); | 
				
			||||
		$db = new Connection(); | 
				
			||||
		if ($reset) { | 
				
			||||
			$db->open(); | 
				
			||||
		} | 
				
			||||
		return $db; | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,185 @@
					 | 
				
			||||
<?php | 
				
			||||
 | 
				
			||||
namespace yiiunit\extensions\elasticsearch; | 
				
			||||
 | 
				
			||||
use yii\elasticsearch\Query; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @group elasticsearch | 
				
			||||
 */ | 
				
			||||
class QueryTest extends ElasticSearchTestCase | 
				
			||||
{ | 
				
			||||
	protected function setUp() | 
				
			||||
	{ | 
				
			||||
		parent::setUp(); | 
				
			||||
 | 
				
			||||
		$command = $this->getConnection()->createCommand(); | 
				
			||||
 | 
				
			||||
		$command->deleteAllIndexes(); | 
				
			||||
 | 
				
			||||
		$command->insert('test', 'user', ['name' => 'user1', 'email' => 'user1@example.com', 'status' => 1], 1); | 
				
			||||
		$command->insert('test', 'user', ['name' => 'user2', 'email' => 'user2@example.com', 'status' => 1], 2); | 
				
			||||
		$command->insert('test', 'user', ['name' => 'user3', 'email' => 'user3@example.com', 'status' => 2], 3); | 
				
			||||
		$command->insert('test', 'user', ['name' => 'user4', 'email' => 'user4@example.com', 'status' => 1], 4); | 
				
			||||
 | 
				
			||||
		$command->flushIndex(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFields() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$query->fields(['name', 'status']); | 
				
			||||
		$this->assertEquals(['name', 'status'], $query->fields); | 
				
			||||
 | 
				
			||||
		$query->fields('name', 'status'); | 
				
			||||
		$this->assertEquals(['name', 'status'], $query->fields); | 
				
			||||
 | 
				
			||||
		$result = $query->one($this->getConnection()); | 
				
			||||
		$this->assertEquals(2, count($result['_source'])); | 
				
			||||
		$this->assertArrayHasKey('status', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('name', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
 | 
				
			||||
		$query->fields([]); | 
				
			||||
		$this->assertEquals([], $query->fields); | 
				
			||||
 | 
				
			||||
		$result = $query->one($this->getConnection()); | 
				
			||||
		$this->assertEquals([], $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
 | 
				
			||||
		$query->fields(null); | 
				
			||||
		$this->assertNull($query->fields); | 
				
			||||
 | 
				
			||||
		$result = $query->one($this->getConnection()); | 
				
			||||
		$this->assertEquals(3, count($result['_source'])); | 
				
			||||
		$this->assertArrayHasKey('status', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('email', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('name', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testOne() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$result = $query->one($this->getConnection()); | 
				
			||||
		$this->assertEquals(3, count($result['_source'])); | 
				
			||||
		$this->assertArrayHasKey('status', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('email', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('name', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
 | 
				
			||||
		$result = $query->where(['name' => 'user1'])->one($this->getConnection()); | 
				
			||||
		$this->assertEquals(3, count($result['_source'])); | 
				
			||||
		$this->assertArrayHasKey('status', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('email', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('name', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
		$this->assertEquals(1, $result['_id']); | 
				
			||||
 | 
				
			||||
		$result = $query->where(['name' => 'user5'])->one($this->getConnection()); | 
				
			||||
		$this->assertFalse($result); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testAll() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$results = $query->all($this->getConnection()); | 
				
			||||
		$this->assertEquals(4, count($results)); | 
				
			||||
		$result = reset($results); | 
				
			||||
		$this->assertEquals(3, count($result['_source'])); | 
				
			||||
		$this->assertArrayHasKey('status', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('email', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('name', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
 | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$results = $query->where(['name' => 'user1'])->all($this->getConnection()); | 
				
			||||
		$this->assertEquals(1, count($results)); | 
				
			||||
		$result = reset($results); | 
				
			||||
		$this->assertEquals(3, count($result['_source'])); | 
				
			||||
		$this->assertArrayHasKey('status', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('email', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('name', $result['_source']); | 
				
			||||
		$this->assertArrayHasKey('_id', $result); | 
				
			||||
		$this->assertEquals(1, $result['_id']); | 
				
			||||
 | 
				
			||||
		// indexBy | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$results = $query->indexBy('name')->all($this->getConnection()); | 
				
			||||
		$this->assertEquals(4, count($results)); | 
				
			||||
		ksort($results); | 
				
			||||
		$this->assertEquals(['user1', 'user2', 'user3', 'user4'], array_keys($results)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testScalar() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$result = $query->where(['name' => 'user1'])->scalar('name', $this->getConnection()); | 
				
			||||
		$this->assertEquals('user1', $result); | 
				
			||||
		$result = $query->where(['name' => 'user1'])->scalar('noname', $this->getConnection()); | 
				
			||||
		$this->assertNull($result); | 
				
			||||
		$result = $query->where(['name' => 'user5'])->scalar('name', $this->getConnection()); | 
				
			||||
		$this->assertNull($result); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testColumn() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->from('test', 'user'); | 
				
			||||
 | 
				
			||||
		$result = $query->orderBy(['name' => SORT_ASC])->column('name', $this->getConnection()); | 
				
			||||
		$this->assertEquals(['user1', 'user2', 'user3', 'user4'], $result); | 
				
			||||
		$result = $query->column('noname', $this->getConnection()); | 
				
			||||
		$this->assertEquals([null, null, null, null], $result); | 
				
			||||
		$result = $query->where(['name' => 'user5'])->scalar('name', $this->getConnection()); | 
				
			||||
		$this->assertNull($result); | 
				
			||||
 | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// TODO test facets | 
				
			||||
 | 
				
			||||
	// TODO test complex where() every edge of QueryBuilder | 
				
			||||
 | 
				
			||||
	public function testOrder() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->orderBy('team'); | 
				
			||||
		$this->assertEquals(['team' => SORT_ASC], $query->orderBy); | 
				
			||||
 | 
				
			||||
		$query->addOrderBy('company'); | 
				
			||||
		$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); | 
				
			||||
 | 
				
			||||
		$query->addOrderBy('age'); | 
				
			||||
		$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); | 
				
			||||
 | 
				
			||||
		$query->addOrderBy(['age' => SORT_DESC]); | 
				
			||||
		$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); | 
				
			||||
 | 
				
			||||
		$query->addOrderBy('age ASC, company DESC'); | 
				
			||||
		$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testLimitOffset() | 
				
			||||
	{ | 
				
			||||
		$query = new Query; | 
				
			||||
		$query->limit(10)->offset(5); | 
				
			||||
		$this->assertEquals(10, $query->limit); | 
				
			||||
		$this->assertEquals(5, $query->offset); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testUnion() | 
				
			||||
	{ | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,759 @@
					 | 
				
			||||
<?php | 
				
			||||
/** | 
				
			||||
 *  | 
				
			||||
 *  | 
				
			||||
 * @author Carsten Brandt <mail@cebe.cc> | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
namespace yiiunit\framework\ar; | 
				
			||||
 | 
				
			||||
use yii\db\ActiveQueryInterface; | 
				
			||||
use yiiunit\TestCase; | 
				
			||||
use yiiunit\data\ar\Customer; | 
				
			||||
use yiiunit\data\ar\Order; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * This trait provides unit tests shared by the differen AR implementations | 
				
			||||
 * | 
				
			||||
 * @var TestCase $this | 
				
			||||
 */ | 
				
			||||
trait ActiveRecordTestTrait | 
				
			||||
{ | 
				
			||||
	/** | 
				
			||||
	 * This method should call Customer::find($q) | 
				
			||||
	 * @param $q | 
				
			||||
	 * @return mixed | 
				
			||||
	 */ | 
				
			||||
	public abstract function callCustomerFind($q = null); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should call Order::find($q) | 
				
			||||
	 * @param $q | 
				
			||||
	 * @return mixed | 
				
			||||
	 */ | 
				
			||||
	public abstract function callOrderFind($q = null); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should call OrderItem::find($q) | 
				
			||||
	 * @param $q | 
				
			||||
	 * @return mixed | 
				
			||||
	 */ | 
				
			||||
	public abstract function callOrderItemFind($q = null); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should call Item::find($q) | 
				
			||||
	 * @param $q | 
				
			||||
	 * @return mixed | 
				
			||||
	 */ | 
				
			||||
	public abstract function callItemFind($q = null); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should return the classname of Customer class | 
				
			||||
	 * @return string | 
				
			||||
	 */ | 
				
			||||
	public abstract function getCustomerClass(); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should return the classname of Order class | 
				
			||||
	 * @return string | 
				
			||||
	 */ | 
				
			||||
	public abstract function getOrderClass(); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should return the classname of OrderItem class | 
				
			||||
	 * @return string | 
				
			||||
	 */ | 
				
			||||
	public abstract function getOrderItemClass(); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * This method should return the classname of Item class | 
				
			||||
	 * @return string | 
				
			||||
	 */ | 
				
			||||
	public abstract function getItemClass(); | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * can be overridden to do things after save() | 
				
			||||
	 */ | 
				
			||||
	public function afterSave() | 
				
			||||
	{ | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
	public function testFind() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// find one | 
				
			||||
		$result = $this->callCustomerFind(); | 
				
			||||
		$this->assertTrue($result instanceof ActiveQueryInterface); | 
				
			||||
		$customer = $result->one(); | 
				
			||||
		$this->assertTrue($customer instanceof $customerClass); | 
				
			||||
 | 
				
			||||
		// find all | 
				
			||||
		$customers = $this->callCustomerFind()->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers[0] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers[1] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers[2] instanceof $customerClass); | 
				
			||||
 | 
				
			||||
		// find all asArray | 
				
			||||
		$customers = $this->callCustomerFind()->asArray()->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('email', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('address', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('status', $customers[0]); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('email', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('address', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('status', $customers[1]); | 
				
			||||
		$this->assertArrayHasKey('id', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('name', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('email', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('address', $customers[2]); | 
				
			||||
		$this->assertArrayHasKey('status', $customers[2]); | 
				
			||||
 | 
				
			||||
		// find by a single primary key | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertTrue($customer instanceof $customerClass); | 
				
			||||
		$this->assertEquals('user2', $customer->name); | 
				
			||||
		$customer = $this->callCustomerFind(5); | 
				
			||||
		$this->assertNull($customer); | 
				
			||||
		$customer = $this->callCustomerFind(['id' => [5, 6, 1]]); | 
				
			||||
		$this->assertEquals(1, count($customer)); | 
				
			||||
		$customer = $this->callCustomerFind()->where(['id' => [5, 6, 1]])->one(); | 
				
			||||
		$this->assertNotNull($customer); | 
				
			||||
 | 
				
			||||
		// find by column values | 
				
			||||
		$customer = $this->callCustomerFind(['id' => 2, 'name' => 'user2']); | 
				
			||||
		$this->assertTrue($customer instanceof $customerClass); | 
				
			||||
		$this->assertEquals('user2', $customer->name); | 
				
			||||
		$customer = $this->callCustomerFind(['id' => 2, 'name' => 'user1']); | 
				
			||||
		$this->assertNull($customer); | 
				
			||||
		$customer = $this->callCustomerFind(['id' => 5]); | 
				
			||||
		$this->assertNull($customer); | 
				
			||||
		$customer = $this->callCustomerFind(['name' => 'user5']); | 
				
			||||
		$this->assertNull($customer); | 
				
			||||
 | 
				
			||||
		// find by attributes | 
				
			||||
		$customer = $this->callCustomerFind()->where(['name' => 'user2'])->one(); | 
				
			||||
		$this->assertTrue($customer instanceof $customerClass); | 
				
			||||
		$this->assertEquals(2, $customer->id); | 
				
			||||
 | 
				
			||||
		// scope | 
				
			||||
		$this->assertEquals(2, count($this->callCustomerFind()->active()->all())); | 
				
			||||
		$this->assertEquals(2, $this->callCustomerFind()->active()->count()); | 
				
			||||
 | 
				
			||||
		// asArray | 
				
			||||
		$customer = $this->callCustomerFind()->where(['id' => 2])->asArray()->one(); | 
				
			||||
		$this->assertEquals([ | 
				
			||||
			'id' => '2', | 
				
			||||
			'email' => 'user2@example.com', | 
				
			||||
			'name' => 'user2', | 
				
			||||
			'address' => 'address2', | 
				
			||||
			'status' => '1', | 
				
			||||
		], $customer); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindScalar() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// query scalar | 
				
			||||
		$customerName = $this->callCustomerFind()->where(['id' => 2])->scalar('name'); | 
				
			||||
		$this->assertEquals('user2', $customerName); | 
				
			||||
		$customerName = $this->callCustomerFind()->where(['status' => 2])->scalar('name'); | 
				
			||||
		$this->assertEquals('user3', $customerName); | 
				
			||||
		$customerName = $this->callCustomerFind()->where(['status' => 2])->scalar('noname'); | 
				
			||||
		$this->assertNull($customerName); | 
				
			||||
		$customerId = $this->callCustomerFind()->where(['status' => 2])->scalar('id'); | 
				
			||||
		$this->assertEquals(3, $customerId); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindColumn() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$this->assertEquals(['user1', 'user2', 'user3'], $this->callCustomerFind()->orderBy(['name' => SORT_ASC])->column('name')); | 
				
			||||
		$this->assertEquals(['user3', 'user2', 'user1'], $this->callCustomerFind()->orderBy(['name' => SORT_DESC])->column('name')); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testfindIndexBy() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// indexBy | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy('name')->orderBy('id')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers['user1'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['user2'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['user3'] instanceof $customerClass); | 
				
			||||
 | 
				
			||||
		// indexBy callable | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy(function ($customer) { | 
				
			||||
			return $customer->id . '-' . $customer->name; | 
				
			||||
		})->orderBy('id')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers['1-user1'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['2-user2'] instanceof $customerClass); | 
				
			||||
		$this->assertTrue($customers['3-user3'] instanceof $customerClass); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testfindIndexByAsArray() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// indexBy + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->asArray()->indexBy('name')->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('email', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('address', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('status', $customers['user1']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('email', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('address', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('status', $customers['user2']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['user3']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['user3']); | 
				
			||||
		$this->assertArrayHasKey('email', $customers['user3']); | 
				
			||||
		$this->assertArrayHasKey('address', $customers['user3']); | 
				
			||||
		$this->assertArrayHasKey('status', $customers['user3']); | 
				
			||||
 | 
				
			||||
		// indexBy callable + asArray | 
				
			||||
		$customers = $this->callCustomerFind()->indexBy(function ($customer) { | 
				
			||||
			return $customer['id'] . '-' . $customer['name']; | 
				
			||||
		})->asArray()->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('email', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('address', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('status', $customers['1-user1']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('email', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('address', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('status', $customers['2-user2']); | 
				
			||||
		$this->assertArrayHasKey('id', $customers['3-user3']); | 
				
			||||
		$this->assertArrayHasKey('name', $customers['3-user3']); | 
				
			||||
		$this->assertArrayHasKey('email', $customers['3-user3']); | 
				
			||||
		$this->assertArrayHasKey('address', $customers['3-user3']); | 
				
			||||
		$this->assertArrayHasKey('status', $customers['3-user3']); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testRefresh() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customer = new $customerClass(); | 
				
			||||
		$this->assertFalse($customer->refresh()); | 
				
			||||
 | 
				
			||||
		$customer = $this->callCustomerFind(1); | 
				
			||||
		$customer->name = 'to be refreshed'; | 
				
			||||
		$this->assertTrue($customer->refresh()); | 
				
			||||
		$this->assertEquals('user1', $customer->name); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testEquals() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		$itemClass = $this->getItemClass(); | 
				
			||||
 | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customerA = new $customerClass(); | 
				
			||||
		$customerB = new $customerClass(); | 
				
			||||
		$this->assertFalse($customerA->equals($customerB)); | 
				
			||||
 | 
				
			||||
		$customerA = new $customerClass(); | 
				
			||||
		$customerB = new $itemClass(); | 
				
			||||
		$this->assertFalse($customerA->equals($customerB)); | 
				
			||||
 | 
				
			||||
		$customerA = $this->callCustomerFind(1); | 
				
			||||
		$customerB = $this->callCustomerFind(2); | 
				
			||||
		$this->assertFalse($customerA->equals($customerB)); | 
				
			||||
 | 
				
			||||
		$customerB = $this->callCustomerFind(1); | 
				
			||||
		$this->assertTrue($customerA->equals($customerB)); | 
				
			||||
 | 
				
			||||
		$customerA = $this->callCustomerFind(1); | 
				
			||||
		$customerB = $this->callItemFind(1); | 
				
			||||
		$this->assertFalse($customerA->equals($customerB)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindCount() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$this->assertEquals(3, $this->callCustomerFind()->count()); | 
				
			||||
		// TODO should limit have effect on count() | 
				
			||||
//		$this->assertEquals(1, $this->callCustomerFind()->limit(1)->count()); | 
				
			||||
//		$this->assertEquals(2, $this->callCustomerFind()->limit(2)->count()); | 
				
			||||
//		$this->assertEquals(1, $this->callCustomerFind()->offset(2)->limit(2)->count()); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindLimit() | 
				
			||||
	{ | 
				
			||||
		if (getenv('TRAVIS') == 'true' && $this instanceof \yiiunit\extensions\elasticsearch\ActiveRecordTest) { | 
				
			||||
			// https://github.com/yiisoft/yii2/issues/1317 | 
				
			||||
			$this->markTestSkipped('This test is unreproduceable failing on travis-ci, locally it is passing.'); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// all() | 
				
			||||
		$customers = $this->callCustomerFind()->all(); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->orderBy('id')->limit(1)->all(); | 
				
			||||
		$this->assertEquals(1, count($customers)); | 
				
			||||
		$this->assertEquals('user1', $customers[0]->name); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->orderBy('id')->limit(1)->offset(1)->all(); | 
				
			||||
		$this->assertEquals(1, count($customers)); | 
				
			||||
		$this->assertEquals('user2', $customers[0]->name); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->orderBy('id')->limit(1)->offset(2)->all(); | 
				
			||||
		$this->assertEquals(1, count($customers)); | 
				
			||||
		$this->assertEquals('user3', $customers[0]->name); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->orderBy('id')->limit(2)->offset(1)->all(); | 
				
			||||
		$this->assertEquals(2, count($customers)); | 
				
			||||
		$this->assertEquals('user2', $customers[0]->name); | 
				
			||||
		$this->assertEquals('user3', $customers[1]->name); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->limit(2)->offset(3)->all(); | 
				
			||||
		$this->assertEquals(0, count($customers)); | 
				
			||||
 | 
				
			||||
		// one() | 
				
			||||
		$customer = $this->callCustomerFind()->orderBy('id')->one(); | 
				
			||||
		$this->assertEquals('user1', $customer->name); | 
				
			||||
 | 
				
			||||
		$customer = $this->callCustomerFind()->orderBy('id')->offset(0)->one(); | 
				
			||||
		$this->assertEquals('user1', $customer->name); | 
				
			||||
 | 
				
			||||
		$customer = $this->callCustomerFind()->orderBy('id')->offset(1)->one(); | 
				
			||||
		$this->assertEquals('user2', $customer->name); | 
				
			||||
 | 
				
			||||
		$customer = $this->callCustomerFind()->orderBy('id')->offset(2)->one(); | 
				
			||||
		$this->assertEquals('user3', $customer->name); | 
				
			||||
 | 
				
			||||
		$customer = $this->callCustomerFind()->offset(3)->one(); | 
				
			||||
		$this->assertNull($customer); | 
				
			||||
 | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindComplexCondition() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$this->assertEquals(2, $this->callCustomerFind()->where(['OR', ['name' => 'user1'], ['name' => 'user2']])->count()); | 
				
			||||
		$this->assertEquals(2, count($this->callCustomerFind()->where(['OR', ['name' => 'user1'], ['name' => 'user2']])->all())); | 
				
			||||
 | 
				
			||||
		$this->assertEquals(2, $this->callCustomerFind()->where(['name' => ['user1','user2']])->count()); | 
				
			||||
		$this->assertEquals(2, count($this->callCustomerFind()->where(['name' => ['user1','user2']])->all())); | 
				
			||||
 | 
				
			||||
		$this->assertEquals(1, $this->callCustomerFind()->where(['AND', ['name' => ['user2','user3']], ['BETWEEN', 'status', 2, 4]])->count()); | 
				
			||||
		$this->assertEquals(1, count($this->callCustomerFind()->where(['AND', ['name' => ['user2','user3']], ['BETWEEN', 'status', 2, 4]])->all())); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindNullValues() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$customer->name = null; | 
				
			||||
		$customer->save(false); | 
				
			||||
		$this->afterSave(); | 
				
			||||
 | 
				
			||||
		$result = $this->callCustomerFind()->where(['name' => null])->all(); | 
				
			||||
		$this->assertEquals(1, count($result)); | 
				
			||||
		$this->assertEquals(2, reset($result)->primaryKey); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testExists() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$this->assertTrue($this->callCustomerFind()->where(['id' => 2])->exists()); | 
				
			||||
		$this->assertFalse($this->callCustomerFind()->where(['id' => 5])->exists()); | 
				
			||||
		$this->assertTrue($this->callCustomerFind()->where(['name' => 'user1'])->exists()); | 
				
			||||
		$this->assertFalse($this->callCustomerFind()->where(['name' => 'user5'])->exists()); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindLazy() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertFalse($customer->isRelationPopulated('orders')); | 
				
			||||
		$orders = $customer->orders; | 
				
			||||
		$this->assertTrue($customer->isRelationPopulated('orders')); | 
				
			||||
		$this->assertEquals(2, count($orders)); | 
				
			||||
		$this->assertEquals(1, count($customer->populatedRelations)); | 
				
			||||
 | 
				
			||||
		/** @var Customer $customer */ | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertFalse($customer->isRelationPopulated('orders')); | 
				
			||||
		$orders = $customer->getOrders()->where(['id' => 3])->all(); | 
				
			||||
		$this->assertFalse($customer->isRelationPopulated('orders')); | 
				
			||||
		$this->assertEquals(0, count($customer->populatedRelations)); | 
				
			||||
 | 
				
			||||
		$this->assertEquals(1, count($orders)); | 
				
			||||
		$this->assertEquals(3, $orders[0]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindEager() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customers = $this->callCustomerFind()->with('orders')->indexBy('id')->all(); | 
				
			||||
		ksort($customers); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers[1]->isRelationPopulated('orders')); | 
				
			||||
		$this->assertTrue($customers[2]->isRelationPopulated('orders')); | 
				
			||||
		$this->assertTrue($customers[3]->isRelationPopulated('orders')); | 
				
			||||
		$this->assertEquals(1, count($customers[1]->orders)); | 
				
			||||
		$this->assertEquals(2, count($customers[2]->orders)); | 
				
			||||
		$this->assertEquals(0, count($customers[3]->orders)); | 
				
			||||
 | 
				
			||||
		$customer = $this->callCustomerFind()->where(['id' => 1])->with('orders')->one(); | 
				
			||||
		$this->assertTrue($customer->isRelationPopulated('orders')); | 
				
			||||
		$this->assertEquals(1, count($customer->orders)); | 
				
			||||
		$this->assertEquals(1, count($customer->populatedRelations)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindLazyVia() | 
				
			||||
	{ | 
				
			||||
		if (getenv('TRAVIS') == 'true' && $this instanceof \yiiunit\extensions\elasticsearch\ActiveRecordTest) { | 
				
			||||
			// https://github.com/yiisoft/yii2/issues/1317 | 
				
			||||
			$this->markTestSkipped('This test is unreproduceable failing on travis-ci, locally it is passing.'); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		/** @var Order $order */ | 
				
			||||
		$order = $this->callOrderFind(1); | 
				
			||||
		$this->assertEquals(1, $order->id); | 
				
			||||
		$this->assertEquals(2, count($order->items)); | 
				
			||||
		$this->assertEquals(1, $order->items[0]->id); | 
				
			||||
		$this->assertEquals(2, $order->items[1]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindLazyVia2() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		/** @var Order $order */ | 
				
			||||
		$order = $this->callOrderFind(1); | 
				
			||||
		$order->id = 100; | 
				
			||||
		$this->assertEquals([], $order->items); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindEagerViaRelation() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$orders = $this->callOrderFind()->with('items')->orderBy('id')->all(); | 
				
			||||
		$this->assertEquals(3, count($orders)); | 
				
			||||
		$order = $orders[0]; | 
				
			||||
		$this->assertEquals(1, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('items')); | 
				
			||||
		$this->assertEquals(2, count($order->items)); | 
				
			||||
		$this->assertEquals(1, $order->items[0]->id); | 
				
			||||
		$this->assertEquals(2, $order->items[1]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testFindNestedRelation() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customers = $this->callCustomerFind()->with('orders', 'orders.items')->indexBy('id')->all(); | 
				
			||||
		ksort($customers); | 
				
			||||
		$this->assertEquals(3, count($customers)); | 
				
			||||
		$this->assertTrue($customers[1]->isRelationPopulated('orders')); | 
				
			||||
		$this->assertTrue($customers[2]->isRelationPopulated('orders')); | 
				
			||||
		$this->assertTrue($customers[3]->isRelationPopulated('orders')); | 
				
			||||
		$this->assertEquals(1, count($customers[1]->orders)); | 
				
			||||
		$this->assertEquals(2, count($customers[2]->orders)); | 
				
			||||
		$this->assertEquals(0, count($customers[3]->orders)); | 
				
			||||
		$this->assertTrue($customers[1]->orders[0]->isRelationPopulated('items')); | 
				
			||||
		$this->assertTrue($customers[2]->orders[0]->isRelationPopulated('items')); | 
				
			||||
		$this->assertTrue($customers[2]->orders[1]->isRelationPopulated('items')); | 
				
			||||
		$this->assertEquals(2, count($customers[1]->orders[0]->items)); | 
				
			||||
		$this->assertEquals(3, count($customers[2]->orders[0]->items)); | 
				
			||||
		$this->assertEquals(1, count($customers[2]->orders[1]->items)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Ensure ActiveRelation does preserve order of items on find via() | 
				
			||||
	 * https://github.com/yiisoft/yii2/issues/1310 | 
				
			||||
	 */ | 
				
			||||
	public function testFindEagerViaRelationPreserveOrder() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$orders = $this->callOrderFind()->with('itemsInOrder1')->orderBy('create_time')->all(); | 
				
			||||
		$this->assertEquals(3, count($orders)); | 
				
			||||
 | 
				
			||||
		$order = $orders[0]; | 
				
			||||
		$this->assertEquals(1, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('itemsInOrder1')); | 
				
			||||
		$this->assertEquals(2, count($order->itemsInOrder1)); | 
				
			||||
		$this->assertEquals(1, $order->itemsInOrder1[0]->id); | 
				
			||||
		$this->assertEquals(2, $order->itemsInOrder1[1]->id); | 
				
			||||
 | 
				
			||||
		$order = $orders[1]; | 
				
			||||
		$this->assertEquals(2, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('itemsInOrder1')); | 
				
			||||
		$this->assertEquals(3, count($order->itemsInOrder1)); | 
				
			||||
		$this->assertEquals(5, $order->itemsInOrder1[0]->id); | 
				
			||||
		$this->assertEquals(3, $order->itemsInOrder1[1]->id); | 
				
			||||
		$this->assertEquals(4, $order->itemsInOrder1[2]->id); | 
				
			||||
 | 
				
			||||
		$order = $orders[2]; | 
				
			||||
		$this->assertEquals(3, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('itemsInOrder1')); | 
				
			||||
		$this->assertEquals(1, count($order->itemsInOrder1)); | 
				
			||||
		$this->assertEquals(2, $order->itemsInOrder1[0]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// different order in via table | 
				
			||||
	public function testFindEagerViaRelationPreserveOrderB() | 
				
			||||
	{ | 
				
			||||
		$orders = $this->callOrderFind()->with('itemsInOrder2')->orderBy('create_time')->all(); | 
				
			||||
		$this->assertEquals(3, count($orders)); | 
				
			||||
 | 
				
			||||
		$order = $orders[0]; | 
				
			||||
		$this->assertEquals(1, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('itemsInOrder2')); | 
				
			||||
		$this->assertEquals(2, count($order->itemsInOrder2)); | 
				
			||||
		$this->assertEquals(1, $order->itemsInOrder2[0]->id); | 
				
			||||
		$this->assertEquals(2, $order->itemsInOrder2[1]->id); | 
				
			||||
 | 
				
			||||
		$order = $orders[1]; | 
				
			||||
		$this->assertEquals(2, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('itemsInOrder2')); | 
				
			||||
		$this->assertEquals(3, count($order->itemsInOrder2)); | 
				
			||||
		$this->assertEquals(5, $order->itemsInOrder2[0]->id); | 
				
			||||
		$this->assertEquals(3, $order->itemsInOrder2[1]->id); | 
				
			||||
		$this->assertEquals(4, $order->itemsInOrder2[2]->id); | 
				
			||||
 | 
				
			||||
		$order = $orders[2]; | 
				
			||||
		$this->assertEquals(3, $order->id); | 
				
			||||
		$this->assertTrue($order->isRelationPopulated('itemsInOrder2')); | 
				
			||||
		$this->assertEquals(1, count($order->itemsInOrder2)); | 
				
			||||
		$this->assertEquals(2, $order->itemsInOrder2[0]->id); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testLink() | 
				
			||||
	{ | 
				
			||||
		$orderClass = $this->getOrderClass(); | 
				
			||||
		$orderItemClass = $this->getOrderItemClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertEquals(2, count($customer->orders)); | 
				
			||||
 | 
				
			||||
		// has many | 
				
			||||
		$order = new $orderClass; | 
				
			||||
		$order->total = 100; | 
				
			||||
		$this->assertTrue($order->isNewRecord); | 
				
			||||
		$customer->link('orders', $order); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(3, count($customer->orders)); | 
				
			||||
		$this->assertFalse($order->isNewRecord); | 
				
			||||
		$this->assertEquals(3, count($customer->getOrders()->all())); | 
				
			||||
		$this->assertEquals(2, $order->customer_id); | 
				
			||||
 | 
				
			||||
		// belongs to | 
				
			||||
		$order = new $orderClass; | 
				
			||||
		$order->total = 100; | 
				
			||||
		$this->assertTrue($order->isNewRecord); | 
				
			||||
		$customer = $this->callCustomerFind(1); | 
				
			||||
		$this->assertNull($order->customer); | 
				
			||||
		$order->link('customer', $customer); | 
				
			||||
		$this->assertFalse($order->isNewRecord); | 
				
			||||
		$this->assertEquals(1, $order->customer_id); | 
				
			||||
		$this->assertEquals(1, $order->customer->primaryKey); | 
				
			||||
 | 
				
			||||
		// via model | 
				
			||||
		$order = $this->callOrderFind(1); | 
				
			||||
		$this->assertEquals(2, count($order->items)); | 
				
			||||
		$this->assertEquals(2, count($order->orderItems)); | 
				
			||||
		$orderItem = $this->callOrderItemFind(['order_id' => 1, 'item_id' => 3]); | 
				
			||||
		$this->assertNull($orderItem); | 
				
			||||
		$item = $this->callItemFind(3); | 
				
			||||
		$order->link('items', $item, ['quantity' => 10, 'subtotal' => 100]); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(3, count($order->items)); | 
				
			||||
		$this->assertEquals(3, count($order->orderItems)); | 
				
			||||
		$orderItem = $this->callOrderItemFind(['order_id' => 1, 'item_id' => 3]); | 
				
			||||
		$this->assertTrue($orderItem instanceof $orderItemClass); | 
				
			||||
		$this->assertEquals(10, $orderItem->quantity); | 
				
			||||
		$this->assertEquals(100, $orderItem->subtotal); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testUnlink() | 
				
			||||
	{ | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// has many | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertEquals(2, count($customer->orders)); | 
				
			||||
		$customer->unlink('orders', $customer->orders[1], true); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(1, count($customer->orders)); | 
				
			||||
		$this->assertNull($this->callOrderFind(3)); | 
				
			||||
 | 
				
			||||
		// via model | 
				
			||||
		$order = $this->callOrderFind(2); | 
				
			||||
		$this->assertEquals(3, count($order->items)); | 
				
			||||
		$this->assertEquals(3, count($order->orderItems)); | 
				
			||||
		$order->unlink('items', $order->items[2], true); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(2, count($order->items)); | 
				
			||||
		$this->assertEquals(2, count($order->orderItems)); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public static $afterSaveNewRecord; | 
				
			||||
	public static $afterSaveInsert; | 
				
			||||
 | 
				
			||||
	public function testInsert() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customer = new $customerClass; | 
				
			||||
		$customer->email = 'user4@example.com'; | 
				
			||||
		$customer->name = 'user4'; | 
				
			||||
		$customer->address = 'address4'; | 
				
			||||
 | 
				
			||||
		$this->assertNull($customer->id); | 
				
			||||
		$this->assertTrue($customer->isNewRecord); | 
				
			||||
		static::$afterSaveNewRecord = null; | 
				
			||||
		static::$afterSaveInsert = null; | 
				
			||||
 | 
				
			||||
		$customer->save(); | 
				
			||||
		$this->afterSave(); | 
				
			||||
 | 
				
			||||
		$this->assertNotNull($customer->id); | 
				
			||||
		$this->assertFalse(static::$afterSaveNewRecord); | 
				
			||||
		$this->assertTrue(static::$afterSaveInsert); | 
				
			||||
		$this->assertFalse($customer->isNewRecord); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testUpdate() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// save | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertTrue($customer instanceof $customerClass); | 
				
			||||
		$this->assertEquals('user2', $customer->name); | 
				
			||||
		$this->assertFalse($customer->isNewRecord); | 
				
			||||
		static::$afterSaveNewRecord = null; | 
				
			||||
		static::$afterSaveInsert = null; | 
				
			||||
 | 
				
			||||
		$customer->name = 'user2x'; | 
				
			||||
		$customer->save(); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals('user2x', $customer->name); | 
				
			||||
		$this->assertFalse($customer->isNewRecord); | 
				
			||||
		$this->assertFalse(static::$afterSaveNewRecord); | 
				
			||||
		$this->assertFalse(static::$afterSaveInsert); | 
				
			||||
		$customer2 = $this->callCustomerFind(2); | 
				
			||||
		$this->assertEquals('user2x', $customer2->name); | 
				
			||||
 | 
				
			||||
		// updateAll | 
				
			||||
		$customer = $this->callCustomerFind(3); | 
				
			||||
		$this->assertEquals('user3', $customer->name); | 
				
			||||
		$ret = $customerClass::updateAll(['name' => 'temp'], ['id' => 3]); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(1, $ret); | 
				
			||||
		$customer = $this->callCustomerFind(3); | 
				
			||||
		$this->assertEquals('temp', $customer->name); | 
				
			||||
 | 
				
			||||
		$ret = $customerClass::updateAll(['name' => 'tempX']); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(3, $ret); | 
				
			||||
 | 
				
			||||
		$ret = $customerClass::updateAll(['name' => 'temp'], ['name' => 'user6']); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(0, $ret); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testUpdateCounters() | 
				
			||||
	{ | 
				
			||||
		$orderItemClass = $this->getOrderItemClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// updateCounters | 
				
			||||
		$pk = ['order_id' => 2, 'item_id' => 4]; | 
				
			||||
		$orderItem = $this->callOrderItemFind($pk); | 
				
			||||
		$this->assertEquals(1, $orderItem->quantity); | 
				
			||||
		$ret = $orderItem->updateCounters(['quantity' => -1]); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertTrue($ret); | 
				
			||||
		$this->assertEquals(0, $orderItem->quantity); | 
				
			||||
		$orderItem = $this->callOrderItemFind($pk); | 
				
			||||
		$this->assertEquals(0, $orderItem->quantity); | 
				
			||||
 | 
				
			||||
		// updateAllCounters | 
				
			||||
		$pk = ['order_id' => 1, 'item_id' => 2]; | 
				
			||||
		$orderItem = $this->callOrderItemFind($pk); | 
				
			||||
		$this->assertEquals(2, $orderItem->quantity); | 
				
			||||
		$ret = $orderItemClass::updateAllCounters([ | 
				
			||||
			'quantity' => 3, | 
				
			||||
			'subtotal' => -10, | 
				
			||||
		], $pk); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(1, $ret); | 
				
			||||
		$orderItem = $this->callOrderItemFind($pk); | 
				
			||||
		$this->assertEquals(5, $orderItem->quantity); | 
				
			||||
		$this->assertEquals(30, $orderItem->subtotal); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public function testDelete() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		// delete | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertTrue($customer instanceof $customerClass); | 
				
			||||
		$this->assertEquals('user2', $customer->name); | 
				
			||||
		$customer->delete(); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$customer = $this->callCustomerFind(2); | 
				
			||||
		$this->assertNull($customer); | 
				
			||||
 | 
				
			||||
		// deleteAll | 
				
			||||
		$customers = $this->callCustomerFind()->all(); | 
				
			||||
		$this->assertEquals(2, count($customers)); | 
				
			||||
		$ret = $customerClass::deleteAll(); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(2, $ret); | 
				
			||||
		$customers = $this->callCustomerFind()->all(); | 
				
			||||
		$this->assertEquals(0, count($customers)); | 
				
			||||
 | 
				
			||||
		$ret = $customerClass::deleteAll(); | 
				
			||||
		$this->afterSave(); | 
				
			||||
		$this->assertEquals(0, $ret); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	/** | 
				
			||||
	 * Some PDO implementations(e.g. cubrid) do not support boolean values. | 
				
			||||
	 * Make sure this does not affect AR layer. | 
				
			||||
	 */ | 
				
			||||
	public function testBooleanAttribute() | 
				
			||||
	{ | 
				
			||||
		$customerClass = $this->getCustomerClass(); | 
				
			||||
		/** @var TestCase|ActiveRecordTestTrait $this */ | 
				
			||||
		$customer = new $customerClass(); | 
				
			||||
		$customer->name = 'boolean customer'; | 
				
			||||
		$customer->email = 'mail@example.com'; | 
				
			||||
		$customer->status = true; | 
				
			||||
		$customer->save(false); | 
				
			||||
 | 
				
			||||
		$customer->refresh(); | 
				
			||||
		$this->assertEquals(1, $customer->status); | 
				
			||||
 | 
				
			||||
		$customer->status = false; | 
				
			||||
		$customer->save(false); | 
				
			||||
 | 
				
			||||
		$customer->refresh(); | 
				
			||||
		$this->assertEquals(0, $customer->status); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->where(['status' => true])->all(); | 
				
			||||
		$this->assertEquals(2, count($customers)); | 
				
			||||
 | 
				
			||||
		$customers = $this->callCustomerFind()->where(['status' => false])->all(); | 
				
			||||
		$this->assertEquals(1, count($customers)); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue