├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src └── Behavior.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.phar -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Yii2 Elasticsearch Behavior 2 | =========================== 3 | 4 | 0.0.2 under development 5 | ----------------------- 6 | 7 | - Bug [#1](https://github.com/Borales/yii2-elasticsearch-behavior/issues/1): Fixed using of \yii\db\Expression as an attribute value (Borales) 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The yii2-elasticsearch-behavior is free software. 2 | It is released under the terms of the following BSD License. 3 | 4 | Copyright © 2014 by Alexandr Bordun. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Name of Alexandr Bordun may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 25 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Elasticsearch Behavior for Yii 2 2 | ================================ 3 | 4 | [![Latest Stable Version](https://poser.pugx.org/borales/yii2-elasticsearch-behavior/v/stable.svg)](https://packagist.org/packages/borales/yii2-elasticsearch-behavior) 5 | [![Total Downloads](https://poser.pugx.org/borales/yii2-elasticsearch-behavior/downloads.svg)](https://packagist.org/packages/borales/yii2-elasticsearch-behavior) 6 | [![Latest Unstable Version](https://poser.pugx.org/borales/yii2-elasticsearch-behavior/v/unstable.svg)](https://packagist.org/packages/borales/yii2-elasticsearch-behavior) 7 | [![License](https://poser.pugx.org/borales/yii2-elasticsearch-behavior/license.svg)](https://packagist.org/packages/borales/yii2-elasticsearch-behavior) 8 | 9 | Yii2 AR behavior to support [Elasticsearch](http://www.elasticsearch.org/) auto-indexing. 10 | 11 | ![image](https://cloud.githubusercontent.com/assets/1118933/5841840/63c39bb0-a1a0-11e4-9a6b-df0911203ba5.png) 12 | 13 | ## Installation 14 | 15 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 16 | 17 | Either run 18 | 19 | ```bash 20 | $ php composer.phar require "borales/yii2-elasticsearch-behavior" "*" 21 | ``` 22 | 23 | or add 24 | 25 | ``` 26 | "borales/yii2-elasticsearch-behavior": "*" 27 | ``` 28 | 29 | to the `require` section of your `composer.json` file. 30 | 31 | ## Configuring 32 | 33 | Configure model as follows (for "command" `mode`): 34 | 35 | ```php 36 | class Post extends \yii\db\ActiveRecord 37 | { 38 | public function behaviors() 39 | { 40 | return [ 41 | ... 42 | 'elasticsearch' => [ 43 | 'class' => \borales\behaviors\elasticsearch\Behavior::className(), 44 | 'mode' => 'command', 45 | 'elasticIndex' => 'project-index', 46 | 'elasticType' => 'posts', 47 | 'dataMap' => [ 48 | 'id' => 'id', 49 | 'title' => 'name', 50 | 'body' => function() { 51 | return strip_tags($this->body); 52 | }, 53 | 'date_publish' => function() { 54 | return date('U', strtotime($this->date_create)); 55 | }, 56 | 'author' => function() { 57 | return ucfirst($this->author->name); 58 | } 59 | ] 60 | ], 61 | ]; 62 | } 63 | 64 | ... 65 | } 66 | ``` 67 | 68 | Configuration values of the behavior: 69 | - `mode` (possible values: `command` or `model`. Default is `command`) - it is the option, which controls how to interact with Elasticsearch: 70 | - in case of `command` - the behavior use `\Yii::$app->elasticsearch->createCommand()` way to execute commands. In this mode - it is required to set up `elasticIndex` and `elasticType` params. 71 | - in case of `model` - it is required to set up `elasticClass` parameter with the value of model class name (specified class must extend the `\yii\elasticsearch\ActiveRecord` model class). In this case behavior will communicate with Elasticsearch through the specified model class. 72 | - `dataMap` - this is an optional parameter. By default - the behavior will use `$this->owner->attributes` dynamic property of the `\yii\db\ActiveRecord` class (you can learn more how to set up this property [here](https://github.com/yiisoft/yii2/blob/master/docs/guide/structure-models.md#data-exporting-)). Otherwise - this is a key-value array, where the keys are the field names for the Elasticsearch entry and the values are the field names of the current `\yii\db\ActiveRecord` model or anonymous functions (callbacks). 73 | 74 | ### Example of using "model" `mode` 75 | 76 | ```php 77 | class Post extends \yii\db\ActiveRecord 78 | { 79 | public function behaviors() 80 | { 81 | return [ 82 | ... 83 | 'elasticsearch' => [ 84 | 'class' => \borales\behaviors\elasticsearch\Behavior::className(), 85 | 'mode' => 'model', 86 | 'elasticClass' => \common\models\elastic\PostElastic::className(), 87 | 'dataMap' => [ 88 | 'id' => 'id', 89 | 'title' => 'name', 90 | 'body' => function() { 91 | return strip_tags($this->body); 92 | }, 93 | 'date_publish' => function() { 94 | return date('U', strtotime($this->date_create)); 95 | }, 96 | 'author' => function() { 97 | return ucfirst($this->author->name); 98 | } 99 | ] 100 | ], 101 | ]; 102 | } 103 | 104 | ... 105 | } 106 | 107 | ... 108 | 109 | class PostElastic extends \yii\elasticsearch\ActiveRecord 110 | { 111 | /** 112 | * @return array the list of attributes for this record 113 | */ 114 | public function attributes() 115 | { 116 | // path mapping for '_id' is setup to field 'id' 117 | return ['id', 'title', 'body', 'date_publish', 'author']; 118 | } 119 | } 120 | 121 | ``` 122 | 123 | More details and features about Elasticsearch ActiveRecord you will find [here](https://github.com/yiisoft/yii2-elasticsearch#using-the-activerecord). -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "borales/yii2-elasticsearch-behavior", 3 | "version": "0.0.1", 4 | "description": "Yii2 behavior to support Elasticsearch indexing", 5 | "keywords": [ 6 | "yii2", 7 | "yii2-behavior", 8 | "behavior", 9 | "elasticsearch", 10 | "es" 11 | ], 12 | "homepage": "https://github.com/Borales/yii2-elasticsearch-behavior", 13 | "minimum-stability": "dev", 14 | "type": "yii2-extension", 15 | "license": "BSD-3-Clause", 16 | "authors": [ 17 | { 18 | "name": "Borales (Alexandr Bordun)", 19 | "email": "bordun.alexandr@gmail.com" 20 | } 21 | ], 22 | "support": { 23 | "issues": "https://github.com/Borales/yii2-elasticsearch-behavior/issues?state=open", 24 | "source": "https://github.com/Borales/yii2-elasticsearch-behavior" 25 | }, 26 | "autoload": { 27 | "psr-4": {"borales\\behaviors\\elasticsearch\\": "src/"} 28 | }, 29 | "require": { 30 | "yiisoft/yii2-elasticsearch": "*" 31 | } 32 | } -------------------------------------------------------------------------------- /src/Behavior.php: -------------------------------------------------------------------------------- 1 | [ 22 | * 'class' => '\borales\behaviors\elasticsearch\Behavior', 23 | * 'mode' => 'command', 24 | * 'elasticIndex' => 'project-index', 25 | * 'elasticType' => 'posts', 26 | * 'dataMap' => [ 27 | * 'id' => 'id', 28 | * 'title' => 'name', 29 | * 'body' => function() { 30 | * return strip_tags($this->body); 31 | * }, 32 | * 'date_publish' => function() { 33 | * return date('U', strtotime($this->date_create)); 34 | * }, 35 | * 'author' => function() { 36 | * return ucfirst($this->author->name); 37 | * } 38 | * ] 39 | * ], 40 | * ... 41 | * ]; 42 | * } 43 | * ``` 44 | * 45 | * @link https://github.com/Borales/yii2-elasticsearch-behavior 46 | * @author Borales 47 | * @version 0.0.1 48 | */ 49 | class Behavior extends \yii\base\Behavior 50 | { 51 | const MODE_COMMAND = 'command'; 52 | const MODE_MODEL = 'model'; 53 | 54 | /** 55 | * @var string Behavior mode 56 | */ 57 | public $mode = self::MODE_COMMAND; 58 | /** 59 | * @var string Elasticsearch App Component 60 | */ 61 | public $esComponent = 'elasticsearch'; 62 | /** 63 | * @var string Class name (extended from \yii\elasticsearch\ActiveRecord) 64 | */ 65 | public $elasticClass; 66 | /** 67 | * @var string Elasticsearch Index 68 | */ 69 | public $elasticIndex; 70 | /** 71 | * @var string Elasticsearch Type 72 | */ 73 | public $elasticType; 74 | /** 75 | * @var array Model's data 76 | */ 77 | public $dataMap; 78 | 79 | /** 80 | * @throws InvalidConfigException 81 | */ 82 | public function init() 83 | { 84 | parent::init(); 85 | if ($this->mode == self::MODE_COMMAND) { 86 | if (!$this->elasticType || !$this->elasticIndex) { 87 | throw new InvalidConfigException("You must set 'elasticIndex' and 'elasticType' attributes while working in MODE_COMMAND"); 88 | } 89 | } else { 90 | if (!$this->elasticClass) { 91 | throw new InvalidConfigException("You must set 'elasticClass' attribute (extended from \\yii\\elasticsearch\\ActiveRecord) while working in MODE_MODEL"); 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * @return array 98 | */ 99 | public function events() 100 | { 101 | return [ 102 | BaseActiveRecord::EVENT_AFTER_INSERT => 'insert', 103 | BaseActiveRecord::EVENT_AFTER_UPDATE => 'update', 104 | BaseActiveRecord::EVENT_AFTER_DELETE => 'delete', 105 | ]; 106 | } 107 | 108 | /** 109 | * Inserting Elasticsearch index record 110 | * @param Event $event 111 | * @param null $data 112 | */ 113 | public function insert(Event $event, $data = null) 114 | { 115 | $data = $data ? $data : $this->getProcessedData(); 116 | if ($this->mode == self::MODE_COMMAND) { 117 | $this->db()->createCommand() 118 | ->insert($this->elasticIndex, $this->elasticType, $data, $this->getPK()); 119 | } else { 120 | /** @var ActiveRecord $model */ 121 | $model = \Yii::createObject($this->elasticClass, $data); 122 | $model->save(); 123 | } 124 | } 125 | 126 | /** 127 | * Updating Elasticsearch index record. 128 | * If indexed record was not found - insert operation will be executed. 129 | * @param Event $event 130 | */ 131 | public function update(Event $event) 132 | { 133 | $data = $this->getProcessedData(); 134 | if ($this->mode == self::MODE_COMMAND) { 135 | $this->db()->createCommand() 136 | ->update($this->elasticIndex, $this->elasticType, $this->getPK(), $data); 137 | } else { 138 | /** @var ActiveRecord $class */ 139 | $class = $this->elasticClass; 140 | if (!$class::updateAll($data, ['_id' => $this->getPK()])) { 141 | $this->insert($event, $data); 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * Deleting record from Elasticsearch index 148 | * @param Event $event 149 | */ 150 | public function delete(Event $event) 151 | { 152 | if ($this->mode == self::MODE_COMMAND) { 153 | $this->db()->createCommand() 154 | ->delete($this->elasticIndex, $this->elasticType, $this->getPK()); 155 | } else { 156 | /** @var ActiveRecord $class */ 157 | $class = $this->elasticClass; 158 | $class::deleteAll(['_id' => $this->getPK()]); 159 | } 160 | } 161 | 162 | /** 163 | * Retrieve owner's attribute values 164 | * @return array 165 | */ 166 | protected function getProcessedData() 167 | { 168 | $data = []; 169 | if(!$this->dataMap) { 170 | return $this->owner->attributes; 171 | } 172 | 173 | foreach ($this->dataMap as $elasticField => $attribute) { 174 | if (is_callable($attribute)) { 175 | $data[$elasticField] = call_user_func($attribute); 176 | if($data[$elasticField] instanceof Expression) { 177 | if(trim($data[$elasticField]->expression) == 'NOW()') { 178 | $data[$elasticField] = date("Y-m-d H:i:s"); 179 | } else { 180 | throw new InvalidParamException('Attribute value of "'.$elasticField.'" can not be instance of \yii\db\Expression'); 181 | } 182 | } elseif(!is_string($data[$elasticField])) { 183 | throw new InvalidParamException("Unknown value format for the attribute \"{$elasticField}\"!"); 184 | } 185 | } elseif(is_string($attribute)) { 186 | $data[$elasticField] = $this->owner->{$attribute}; 187 | } else { 188 | throw new InvalidParamException("Unknown value format for the attribute \"{$elasticField}\"!"); 189 | } 190 | } 191 | return $data; 192 | } 193 | 194 | /** 195 | * @return mixed 196 | */ 197 | protected function getPK() 198 | { 199 | /** @var \yii\db\ActiveRecord $owner */ 200 | $owner = $this->owner; 201 | return $owner->primaryKey; 202 | } 203 | 204 | /** 205 | * @return null|Connection 206 | * @throws \yii\base\InvalidConfigException 207 | */ 208 | protected function db() 209 | { 210 | return \Yii::$app->get($this->esComponent); 211 | } 212 | } --------------------------------------------------------------------------------