├── tests ├── data │ ├── delete.yml │ ├── update-delete.yml │ ├── update-add-and-delete.yml │ ├── update-add.yml │ ├── update-create.yml │ ├── create.yml │ └── initial.yml ├── models │ ├── User.php │ ├── TestUser.php │ └── Test.php ├── bootstrap.php ├── migrations │ └── schema-mysql.sql ├── DatabaseTestCase.php └── ManyToManyBehaviorTest.php ├── messages └── ru │ └── many-to-many.php ├── phpunit.xml.dist ├── .gitignore ├── composer.json ├── LICENSE.md ├── behaviors └── ManyToManyBehavior.php ├── validators └── ManyToManyValidator.php ├── components └── ManyToManyRelation.php ├── README.md └── composer.lock /tests/data/delete.yml: -------------------------------------------------------------------------------- 1 | tests_users: 2 | -------------------------------------------------------------------------------- /tests/data/update-delete.yml: -------------------------------------------------------------------------------- 1 | tests_users: 2 | - 3 | test_id: 1 4 | user_id: 1 5 | -------------------------------------------------------------------------------- /tests/data/update-add-and-delete.yml: -------------------------------------------------------------------------------- 1 | tests_users: 2 | - 3 | test_id: 1 4 | user_id: 1 5 | - 6 | test_id: 1 7 | user_id: 3 8 | -------------------------------------------------------------------------------- /tests/data/update-add.yml: -------------------------------------------------------------------------------- 1 | tests_users: 2 | - 3 | test_id: 1 4 | user_id: 1 5 | - 6 | test_id: 1 7 | user_id: 2 8 | - 9 | test_id: 1 10 | user_id: 3 11 | -------------------------------------------------------------------------------- /tests/data/update-create.yml: -------------------------------------------------------------------------------- 1 | tests_users: 2 | - 3 | test_id: 1 4 | user_id: 1 5 | - 6 | test_id: 1 7 | user_id: 2 8 | - 9 | test_id: 2 10 | user_id: 1 11 | - 12 | test_id: 2 13 | user_id: 2 14 | -------------------------------------------------------------------------------- /messages/ru/many-to-many.php: -------------------------------------------------------------------------------- 1 | 'Значение «{attribute}» должно быть списком.', 5 | 'There are nonexistent elements in {attribute} list.' => 'В списке «{attribute}» есть несуществующие элементы.', 6 | ]; 7 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/models/User.php: -------------------------------------------------------------------------------- 1 | 'unit', 11 | 'basePath' => __DIR__, 12 | 'aliases' => [ 13 | 'tests' => __DIR__, 14 | 'vendor' => __DIR__ . '/../vendor', 15 | ], 16 | 'components' => [ 17 | 'db' => include('config/db-local.php'), 18 | ], 19 | ]; 20 | 21 | new \yii\console\Application($config); 22 | -------------------------------------------------------------------------------- /tests/migrations/schema-mysql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `users` ( 2 | `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 | `name` varchar(255) NOT NULL 4 | ) ENGINE='InnoDB' COLLATE 'utf8_general_ci'; 5 | 6 | CREATE TABLE IF NOT EXISTS `tests` ( 7 | `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, 8 | `name` varchar(255) NOT NULL 9 | ) ENGINE='InnoDB' COLLATE 'utf8_general_ci'; 10 | 11 | CREATE TABLE IF NOT EXISTS `tests_users` ( 12 | `test_id` int(11) NOT NULL, 13 | `user_id` int(11) NOT NULL, 14 | PRIMARY KEY (`test_id`, `user_id`), 15 | FOREIGN KEY (`test_id`) REFERENCES `tests` (`id`) ON DELETE CASCADE, 16 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE 17 | ) ENGINE='InnoDB' COLLATE 'utf8_general_ci'; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer vendor dir 16 | /vendor 17 | # cubrid install dir 18 | /cubrid 19 | 20 | # composer itself is not needed 21 | composer.phar 22 | 23 | # composer.lock in applications is ignored since it's automatically created by composer when application is installed 24 | /apps/*/composer.lock 25 | 26 | # Mac DS_Store Files 27 | .DS_Store 28 | 29 | # phpunit itself is not needed 30 | phpunit.phar 31 | # local phpunit config 32 | /phpunit.xml 33 | 34 | # ignore sub directory for dev installed apps and extensions 35 | /apps 36 | /extensions 37 | 38 | # Local database config for tests 39 | /tests/config/db-local.php 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arogachev/yii2-many-to-many", 3 | "description": "Many-to-many ActiveRecord relation for Yii 2 framework", 4 | "keywords": [ 5 | "yii2", 6 | "many-to-many", 7 | "relation", 8 | "behavior", 9 | "validator" 10 | ], 11 | "homepage": "https://github.com/arogachev/yii2-many-to-many", 12 | "type": "yii2-extension", 13 | "license": "BSD-3-Clause", 14 | "authors": [ 15 | { 16 | "name": "Alexey Rogachev", 17 | "email": "arogachev90@gmail.com" 18 | } 19 | ], 20 | "require": { 21 | "yiisoft/yii2": "~2.0.15" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "~7.1", 25 | "phpunit/dbunit": "~4.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "arogachev\\ManyToMany\\": "" 30 | } 31 | }, 32 | "repositories": [ 33 | { 34 | "type": "composer", 35 | "url": "https://asset-packagist.org" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tests/DatabaseTestCase.php: -------------------------------------------------------------------------------- 1 | createDefaultDBConnection(Yii::$app->db->pdo); 17 | } 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public static function setUpBeforeClass() 23 | { 24 | Yii::$app->db->open(); 25 | $sql = file_get_contents(dirname(__FILE__) . '/migrations/schema-mysql.sql'); 26 | Yii::$app->db->createCommand($sql)->execute(); 27 | } 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | protected function getDataSet() 33 | { 34 | return $this->getYamlDataSet('initial'); 35 | } 36 | 37 | /** 38 | * Get data set from file in .yml format 39 | * @param string $name Data set name (file name without extension) 40 | * @return YamlDataSet 41 | */ 42 | protected function getYamlDataSet($name) 43 | { 44 | return new YamlDataSet(dirname(__FILE__) . "/data/$name.yml"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Yii 2 Many-to-many extension for Yii 2 framework is free software. 2 | It is released under the terms of the following BSD License. 3 | 4 | Copyright © 2015, Alexey Rogachev (https://github.com/arogachev) 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of test nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /behaviors/ManyToManyBehavior.php: -------------------------------------------------------------------------------- 1 | 'customInit', 30 | ActiveRecord::EVENT_AFTER_FIND => 'afterFind', 31 | ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert', 32 | ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate', 33 | ]; 34 | } 35 | 36 | public function customInit() 37 | { 38 | foreach ($this->relations as $config) { 39 | $config['model'] = $this->owner; 40 | $this->_relations[] = new ManyToManyRelation($config); 41 | } 42 | } 43 | 44 | public function afterFind() 45 | { 46 | foreach ($this->_relations as $relation) { 47 | $relation->autoFill(); 48 | } 49 | } 50 | 51 | public function afterInsert() 52 | { 53 | foreach ($this->_relations as $relation) { 54 | $relation->insert(); 55 | } 56 | } 57 | 58 | public function afterUpdate() 59 | { 60 | foreach ($this->_relations as $relation) { 61 | $relation->update(); 62 | } 63 | } 64 | 65 | /** 66 | * @return ManyToManyRelation[] 67 | */ 68 | public function getManyToManyRelations() 69 | { 70 | return $this->_relations; 71 | } 72 | 73 | /** 74 | * @param string $name 75 | * @return ManyToManyRelation|null 76 | */ 77 | public function getManyToManyRelation($name) 78 | { 79 | foreach ($this->_relations as $relation) { 80 | if ($relation->name == $name || $relation->table == $name) { 81 | return $relation; 82 | } 83 | } 84 | 85 | return null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/models/Test.php: -------------------------------------------------------------------------------- 1 | [ 35 | 'class' => ManyToManyBehavior::className(), 36 | 'relations' => [ 37 | array_merge( 38 | [ 39 | 'editableAttribute' => 'editableUsers', 40 | 'table' => 'tests_users', 41 | 'ownAttribute' => 'test_id', 42 | 'relatedModel' => User::className(), 43 | 'relatedAttribute' => 'user_id', 44 | ], 45 | self::$additionalUsersRelationConfig 46 | ), 47 | ], 48 | ], 49 | ]; 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public static function tableName() 56 | { 57 | return 'tests'; 58 | } 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | public function rules() 64 | { 65 | return [ 66 | ['editableUsers', ManyToManyValidator::className()], 67 | ]; 68 | } 69 | 70 | /** 71 | * @return \yii\db\ActiveQuery 72 | */ 73 | public function getUsersViaTable() 74 | { 75 | return $this->hasMany(User::className(), ['id' => 'user_id']) 76 | ->viaTable('tests_users', ['test_id' => 'id']); 77 | } 78 | 79 | /** 80 | * @return \yii\db\ActiveQuery 81 | */ 82 | public function getTestUsers() 83 | { 84 | return $this->hasMany(TestUser::className(), ['test_id' => 'id']); 85 | } 86 | 87 | /** 88 | * @return \yii\db\ActiveQuery 89 | */ 90 | public function getUsersViaRelation() 91 | { 92 | return $this->hasMany(User::className(), ['id' => 'user_id']) 93 | ->via('testUsers'); 94 | } 95 | 96 | /** 97 | * @inheritdoc 98 | */ 99 | public function afterFind() 100 | { 101 | parent::afterFind(); 102 | 103 | self::$additionalUsersRelationConfig = []; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /validators/ManyToManyValidator.php: -------------------------------------------------------------------------------- 1 | i18n->translations['many-to-many'] = [ 22 | 'class' => PhpMessageSource::className(), 23 | 'basePath' => '@many-to-many/messages', 24 | ]; 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function validateAttribute($model, $attribute) 31 | { 32 | $label = $model->getAttributeLabel($attribute); 33 | 34 | if (!is_array($model->$attribute)) { 35 | $model->addError($attribute, Yii::t('many-to-many', '{attribute} must be a list.', [ 36 | 'attribute' => $label, 37 | ])); 38 | 39 | return; 40 | } 41 | 42 | /* @var $behavior null|ManyToManyBehavior */ 43 | $behavior = null; 44 | 45 | foreach ($model->behaviors as $key => $attachedBehavior) { 46 | if ($attachedBehavior::className() == ManyToManyBehavior::className()) { 47 | $behavior = $attachedBehavior; 48 | 49 | break; 50 | } 51 | } 52 | 53 | if (!$behavior) { 54 | throw new InvalidConfigException("Behavior not detected."); 55 | } 56 | 57 | /* @var $relation null|\arogachev\ManyToMany\components\ManyToManyRelation */ 58 | $relation = null; 59 | 60 | foreach ($behavior->getManyToManyRelations() as $attachedRelation) { 61 | if ($attachedRelation->editableAttribute == $attribute) { 62 | $relation = $attachedRelation; 63 | 64 | break; 65 | } 66 | } 67 | 68 | if (!$relation) { 69 | throw new InvalidConfigException("Relation not detected."); 70 | } 71 | 72 | $primaryKeys = $model->$attribute; 73 | 74 | if (!$primaryKeys) { 75 | return; 76 | } 77 | 78 | /* @var $relatedModel \yii\db\ActiveRecord */ 79 | $relatedModel = $relation->relatedModel; 80 | $relatedModelPk = $relatedModel::primaryKey()[0]; 81 | $relatedModelsCount = $relatedModel::find()->where([$relatedModelPk => $primaryKeys])->count(); 82 | 83 | if (count($primaryKeys) != $relatedModelsCount) { 84 | $error = 'There are nonexistent elements in {attribute} list.'; 85 | $model->addError($attribute, Yii::t('many-to-many', $error, ['attribute' => $label])); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /components/ManyToManyRelation.php: -------------------------------------------------------------------------------- 1 | editableAttribute) { 70 | throw new InvalidConfigException('$editableAttribute is required.'); 71 | } 72 | 73 | if ($this->name) { 74 | $query = $this->getQuery(); 75 | 76 | if (is_array($query->via)) { 77 | // via 78 | /* @var $modelClass \yii\db\ActiveRecord */ 79 | $modelClass = $query->via[1]->modelClass; 80 | $this->table = $modelClass::tableName(); 81 | $this->ownAttribute = key($query->via[1]->link); 82 | } else { 83 | // viaTable 84 | $this->table = $query->via->from[0]; 85 | $this->ownAttribute = key($query->via->link); 86 | } 87 | 88 | $this->relatedModel = $query->modelClass; 89 | $this->relatedAttribute = reset($query->link); 90 | } else { 91 | if (!$this->table) { 92 | throw new InvalidConfigException('$table must be explicitly set in case of missing $name.'); 93 | } 94 | 95 | if (!$this->ownAttribute) { 96 | throw new InvalidConfigException('$ownAttribute must be explicitly set in case of missing $name.'); 97 | } 98 | 99 | if (!$this->relatedModel) { 100 | throw new InvalidConfigException('$relatedModel must be explicitly set in case of missing $name.'); 101 | } 102 | 103 | if (!$this->relatedAttribute) { 104 | throw new InvalidConfigException('$relatedAttribute must be explicitly set in case of missing $name.'); 105 | } 106 | } 107 | 108 | parent::init(); 109 | } 110 | 111 | /** 112 | * @throws \yii\db\Exception 113 | */ 114 | public function insert() 115 | { 116 | $primaryKeys = $this->getAddedPrimaryKeys(); 117 | if (!$primaryKeys) { 118 | return; 119 | } 120 | 121 | $rows = []; 122 | foreach ($primaryKeys as $primaryKey) { 123 | $rows[] = [$this->_model->primaryKey, $primaryKey]; 124 | } 125 | 126 | Yii::$app 127 | ->db 128 | ->createCommand() 129 | ->batchInsert($this->table, [$this->ownAttribute, $this->relatedAttribute], $rows) 130 | ->execute(); 131 | } 132 | 133 | public function update() 134 | { 135 | if (!$this->_filled) { 136 | return; 137 | } 138 | 139 | $this->delete(); 140 | $this->insert(); 141 | } 142 | 143 | /** 144 | * @throws \yii\db\Exception 145 | */ 146 | public function delete() 147 | { 148 | $primaryKeys = $this->getDeletedPrimaryKeys(); 149 | if (!$primaryKeys) { 150 | return; 151 | } 152 | 153 | Yii::$app->db->createCommand()->delete($this->table, [ 154 | $this->ownAttribute => $this->_model->primaryKey, 155 | $this->relatedAttribute => $primaryKeys, 156 | ])->execute(); 157 | } 158 | 159 | public function autoFill() 160 | { 161 | if (is_callable($this->autoFill) && !call_user_func($this->autoFill, $this->_model)) { 162 | return; 163 | } 164 | 165 | if (!$this->autoFill) { 166 | return; 167 | } 168 | 169 | $this->fill(); 170 | } 171 | 172 | public function fill() 173 | { 174 | $this->setEditableList($this->getRelatedList()); 175 | $this->_filled = true; 176 | } 177 | 178 | /** 179 | * @return array 180 | */ 181 | public function getAddedPrimaryKeys() 182 | { 183 | return array_values(array_diff($this->getEditableList(), $this->getRelatedList())); 184 | } 185 | 186 | /** 187 | * @return array 188 | */ 189 | public function getDeletedPrimaryKeys() 190 | { 191 | return array_values(array_diff($this->getRelatedList(), $this->getEditableList())); 192 | } 193 | 194 | /** 195 | * @param \yii\db\ActiveRecord $value 196 | */ 197 | public function setModel($value) 198 | { 199 | $this->_model = $value; 200 | } 201 | 202 | /** 203 | * @return array 204 | */ 205 | protected function getEditableList() 206 | { 207 | return $this->_model->{$this->editableAttribute} ?: []; 208 | } 209 | 210 | /** 211 | * @param array $value 212 | */ 213 | protected function setEditableList($value) 214 | { 215 | $this->_model->{$this->editableAttribute} = $value; 216 | } 217 | 218 | /** 219 | * @return array 220 | */ 221 | protected function getRelatedList() 222 | { 223 | if ($this->_relatedList) { 224 | return $this->_relatedList; 225 | } 226 | 227 | if ($this->name) { 228 | /* @var $relatedModel \yii\db\ActiveRecord */ 229 | $relatedModel = $this->relatedModel; 230 | $primaryKey = $relatedModel::primaryKey()[0]; 231 | 232 | $models = $this->_model->{$this->name}; 233 | $primaryKeys = ArrayHelper::getColumn($models, $primaryKey); 234 | } else { 235 | $rows = (new Query) 236 | ->from($this->table) 237 | ->select($this->relatedAttribute) 238 | ->where([$this->ownAttribute => $this->_model->primaryKey]) 239 | ->all(); 240 | 241 | $primaryKeys = ArrayHelper::getColumn($rows, $this->relatedAttribute); 242 | } 243 | 244 | $this->_relatedList = $primaryKeys; 245 | 246 | return $primaryKeys; 247 | } 248 | 249 | /** 250 | * @return null|\yii\db\ActiveQuery 251 | */ 252 | protected function getQuery() 253 | { 254 | if (!$this->name) { 255 | return null; 256 | } 257 | 258 | $methodName = 'get' . ucfirst($this->name); 259 | 260 | return $this->_model->$methodName(); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii 2 Many-to-many 2 | 3 | Implementation of [Many-to-many relationship](http://en.wikipedia.org/wiki/Many-to-many_%28data_model%29) 4 | for Yii 2 framework. 5 | 6 | [![Latest Stable Version](https://poser.pugx.org/arogachev/yii2-many-to-many/v/stable)](https://packagist.org/packages/arogachev/yii2-many-to-many) 7 | [![Total Downloads](https://poser.pugx.org/arogachev/yii2-many-to-many/downloads)](https://packagist.org/packages/arogachev/yii2-many-to-many) 8 | [![Latest Unstable Version](https://poser.pugx.org/arogachev/yii2-many-to-many/v/unstable)](https://packagist.org/packages/arogachev/yii2-many-to-many) 9 | [![License](https://poser.pugx.org/arogachev/yii2-many-to-many/license)](https://packagist.org/packages/arogachev/yii2-many-to-many) 10 | 11 | - [Installation](#installation) 12 | - [Features](#features) 13 | - [Creating editable attribute](#creating-editable-attribute) 14 | - [Attaching and configuring behavior](#attaching-and-configuring-behavior) 15 | - [Filling relations](#filling-relations) 16 | - [Saving relations without massive assignment](#saving-relations-without-massive-assignment) 17 | - [Adding attribute as safe](#adding-attribute-as-safe) 18 | - [Adding control to view](#adding-control-to-view) 19 | - [Relation features](#relation-features) 20 | - [Running tests](#running-tests) 21 | 22 | ## Installation 23 | 24 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 25 | 26 | Either run 27 | 28 | ``` 29 | php composer.phar require --prefer-dist arogachev/yii2-many-to-many 30 | ``` 31 | 32 | or add 33 | 34 | ``` 35 | "arogachev/yii2-many-to-many": "0.2.*" 36 | ``` 37 | 38 | to the require section of your `composer.json` file. 39 | 40 | ## Features 41 | 42 | - Configuring using existing ```hasMany``` relations 43 | - Multiple relations 44 | - No extra queries. For example, if initially model has 100 related records, 45 | after adding just one, exactly one row will be inserted. If nothing was changed, no queries will be executed. 46 | - Auto filling of editable attribute 47 | - Validator for checking if the received list is valid 48 | 49 | ## Creating editable attribute 50 | 51 | Simply add public property to your `ActiveRecord` model like this: 52 | 53 | ```php 54 | /** 55 | * @var array 56 | */ 57 | public $editableUsers = []; 58 | ``` 59 | 60 | It will store primary keys of related records during update. 61 | 62 | ## Attaching and configuring behavior 63 | 64 | First way is to explicitly specify all parameters: 65 | 66 | ```php 67 | use arogachev\ManyToMany\behaviors\ManyToManyBehavior; 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function behaviors() 73 | { 74 | return [ 75 | [ 76 | 'class' => ManyToManyBehavior::className(), 77 | 'relations' => [ 78 | [ 79 | 'editableAttribute' => 'editableUsers', // Editable attribute name 80 | 'table' => 'tests_to_users', // Name of the junction table 81 | 'ownAttribute' => 'test_id', // Name of the column in junction table that represents current model 82 | 'relatedModel' => User::className(), // Related model class 83 | 'relatedAttribute' => 'user_id', // Name of the column in junction table that represents related model 84 | ], 85 | ], 86 | ], 87 | ]; 88 | } 89 | ``` 90 | 91 | But more often we also need to display related models, 92 | so it's better to define relation for that and use it for both display and behavior configuration. 93 | Both ways (```via``` and ```viaTable```) are considered valid: 94 | 95 | Using ```viaTable```: 96 | 97 | ```php 98 | /** 99 | * @return \yii\db\ActiveQuery 100 | */ 101 | public function getUsers() 102 | { 103 | return $this->hasMany(User::className(), ['id' => 'user_id']) 104 | ->viaTable('tests_to_users', ['test_id' => 'id']) 105 | ->orderBy('name'); 106 | } 107 | ``` 108 | 109 | Using ```via``` (requires additional model for junction table): 110 | 111 | ```php 112 | /** 113 | * @return \yii\db\ActiveQuery 114 | */ 115 | public function getTestUsers() 116 | { 117 | return $this->hasMany(TestUser::className(), ['test_id' => 'id']); 118 | } 119 | 120 | /** 121 | * @return \yii\db\ActiveQuery 122 | */ 123 | public function getUsers() 124 | { 125 | return $this->hasMany(User::className(), ['id' => 'user_id']) 126 | ->via('testUsers') 127 | ->orderBy('name'); 128 | } 129 | ``` 130 | 131 | Order is not required. 132 | 133 | Then just pass the name of this relation and all other parameters will be fetched automatically. 134 | 135 | ```php 136 | /** 137 | * @inheritdoc 138 | */ 139 | public function behaviors() 140 | { 141 | return [ 142 | [ 143 | 'class' => ManyToManyBehavior::className(), 144 | 'relations' => [ 145 | [ 146 | 'name' => 'users', 147 | // This is the same as in previous example 148 | 'editableAttribute' => 'editableUsers', 149 | ], 150 | ], 151 | ], 152 | ]; 153 | } 154 | ``` 155 | 156 | Additional many-to-many relations can be added exactly the same. 157 | Note that even for one relation you should declare it as a part of `relations` section. 158 | 159 | ## Filling relations 160 | 161 | By default, `editableAttribute` of each found model will be populated with ids of related models (eager loading is 162 | used). If you want more manual control, prevent extra queries, disable `autoFill` option: 163 | 164 | ```php 165 | 'autoFill' => false, 166 | ``` 167 | 168 | and fill it only when it's needed, for example in `update` action of controller. This is recommended way of using. 169 | 170 | ```php 171 | public function actionUpdate($id) 172 | { 173 | $model = $this->findModel($id); 174 | $model->getManyToManyRelation('users')->fill(); 175 | // ... 176 | } 177 | ``` 178 | 179 | Alternatively you can specify conditions of filling in closure: 180 | 181 | ```php 182 | 'autoFill' => function ($model) { 183 | return $model->scenario == Test::SCENARIO_UPDATE; // boolean value 184 | } 185 | ``` 186 | 187 | Even it's possible to do something like this: 188 | 189 | ```php 190 | 'autoFill' => function ($model) { 191 | return Yii::$app->controller->route == 'tests/default/update'; 192 | } 193 | ``` 194 | 195 | but it's not recommended for usage because model is not appropriate place for handling routes. 196 | 197 | ## Saving relations without massive assignment 198 | 199 | When creating model: 200 | 201 | ```php 202 | $model = new Test; 203 | $model->editableUsers = [1, 2]; 204 | $model->save(); 205 | ``` 206 | 207 | When updating model (`'autoFill' => true`): 208 | 209 | ```php 210 | $model = new Test; 211 | $model->editableUsers = [1, 2]; 212 | $model->save(); 213 | ``` 214 | 215 | When updating model (`'autoFill' => false`, manual filling): 216 | 217 | ```php 218 | $model = new Test; 219 | $model->getManyToManyRelation('users')->fill(); 220 | var_dump($model->editableUsers) // [1, 2] 221 | $model->editableUsers = [1, 2, 3]; 222 | $model->save(); 223 | ``` 224 | 225 | When updating model (`'autoFill' => false`, without manual filling): 226 | 227 | ```php 228 | $model = new Test; 229 | var_dump($model->editableUsers) // empty array 230 | $model->save(); 231 | ``` 232 | 233 | In this case many-to-many relations will stay untouched. 234 | 235 | ## Adding attribute as safe 236 | 237 | Add editable attribute to model rules for massive assignment. 238 | 239 | Either mark it as safe at least: 240 | 241 | ```php 242 | public function rules() 243 | { 244 | ['editableUsers', 'safe'], 245 | } 246 | ``` 247 | 248 | Or use custom validator: 249 | 250 | ```php 251 | use arogachev\ManyToMany\validators\ManyToManyValidator; 252 | 253 | public function rules() 254 | { 255 | ['editableUsers', ManyToManyValidator::className()], 256 | } 257 | ``` 258 | 259 | Validator checks list for being array and containing only primary keys presented in related model. 260 | It can not be used without attaching `ManyToManyBehavior`. 261 | 262 | ## Adding control to view 263 | 264 | Add control to view for managing related list. Without extensions it can be done with multiple select: 265 | 266 | ```php 267 | field($model, 'editableUsers')->dropDownList(User::getList(), ['multiple' => true]) ?> 268 | ``` 269 | 270 | Example of `getList()` method contents (it needs to be placed in `User` model): 271 | 272 | ```php 273 | use yii\helpers\ArrayHelper; 274 | 275 | /** 276 | * @return array 277 | */ 278 | public static function getList() 279 | { 280 | $models = static::find()->orderBy('name')->all(); 281 | 282 | return ArrayHelper::map($models, 'id', 'name'); 283 | } 284 | ``` 285 | 286 | ## Relation features 287 | 288 | You can access many-to-many relation like so: 289 | 290 | ```php 291 | $relation = $model->getManyToManyRelation('users'); 292 | ``` 293 | 294 | `users` can be value of either `name` or `table` relation property specified in config. 295 | 296 | You can fill `editableAttribute` with ids of related records like so: 297 | 298 | ```php 299 | $model->getManyToManyRelation('users')->fill(); 300 | ``` 301 | 302 | You can get added and deleted primary keys of related models for specific relation like so: 303 | 304 | ```php 305 | $addedPrimaryKeys = $model->getManyToManyRelation('users')->getAddedPrimaryKeys(); 306 | $deletedPrimaryKeys = $model->getManyToManyRelation('users')->getDeletedPrimaryKeys(); 307 | ``` 308 | 309 | Note that they are only available after the model was saved so you can access it after `$model->save()` call 310 | or in `afterSave()` event handler. 311 | 312 | ## Running tests 313 | 314 | Install dependencies: 315 | 316 | ``` 317 | composer install 318 | ``` 319 | 320 | Add database config (`tests/config/db-local.php` file) with following contents: 321 | 322 | ```php 323 | 'yii\db\Connection', 327 | 'dsn' => 'mysql:host=localhost;dbname=yii2_many_to_many', 328 | 'username' => 'root', 329 | 'password' => '', 330 | ]; 331 | ``` 332 | 333 | You can change `dbname`, `username` and `password` how you want. Make sure create database and user before running 334 | tests. 335 | 336 | Run tests: 337 | 338 | ``` 339 | vendor/bin/phpunit 340 | ``` 341 | -------------------------------------------------------------------------------- /tests/ManyToManyBehaviorTest.php: -------------------------------------------------------------------------------- 1 | useRelationViaTable($test); 32 | $relation = $test->getManyToManyRelation('usersViaTable'); 33 | $this->assertRelationConfigsEqual($relation); 34 | 35 | $test = $this->useRelationViaRelation($test); 36 | $relation = $test->getManyToManyRelation('usersViaRelation'); 37 | $this->assertRelationConfigsEqual($relation); 38 | } 39 | 40 | /** 41 | * Validate test 42 | * @param Test|null $test 43 | */ 44 | public function testValidate($test = null) 45 | { 46 | $test = $test ? $test : $this->findTestModel(2); 47 | $test->editableUsers = '1, 2'; 48 | $this->assertEquals(false, $test->save()); 49 | $this->assertEquals(['Editable Users must be a list.'], $test->getErrors('editableUsers')); 50 | 51 | $test->editableUsers = [1, 10]; 52 | $this->assertEquals(false, $test->save()); 53 | $this->assertEquals( 54 | ['There are nonexistent elements in Editable Users list.'], 55 | $test->getErrors('editableUsers') 56 | ); 57 | } 58 | 59 | /** 60 | * Validate test using relation via table 61 | */ 62 | public function testValidateUsingRelationViaTable() 63 | { 64 | $test = $this->findTestModel(2); 65 | $test = $this->useRelationViaTable($test); 66 | $this->testValidate($test); 67 | } 68 | 69 | /** 70 | * Validate test using relation via relation 71 | */ 72 | public function testValidateUsingRelationViaRelation() 73 | { 74 | $test = $this->findTestModel(2); 75 | $test = $this->useRelationViaRelation($test); 76 | $this->testValidate($test); 77 | } 78 | 79 | /** 80 | * Create test 81 | * @param Test|null $test 82 | */ 83 | public function testCreate($test = null) 84 | { 85 | $test = $test ? $test : new Test; 86 | $test->name = 'Job Test'; 87 | $test->editableUsers = [1, 2]; 88 | $test->save(); 89 | $this->assertTestsEqual('create'); 90 | $this->assertTestsUsersEqual('create'); 91 | } 92 | 93 | /** 94 | * Create test using relation via table 95 | */ 96 | public function testCreateUsingRelationViaTable() 97 | { 98 | $test = new Test; 99 | $test = $this->useRelationViaTable($test); 100 | $this->testCreate($test); 101 | } 102 | 103 | /** 104 | * Create test using relation via relation 105 | */ 106 | public function testCreateUsingRelationViaRelation() 107 | { 108 | $test = new Test; 109 | $test = $this->useRelationViaRelation($test); 110 | $this->testCreate($test); 111 | } 112 | 113 | /** 114 | * Update not filled test 115 | * @param Test|null $test 116 | */ 117 | public function testUpdateNotFilled($test = null) 118 | { 119 | $test = $test ? $test : $this->findTestModel(1, ['autoFill' => false]); 120 | $test->editableUsers = []; 121 | $test->save(); 122 | $this->assertTestsUsersEqual('initial'); 123 | } 124 | 125 | /** 126 | * Update not filled test using relation via table 127 | */ 128 | public function testUpdateNotFilledUsingRelationViaTable() 129 | { 130 | $additionalConfig = ['autoFill' => false]; 131 | $test = $this->findTestModel(1, $additionalConfig); 132 | $test = $this->useRelationViaTable($test, $additionalConfig); 133 | $this->testUpdateNotFilled($test); 134 | } 135 | 136 | /** 137 | * Update not filled test using relation via relation 138 | */ 139 | public function testUpdateNotFilledUsingRelationViaRelation() 140 | { 141 | $additionalConfig = ['autoFill' => false]; 142 | $test = $this->findTestModel(1, $additionalConfig); 143 | $test = $this->useRelationViaRelation($test, $additionalConfig); 144 | $this->testUpdateNotFilled($test); 145 | } 146 | 147 | /** 148 | * Update (create) test 149 | * @param Test|null $test 150 | */ 151 | public function testUpdateCreate($test = null) 152 | { 153 | $test = $test ? $test : $this->findTestModel(2); 154 | $test->editableUsers = [1, 2]; 155 | $test->save(); 156 | $this->assertTestsUsersEqual('update-create'); 157 | } 158 | 159 | /** 160 | * Update (create) test using relation via table 161 | */ 162 | public function testUpdateCreateUsingRelationViaTable() 163 | { 164 | $test = $this->findTestModel(2); 165 | $test = $this->useRelationViaTable($test); 166 | $this->testUpdateCreate($test); 167 | } 168 | 169 | /** 170 | * Update (create) test using relation via relation 171 | */ 172 | public function testUpdateCreateUsingRelationViaRelation() 173 | { 174 | $test = $this->findTestModel(2); 175 | $test = $this->useRelationViaRelation($test); 176 | $this->testUpdateCreate($test); 177 | } 178 | 179 | /** 180 | * Update (add) test 181 | * @param Test|null $test 182 | */ 183 | public function testUpdateAdd($test = null) 184 | { 185 | $test = $test ? $test : $this->findTestModel(1); 186 | $test->editableUsers = [1, 2, 3]; 187 | $test->save(); 188 | 189 | $this->assertTestsUsersEqual('update-add'); 190 | } 191 | 192 | /** 193 | * Update (add) test using relation via table 194 | */ 195 | public function testUpdateAddUsingRelationViaTable() 196 | { 197 | $test = $this->findTestModel(1); 198 | $test = $this->useRelationViaTable($test); 199 | $this->testUpdateAdd($test); 200 | } 201 | 202 | /** 203 | * Update (add) test using relation via relation 204 | */ 205 | public function testUpdateAddUsingRelationViaRelation() 206 | { 207 | $test = $this->findTestModel(1); 208 | $test = $this->useRelationViaRelation($test); 209 | $this->testUpdateAdd($test); 210 | } 211 | 212 | /** 213 | * Update (delete) test 214 | * @param Test|null $test 215 | */ 216 | public function testUpdateDelete($test = null) 217 | { 218 | $test = $test ? $test : $this->findTestModel(1); 219 | $test->editableUsers = [1]; 220 | $test->save(); 221 | 222 | $this->assertTestsUsersEqual('update-delete'); 223 | } 224 | 225 | /** 226 | * Update (delete) test using relation via table 227 | */ 228 | public function testUpdateDeleteUsingRelationViaTable() 229 | { 230 | $test = $this->findTestModel(1); 231 | $test = $this->useRelationViaTable($test); 232 | $this->testUpdateDelete($test); 233 | } 234 | 235 | /** 236 | * Update (delete) test using relation via relation 237 | */ 238 | public function testUpdateDeleteUsingRelationViaRelation() 239 | { 240 | $test = $this->findTestModel(1); 241 | $test = $this->useRelationViaRelation($test); 242 | $this->testUpdateDelete($test); 243 | } 244 | 245 | /** 246 | * Update (add and delete) test 247 | * @param Test|null $test 248 | */ 249 | public function testUpdateAddAndDelete($test = null) 250 | { 251 | $test = $test ? $test : $this->findTestModel(1); 252 | $test->editableUsers = [1, 3]; 253 | $test->save(); 254 | 255 | $this->assertTestsUsersEqual('update-add-and-delete'); 256 | } 257 | 258 | /** 259 | * Update (add and delete) test using relation via table 260 | */ 261 | public function testUpdateAddAndDeleteUsingRelationViaTable() 262 | { 263 | $test = $this->findTestModel(1); 264 | $test = $this->useRelationViaTable($test); 265 | $this->testUpdateAddAndDelete($test); 266 | } 267 | 268 | /** 269 | * Update (add and delete) test using relation via relation 270 | */ 271 | public function testUpdateAddAndDeleteUsingRelationViaRelation() 272 | { 273 | $test = $this->findTestModel(1); 274 | $test = $this->useRelationViaRelation($test); 275 | $this->testUpdateAddAndDelete($test); 276 | } 277 | 278 | /** 279 | * Delete test 280 | * @param Test|null $test 281 | */ 282 | public function testDelete($test = null) 283 | { 284 | $test = $test ? $test : $this->findTestModel(1); 285 | $test->editableUsers = []; 286 | $test->save(); 287 | 288 | $this->assertTestsUsersEqual('delete'); 289 | } 290 | 291 | /** 292 | * Delete test using relation via table 293 | */ 294 | public function testDeleteUsingRelationViaTable() 295 | { 296 | $test = $this->findTestModel(1); 297 | $test = $this->useRelationViaTable($test); 298 | $this->testDelete($test); 299 | } 300 | 301 | /** 302 | * Delete test using relation via relation 303 | */ 304 | public function testDeleteUsingRelationViaRelation() 305 | { 306 | $test = $this->findTestModel(1); 307 | $test = $this->useRelationViaRelation($test); 308 | $this->testDelete($test); 309 | } 310 | 311 | /** 312 | * Auto fill test 313 | * @param Test|null $test 314 | */ 315 | public function testAutoFill($test = null) 316 | { 317 | $test = $test ? $test : $this->findTestModel(1); 318 | $this->assertEquals([1, 2], $test->editableUsers); 319 | 320 | $test = $this->findTestModel(1, ['autoFill' => false]); 321 | $this->assertEquals([], $test->editableUsers); 322 | 323 | $test = $this->findTestModel(1, ['autoFill' => function ($model) { 324 | return true; 325 | }]); 326 | $this->assertEquals([1, 2], $test->editableUsers); 327 | } 328 | 329 | /** 330 | * Auto fill test using relation via table 331 | */ 332 | public function testAutoFillUsingRelationViaTable() 333 | { 334 | $test = $this->findTestModel(1); 335 | $test = $this->useRelationViaTable($test); 336 | $this->testAutoFill($test); 337 | } 338 | 339 | /** 340 | * Auto fill test using relation via relation 341 | */ 342 | public function testAutoFillUsingRelationViaRelation() 343 | { 344 | $test = $this->findTestModel(1); 345 | $test = $this->useRelationViaRelation($test); 346 | $this->testAutoFill($test); 347 | } 348 | 349 | /** 350 | * Fill test 351 | * @param Test|null $test 352 | */ 353 | public function testFill($test = null) 354 | { 355 | $test = $test ? $test : $this->findTestModel(1, ['autoFill' => false]); 356 | $this->assertEquals([], $test->editableUsers); 357 | 358 | $test->getManyToManyRelation('tests_users')->fill(); 359 | $this->assertEquals([1, 2], $test->editableUsers); 360 | } 361 | 362 | /** 363 | * Fill test using relation via table 364 | */ 365 | public function testFillUsingRelationViaTable() 366 | { 367 | $additionalConfig = ['autoFill' => false]; 368 | $test = $this->findTestModel(1, $additionalConfig); 369 | $test = $this->useRelationViaTable($test, $additionalConfig); 370 | $this->testFill($test); 371 | } 372 | 373 | /** 374 | * Fill test using relation via relation 375 | */ 376 | public function testFillUsingRelationViaRelation() 377 | { 378 | $additionalConfig = ['autoFill' => false]; 379 | $test = $this->findTestModel(1, $additionalConfig); 380 | $test = $this->useRelationViaRelation($test, $additionalConfig); 381 | $this->testFill($test); 382 | } 383 | 384 | /** 385 | * Test added and deleted primary keys 386 | * @param Test|null $test 387 | */ 388 | public function testPrimaryKeysDiff($test = null) 389 | { 390 | $test = $test ? $test : $this->findTestModel(1); 391 | $test->editableUsers = [1, 3]; 392 | $test->save(); 393 | 394 | $this->assertEquals([3], $test->getManyToManyRelation('tests_users')->getAddedPrimaryKeys()); 395 | $this->assertEquals([2], $test->getManyToManyRelation('tests_users')->getDeletedPrimaryKeys()); 396 | } 397 | 398 | /** 399 | * Test added and deleted primary keys using relation via table 400 | */ 401 | public function testPrimaryKeysDiffUsingRelationViaTable() 402 | { 403 | $test = $this->findTestModel(1); 404 | $test = $this->useRelationViaTable($test); 405 | $this->testPrimaryKeysDiff($test); 406 | } 407 | 408 | /** 409 | * Test added and deleted primary keys using relation via relation 410 | */ 411 | public function testPrimaryKeysDiffUsingRelationViaRelation() 412 | { 413 | $test = $this->findTestModel(1); 414 | $test = $this->useRelationViaRelation($test); 415 | $this->testPrimaryKeysDiff($test); 416 | } 417 | 418 | /** 419 | * Find test model by id and optionally change users relation config 420 | * @param integer $id 421 | * @param array $additionalUsersRelationConfig 422 | * @return Test|\arogachev\ManyToMany\behaviors\ManyToManyBehavior 423 | */ 424 | protected function findTestModel($id, $additionalUsersRelationConfig = []) 425 | { 426 | Test::$additionalUsersRelationConfig = $additionalUsersRelationConfig; 427 | 428 | return Test::findOne($id); 429 | } 430 | 431 | /** 432 | * Use alternative config - relation via table 433 | * @param Test|\arogachev\ManyToMany\behaviors\ManyToManyBehavior $test 434 | * @param array $additionalUsersRelationConfig 435 | * @return Test|\arogachev\ManyToMany\behaviors\ManyToManyBehavior 436 | */ 437 | protected function useRelationViaTable($test, $additionalUsersRelationConfig = []) 438 | { 439 | $test->attachBehavior('manyToMany', [ 440 | 'class' => ManyToManyBehavior::className(), 441 | 'relations' => [ 442 | array_merge( 443 | [ 444 | 'name' => 'usersViaTable', 445 | 'editableAttribute' => 'editableUsers', 446 | ], 447 | $additionalUsersRelationConfig 448 | ), 449 | ] 450 | ]); 451 | $test->customInit(); 452 | if (!$test->isNewRecord) { 453 | $test->afterFind(); 454 | } 455 | 456 | return $test; 457 | } 458 | 459 | /** 460 | * Use alternative config - relation via relation 461 | * @param Test|\arogachev\ManyToMany\behaviors\ManyToManyBehavior $test 462 | * @param array $additionalUsersRelationConfig 463 | * @return Test|\arogachev\ManyToMany\behaviors\ManyToManyBehavior 464 | */ 465 | protected function useRelationViaRelation($test, $additionalUsersRelationConfig = []) 466 | { 467 | $test->attachBehavior('manyToMany', [ 468 | 'class' => ManyToManyBehavior::className(), 469 | 'relations' => [ 470 | array_merge( 471 | [ 472 | 'name' => 'usersViaRelation', 473 | 'editableAttribute' => 'editableUsers', 474 | ], 475 | $additionalUsersRelationConfig 476 | ), 477 | ] 478 | ]); 479 | $test->customInit(); 480 | if (!$test->isNewRecord) { 481 | $test->afterFind(); 482 | } 483 | 484 | return $test; 485 | } 486 | 487 | /** 488 | * Check if relation configs are equal 489 | * @param \arogachev\ManyToMany\components\ManyToManyRelation $relation 490 | */ 491 | protected function assertRelationConfigsEqual($relation) 492 | { 493 | $this->assertEquals($relation->table, 'tests_users'); 494 | $this->assertEquals($relation->ownAttribute, 'test_id'); 495 | $this->assertEquals($relation->relatedModel, User::className()); 496 | $this->assertEquals($relation->relatedAttribute, 'user_id'); 497 | } 498 | 499 | /** 500 | * Check if tests tables are equal 501 | * @param string $dataSetName 502 | */ 503 | protected function assertTestsEqual($dataSetName) 504 | { 505 | $dataSet = $this->getYamlDataSet($dataSetName); 506 | $testsTable = $this->getConnection()->createQueryTable( 507 | 'tests', 508 | 'SELECT * FROM `tests` ORDER BY `id`' 509 | ); 510 | $this->assertTablesEqual($dataSet->getTable('tests'), $testsTable); 511 | } 512 | 513 | /** 514 | * Check if tests-users many-to-many tables are equal 515 | * @param string $dataSetName 516 | */ 517 | protected function assertTestsUsersEqual($dataSetName) 518 | { 519 | $dataSet = $this->getYamlDataSet($dataSetName); 520 | $testsUsersTable = $this->getConnection()->createQueryTable( 521 | 'tests_users', 522 | 'SELECT * FROM `tests_users` ORDER BY `test_id`, `user_id`' 523 | ); 524 | $this->assertTablesEqual($dataSet->getTable('tests_users'), $testsUsersTable); 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "bb2603aaa635c11a9330a6f8d3ae7f88", 8 | "packages": [ 9 | { 10 | "name": "bower-asset/inputmask", 11 | "version": "3.3.11", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/RobinHerbots/Inputmask.git", 15 | "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b", 20 | "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b", 21 | "shasum": null 22 | }, 23 | "require": { 24 | "bower-asset/jquery": ">=1.7" 25 | }, 26 | "type": "bower-asset", 27 | "license": [ 28 | "http://opensource.org/licenses/mit-license.php" 29 | ] 30 | }, 31 | { 32 | "name": "bower-asset/jquery", 33 | "version": "2.2.1", 34 | "source": { 35 | "type": "git", 36 | "url": "https://github.com/jquery/jquery-dist.git", 37 | "reference": "788eaba2f83e7b7445c7a83a50c81c0704423874" 38 | }, 39 | "dist": { 40 | "type": "zip", 41 | "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/788eaba2f83e7b7445c7a83a50c81c0704423874", 42 | "reference": "788eaba2f83e7b7445c7a83a50c81c0704423874", 43 | "shasum": "" 44 | }, 45 | "type": "bower-asset-library", 46 | "extra": { 47 | "bower-asset-main": "dist/jquery.js", 48 | "bower-asset-ignore": [ 49 | "package.json" 50 | ] 51 | }, 52 | "license": [ 53 | "MIT" 54 | ], 55 | "keywords": [ 56 | "browser", 57 | "javascript", 58 | "jquery", 59 | "library" 60 | ] 61 | }, 62 | { 63 | "name": "bower-asset/punycode", 64 | "version": "v1.3.2", 65 | "source": { 66 | "type": "git", 67 | "url": "https://github.com/bestiejs/punycode.js.git", 68 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" 69 | }, 70 | "dist": { 71 | "type": "zip", 72 | "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", 73 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", 74 | "shasum": "" 75 | }, 76 | "type": "bower-asset-library", 77 | "extra": { 78 | "bower-asset-main": "punycode.js", 79 | "bower-asset-ignore": [ 80 | "coverage", 81 | "tests", 82 | ".*", 83 | "component.json", 84 | "Gruntfile.js", 85 | "node_modules", 86 | "package.json" 87 | ] 88 | } 89 | }, 90 | { 91 | "name": "bower-asset/yii2-pjax", 92 | "version": "v2.0.5", 93 | "source": { 94 | "type": "git", 95 | "url": "git@github.com:yiisoft/jquery-pjax.git", 96 | "reference": "6818718408086db6bdcf33649cecb86b6b4f9b67" 97 | }, 98 | "dist": { 99 | "type": "zip", 100 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/6818718408086db6bdcf33649cecb86b6b4f9b67", 101 | "reference": "6818718408086db6bdcf33649cecb86b6b4f9b67", 102 | "shasum": "" 103 | }, 104 | "require": { 105 | "bower-asset/jquery": ">=1.8" 106 | }, 107 | "type": "bower-asset-library", 108 | "extra": { 109 | "bower-asset-main": "./jquery.pjax.js", 110 | "bower-asset-ignore": [ 111 | ".travis.yml", 112 | "Gemfile", 113 | "Gemfile.lock", 114 | "vendor/", 115 | "script/", 116 | "test/" 117 | ] 118 | }, 119 | "license": [ 120 | "MIT" 121 | ] 122 | }, 123 | { 124 | "name": "cebe/markdown", 125 | "version": "1.1.0", 126 | "source": { 127 | "type": "git", 128 | "url": "https://github.com/cebe/markdown.git", 129 | "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2" 130 | }, 131 | "dist": { 132 | "type": "zip", 133 | "url": "https://api.github.com/repos/cebe/markdown/zipball/54a2c49de31cc44e864ebf0500a35ef21d0010b2", 134 | "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2", 135 | "shasum": "" 136 | }, 137 | "require": { 138 | "lib-pcre": "*", 139 | "php": ">=5.4.0" 140 | }, 141 | "require-dev": { 142 | "cebe/indent": "*", 143 | "facebook/xhprof": "*@dev", 144 | "phpunit/phpunit": "4.1.*" 145 | }, 146 | "bin": [ 147 | "bin/markdown" 148 | ], 149 | "type": "library", 150 | "extra": { 151 | "branch-alias": { 152 | "dev-master": "1.1.x-dev" 153 | } 154 | }, 155 | "autoload": { 156 | "psr-4": { 157 | "cebe\\markdown\\": "" 158 | } 159 | }, 160 | "notification-url": "https://packagist.org/downloads/", 161 | "license": [ 162 | "MIT" 163 | ], 164 | "authors": [ 165 | { 166 | "name": "Carsten Brandt", 167 | "email": "mail@cebe.cc", 168 | "homepage": "http://cebe.cc/", 169 | "role": "Creator" 170 | } 171 | ], 172 | "description": "A super fast, highly extensible markdown parser for PHP", 173 | "homepage": "https://github.com/cebe/markdown#readme", 174 | "keywords": [ 175 | "extensible", 176 | "fast", 177 | "gfm", 178 | "markdown", 179 | "markdown-extra" 180 | ], 181 | "time": "2015-03-06T05:28:07+00:00" 182 | }, 183 | { 184 | "name": "ezyang/htmlpurifier", 185 | "version": "v4.6.0", 186 | "source": { 187 | "type": "git", 188 | "url": "https://github.com/ezyang/htmlpurifier.git", 189 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" 190 | }, 191 | "dist": { 192 | "type": "zip", 193 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", 194 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", 195 | "shasum": "" 196 | }, 197 | "require": { 198 | "php": ">=5.2" 199 | }, 200 | "type": "library", 201 | "autoload": { 202 | "psr-0": { 203 | "HTMLPurifier": "library/" 204 | }, 205 | "files": [ 206 | "library/HTMLPurifier.composer.php" 207 | ] 208 | }, 209 | "notification-url": "https://packagist.org/downloads/", 210 | "license": [ 211 | "LGPL" 212 | ], 213 | "authors": [ 214 | { 215 | "name": "Edward Z. Yang", 216 | "email": "admin@htmlpurifier.org", 217 | "homepage": "http://ezyang.com" 218 | } 219 | ], 220 | "description": "Standards compliant HTML filter written in PHP", 221 | "homepage": "http://htmlpurifier.org/", 222 | "keywords": [ 223 | "html" 224 | ], 225 | "time": "2013-11-30T08:25:19+00:00" 226 | }, 227 | { 228 | "name": "yiisoft/yii2", 229 | "version": "2.0.15.1", 230 | "source": { 231 | "type": "git", 232 | "url": "https://github.com/yiisoft/yii2-framework.git", 233 | "reference": "ed3a9e1c4abe206e1c3ce48a6b3624119b79850d" 234 | }, 235 | "dist": { 236 | "type": "zip", 237 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/ed3a9e1c4abe206e1c3ce48a6b3624119b79850d", 238 | "reference": "ed3a9e1c4abe206e1c3ce48a6b3624119b79850d", 239 | "shasum": "" 240 | }, 241 | "require": { 242 | "bower-asset/inputmask": "~3.2.2 | ~3.3.5", 243 | "bower-asset/jquery": "3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", 244 | "bower-asset/punycode": "1.3.*", 245 | "bower-asset/yii2-pjax": "~2.0.1", 246 | "cebe/markdown": "~1.0.0 | ~1.1.0", 247 | "ext-ctype": "*", 248 | "ext-mbstring": "*", 249 | "ezyang/htmlpurifier": "~4.6", 250 | "lib-pcre": "*", 251 | "php": ">=5.4.0", 252 | "yiisoft/yii2-composer": "~2.0.4" 253 | }, 254 | "bin": [ 255 | "yii" 256 | ], 257 | "type": "library", 258 | "extra": { 259 | "branch-alias": { 260 | "dev-master": "2.0.x-dev" 261 | } 262 | }, 263 | "autoload": { 264 | "psr-4": { 265 | "yii\\": "" 266 | } 267 | }, 268 | "notification-url": "https://packagist.org/downloads/", 269 | "license": [ 270 | "BSD-3-Clause" 271 | ], 272 | "authors": [ 273 | { 274 | "name": "Qiang Xue", 275 | "email": "qiang.xue@gmail.com", 276 | "homepage": "http://www.yiiframework.com/", 277 | "role": "Founder and project lead" 278 | }, 279 | { 280 | "name": "Alexander Makarov", 281 | "email": "sam@rmcreative.ru", 282 | "homepage": "http://rmcreative.ru/", 283 | "role": "Core framework development" 284 | }, 285 | { 286 | "name": "Maurizio Domba", 287 | "homepage": "http://mdomba.info/", 288 | "role": "Core framework development" 289 | }, 290 | { 291 | "name": "Carsten Brandt", 292 | "email": "mail@cebe.cc", 293 | "homepage": "http://cebe.cc/", 294 | "role": "Core framework development" 295 | }, 296 | { 297 | "name": "Timur Ruziev", 298 | "email": "resurtm@gmail.com", 299 | "homepage": "http://resurtm.com/", 300 | "role": "Core framework development" 301 | }, 302 | { 303 | "name": "Paul Klimov", 304 | "email": "klimov.paul@gmail.com", 305 | "role": "Core framework development" 306 | }, 307 | { 308 | "name": "Dmitry Naumenko", 309 | "email": "d.naumenko.a@gmail.com", 310 | "role": "Core framework development" 311 | }, 312 | { 313 | "name": "Boudewijn Vahrmeijer", 314 | "email": "info@dynasource.eu", 315 | "homepage": "http://dynasource.eu", 316 | "role": "Core framework development" 317 | } 318 | ], 319 | "description": "Yii PHP Framework Version 2", 320 | "homepage": "http://www.yiiframework.com/", 321 | "keywords": [ 322 | "framework", 323 | "yii2" 324 | ], 325 | "time": "2018-03-21T18:36:53+00:00" 326 | }, 327 | { 328 | "name": "yiisoft/yii2-composer", 329 | "version": "2.0.4", 330 | "source": { 331 | "type": "git", 332 | "url": "https://github.com/yiisoft/yii2-composer.git", 333 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" 334 | }, 335 | "dist": { 336 | "type": "zip", 337 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", 338 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", 339 | "shasum": "" 340 | }, 341 | "require": { 342 | "composer-plugin-api": "^1.0" 343 | }, 344 | "type": "composer-plugin", 345 | "extra": { 346 | "class": "yii\\composer\\Plugin", 347 | "branch-alias": { 348 | "dev-master": "2.0.x-dev" 349 | } 350 | }, 351 | "autoload": { 352 | "psr-4": { 353 | "yii\\composer\\": "" 354 | } 355 | }, 356 | "notification-url": "https://packagist.org/downloads/", 357 | "license": [ 358 | "BSD-3-Clause" 359 | ], 360 | "authors": [ 361 | { 362 | "name": "Qiang Xue", 363 | "email": "qiang.xue@gmail.com" 364 | } 365 | ], 366 | "description": "The composer plugin for Yii extension installer", 367 | "keywords": [ 368 | "composer", 369 | "extension installer", 370 | "yii2" 371 | ], 372 | "time": "2016-02-06T00:49:24+00:00" 373 | } 374 | ], 375 | "packages-dev": [ 376 | { 377 | "name": "bower-asset/jquery.inputmask", 378 | "version": "3.2.7", 379 | "source": { 380 | "type": "git", 381 | "url": "git@github.com:RobinHerbots/jquery.inputmask.git", 382 | "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" 383 | }, 384 | "dist": { 385 | "type": "zip", 386 | "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", 387 | "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", 388 | "shasum": "" 389 | }, 390 | "require": { 391 | "bower-asset/jquery": ">=1.7" 392 | }, 393 | "type": "bower-asset-library", 394 | "extra": { 395 | "bower-asset-main": [ 396 | "./dist/inputmask/inputmask.js" 397 | ], 398 | "bower-asset-ignore": [ 399 | "**/*", 400 | "!dist/*", 401 | "!dist/inputmask/*", 402 | "!dist/min/*", 403 | "!dist/min/inputmask/*", 404 | "!extra/bindings/*", 405 | "!extra/dependencyLibs/*", 406 | "!extra/phone-codes/*" 407 | ] 408 | }, 409 | "license": [ 410 | "http://opensource.org/licenses/mit-license.php" 411 | ], 412 | "description": "jquery.inputmask is a jquery plugin which create an input mask.", 413 | "keywords": [ 414 | "form", 415 | "input", 416 | "inputmask", 417 | "jquery", 418 | "mask", 419 | "plugins" 420 | ] 421 | }, 422 | { 423 | "name": "doctrine/instantiator", 424 | "version": "1.1.0", 425 | "source": { 426 | "type": "git", 427 | "url": "https://github.com/doctrine/instantiator.git", 428 | "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" 429 | }, 430 | "dist": { 431 | "type": "zip", 432 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", 433 | "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", 434 | "shasum": "" 435 | }, 436 | "require": { 437 | "php": "^7.1" 438 | }, 439 | "require-dev": { 440 | "athletic/athletic": "~0.1.8", 441 | "ext-pdo": "*", 442 | "ext-phar": "*", 443 | "phpunit/phpunit": "^6.2.3", 444 | "squizlabs/php_codesniffer": "^3.0.2" 445 | }, 446 | "type": "library", 447 | "extra": { 448 | "branch-alias": { 449 | "dev-master": "1.2.x-dev" 450 | } 451 | }, 452 | "autoload": { 453 | "psr-4": { 454 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 455 | } 456 | }, 457 | "notification-url": "https://packagist.org/downloads/", 458 | "license": [ 459 | "MIT" 460 | ], 461 | "authors": [ 462 | { 463 | "name": "Marco Pivetta", 464 | "email": "ocramius@gmail.com", 465 | "homepage": "http://ocramius.github.com/" 466 | } 467 | ], 468 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 469 | "homepage": "https://github.com/doctrine/instantiator", 470 | "keywords": [ 471 | "constructor", 472 | "instantiate" 473 | ], 474 | "time": "2017-07-22T11:58:36+00:00" 475 | }, 476 | { 477 | "name": "myclabs/deep-copy", 478 | "version": "1.7.0", 479 | "source": { 480 | "type": "git", 481 | "url": "https://github.com/myclabs/DeepCopy.git", 482 | "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" 483 | }, 484 | "dist": { 485 | "type": "zip", 486 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", 487 | "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", 488 | "shasum": "" 489 | }, 490 | "require": { 491 | "php": "^5.6 || ^7.0" 492 | }, 493 | "require-dev": { 494 | "doctrine/collections": "^1.0", 495 | "doctrine/common": "^2.6", 496 | "phpunit/phpunit": "^4.1" 497 | }, 498 | "type": "library", 499 | "autoload": { 500 | "psr-4": { 501 | "DeepCopy\\": "src/DeepCopy/" 502 | }, 503 | "files": [ 504 | "src/DeepCopy/deep_copy.php" 505 | ] 506 | }, 507 | "notification-url": "https://packagist.org/downloads/", 508 | "license": [ 509 | "MIT" 510 | ], 511 | "description": "Create deep copies (clones) of your objects", 512 | "keywords": [ 513 | "clone", 514 | "copy", 515 | "duplicate", 516 | "object", 517 | "object graph" 518 | ], 519 | "time": "2017-10-19T19:58:43+00:00" 520 | }, 521 | { 522 | "name": "phar-io/manifest", 523 | "version": "1.0.1", 524 | "source": { 525 | "type": "git", 526 | "url": "https://github.com/phar-io/manifest.git", 527 | "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" 528 | }, 529 | "dist": { 530 | "type": "zip", 531 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", 532 | "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", 533 | "shasum": "" 534 | }, 535 | "require": { 536 | "ext-dom": "*", 537 | "ext-phar": "*", 538 | "phar-io/version": "^1.0.1", 539 | "php": "^5.6 || ^7.0" 540 | }, 541 | "type": "library", 542 | "extra": { 543 | "branch-alias": { 544 | "dev-master": "1.0.x-dev" 545 | } 546 | }, 547 | "autoload": { 548 | "classmap": [ 549 | "src/" 550 | ] 551 | }, 552 | "notification-url": "https://packagist.org/downloads/", 553 | "license": [ 554 | "BSD-3-Clause" 555 | ], 556 | "authors": [ 557 | { 558 | "name": "Arne Blankerts", 559 | "email": "arne@blankerts.de", 560 | "role": "Developer" 561 | }, 562 | { 563 | "name": "Sebastian Heuer", 564 | "email": "sebastian@phpeople.de", 565 | "role": "Developer" 566 | }, 567 | { 568 | "name": "Sebastian Bergmann", 569 | "email": "sebastian@phpunit.de", 570 | "role": "Developer" 571 | } 572 | ], 573 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 574 | "time": "2017-03-05T18:14:27+00:00" 575 | }, 576 | { 577 | "name": "phar-io/version", 578 | "version": "1.0.1", 579 | "source": { 580 | "type": "git", 581 | "url": "https://github.com/phar-io/version.git", 582 | "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" 583 | }, 584 | "dist": { 585 | "type": "zip", 586 | "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", 587 | "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", 588 | "shasum": "" 589 | }, 590 | "require": { 591 | "php": "^5.6 || ^7.0" 592 | }, 593 | "type": "library", 594 | "autoload": { 595 | "classmap": [ 596 | "src/" 597 | ] 598 | }, 599 | "notification-url": "https://packagist.org/downloads/", 600 | "license": [ 601 | "BSD-3-Clause" 602 | ], 603 | "authors": [ 604 | { 605 | "name": "Arne Blankerts", 606 | "email": "arne@blankerts.de", 607 | "role": "Developer" 608 | }, 609 | { 610 | "name": "Sebastian Heuer", 611 | "email": "sebastian@phpeople.de", 612 | "role": "Developer" 613 | }, 614 | { 615 | "name": "Sebastian Bergmann", 616 | "email": "sebastian@phpunit.de", 617 | "role": "Developer" 618 | } 619 | ], 620 | "description": "Library for handling version information and constraints", 621 | "time": "2017-03-05T17:38:23+00:00" 622 | }, 623 | { 624 | "name": "phpdocumentor/reflection-common", 625 | "version": "1.0.1", 626 | "source": { 627 | "type": "git", 628 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 629 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" 630 | }, 631 | "dist": { 632 | "type": "zip", 633 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 634 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 635 | "shasum": "" 636 | }, 637 | "require": { 638 | "php": ">=5.5" 639 | }, 640 | "require-dev": { 641 | "phpunit/phpunit": "^4.6" 642 | }, 643 | "type": "library", 644 | "extra": { 645 | "branch-alias": { 646 | "dev-master": "1.0.x-dev" 647 | } 648 | }, 649 | "autoload": { 650 | "psr-4": { 651 | "phpDocumentor\\Reflection\\": [ 652 | "src" 653 | ] 654 | } 655 | }, 656 | "notification-url": "https://packagist.org/downloads/", 657 | "license": [ 658 | "MIT" 659 | ], 660 | "authors": [ 661 | { 662 | "name": "Jaap van Otterdijk", 663 | "email": "opensource@ijaap.nl" 664 | } 665 | ], 666 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 667 | "homepage": "http://www.phpdoc.org", 668 | "keywords": [ 669 | "FQSEN", 670 | "phpDocumentor", 671 | "phpdoc", 672 | "reflection", 673 | "static analysis" 674 | ], 675 | "time": "2017-09-11T18:02:19+00:00" 676 | }, 677 | { 678 | "name": "phpdocumentor/reflection-docblock", 679 | "version": "4.3.0", 680 | "source": { 681 | "type": "git", 682 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 683 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08" 684 | }, 685 | "dist": { 686 | "type": "zip", 687 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", 688 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08", 689 | "shasum": "" 690 | }, 691 | "require": { 692 | "php": "^7.0", 693 | "phpdocumentor/reflection-common": "^1.0.0", 694 | "phpdocumentor/type-resolver": "^0.4.0", 695 | "webmozart/assert": "^1.0" 696 | }, 697 | "require-dev": { 698 | "doctrine/instantiator": "~1.0.5", 699 | "mockery/mockery": "^1.0", 700 | "phpunit/phpunit": "^6.4" 701 | }, 702 | "type": "library", 703 | "extra": { 704 | "branch-alias": { 705 | "dev-master": "4.x-dev" 706 | } 707 | }, 708 | "autoload": { 709 | "psr-4": { 710 | "phpDocumentor\\Reflection\\": [ 711 | "src/" 712 | ] 713 | } 714 | }, 715 | "notification-url": "https://packagist.org/downloads/", 716 | "license": [ 717 | "MIT" 718 | ], 719 | "authors": [ 720 | { 721 | "name": "Mike van Riel", 722 | "email": "me@mikevanriel.com" 723 | } 724 | ], 725 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 726 | "time": "2017-11-30T07:14:17+00:00" 727 | }, 728 | { 729 | "name": "phpdocumentor/type-resolver", 730 | "version": "0.4.0", 731 | "source": { 732 | "type": "git", 733 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 734 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 735 | }, 736 | "dist": { 737 | "type": "zip", 738 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 739 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 740 | "shasum": "" 741 | }, 742 | "require": { 743 | "php": "^5.5 || ^7.0", 744 | "phpdocumentor/reflection-common": "^1.0" 745 | }, 746 | "require-dev": { 747 | "mockery/mockery": "^0.9.4", 748 | "phpunit/phpunit": "^5.2||^4.8.24" 749 | }, 750 | "type": "library", 751 | "extra": { 752 | "branch-alias": { 753 | "dev-master": "1.0.x-dev" 754 | } 755 | }, 756 | "autoload": { 757 | "psr-4": { 758 | "phpDocumentor\\Reflection\\": [ 759 | "src/" 760 | ] 761 | } 762 | }, 763 | "notification-url": "https://packagist.org/downloads/", 764 | "license": [ 765 | "MIT" 766 | ], 767 | "authors": [ 768 | { 769 | "name": "Mike van Riel", 770 | "email": "me@mikevanriel.com" 771 | } 772 | ], 773 | "time": "2017-07-14T14:27:02+00:00" 774 | }, 775 | { 776 | "name": "phpspec/prophecy", 777 | "version": "1.7.6", 778 | "source": { 779 | "type": "git", 780 | "url": "https://github.com/phpspec/prophecy.git", 781 | "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" 782 | }, 783 | "dist": { 784 | "type": "zip", 785 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", 786 | "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", 787 | "shasum": "" 788 | }, 789 | "require": { 790 | "doctrine/instantiator": "^1.0.2", 791 | "php": "^5.3|^7.0", 792 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 793 | "sebastian/comparator": "^1.1|^2.0|^3.0", 794 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 795 | }, 796 | "require-dev": { 797 | "phpspec/phpspec": "^2.5|^3.2", 798 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" 799 | }, 800 | "type": "library", 801 | "extra": { 802 | "branch-alias": { 803 | "dev-master": "1.7.x-dev" 804 | } 805 | }, 806 | "autoload": { 807 | "psr-0": { 808 | "Prophecy\\": "src/" 809 | } 810 | }, 811 | "notification-url": "https://packagist.org/downloads/", 812 | "license": [ 813 | "MIT" 814 | ], 815 | "authors": [ 816 | { 817 | "name": "Konstantin Kudryashov", 818 | "email": "ever.zet@gmail.com", 819 | "homepage": "http://everzet.com" 820 | }, 821 | { 822 | "name": "Marcello Duarte", 823 | "email": "marcello.duarte@gmail.com" 824 | } 825 | ], 826 | "description": "Highly opinionated mocking framework for PHP 5.3+", 827 | "homepage": "https://github.com/phpspec/prophecy", 828 | "keywords": [ 829 | "Double", 830 | "Dummy", 831 | "fake", 832 | "mock", 833 | "spy", 834 | "stub" 835 | ], 836 | "time": "2018-04-18T13:57:24+00:00" 837 | }, 838 | { 839 | "name": "phpunit/dbunit", 840 | "version": "4.0.0", 841 | "source": { 842 | "type": "git", 843 | "url": "https://github.com/sebastianbergmann/dbunit.git", 844 | "reference": "e77b469c3962b5a563f09a2a989f1c9bd38b8615" 845 | }, 846 | "dist": { 847 | "type": "zip", 848 | "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/e77b469c3962b5a563f09a2a989f1c9bd38b8615", 849 | "reference": "e77b469c3962b5a563f09a2a989f1c9bd38b8615", 850 | "shasum": "" 851 | }, 852 | "require": { 853 | "ext-pdo": "*", 854 | "ext-simplexml": "*", 855 | "php": "^7.1", 856 | "phpunit/phpunit": "^7.0", 857 | "symfony/yaml": "^3.0 || ^4.0" 858 | }, 859 | "type": "library", 860 | "extra": { 861 | "branch-alias": { 862 | "dev-master": "4.0-dev" 863 | } 864 | }, 865 | "autoload": { 866 | "classmap": [ 867 | "src/" 868 | ] 869 | }, 870 | "notification-url": "https://packagist.org/downloads/", 871 | "license": [ 872 | "BSD-3-Clause" 873 | ], 874 | "authors": [ 875 | { 876 | "name": "Sebastian Bergmann", 877 | "email": "sebastian@phpunit.de", 878 | "role": "lead" 879 | } 880 | ], 881 | "description": "PHPUnit extension for database interaction testing", 882 | "homepage": "https://github.com/sebastianbergmann/dbunit/", 883 | "keywords": [ 884 | "database", 885 | "testing", 886 | "xunit" 887 | ], 888 | "time": "2018-02-07T06:47:59+00:00" 889 | }, 890 | { 891 | "name": "phpunit/php-code-coverage", 892 | "version": "6.0.4", 893 | "source": { 894 | "type": "git", 895 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 896 | "reference": "52187754b0eed0b8159f62a6fa30073327e8c2ca" 897 | }, 898 | "dist": { 899 | "type": "zip", 900 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/52187754b0eed0b8159f62a6fa30073327e8c2ca", 901 | "reference": "52187754b0eed0b8159f62a6fa30073327e8c2ca", 902 | "shasum": "" 903 | }, 904 | "require": { 905 | "ext-dom": "*", 906 | "ext-xmlwriter": "*", 907 | "php": "^7.1", 908 | "phpunit/php-file-iterator": "^1.4.2", 909 | "phpunit/php-text-template": "^1.2.1", 910 | "phpunit/php-token-stream": "^3.0", 911 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 912 | "sebastian/environment": "^3.1", 913 | "sebastian/version": "^2.0.1", 914 | "theseer/tokenizer": "^1.1" 915 | }, 916 | "require-dev": { 917 | "phpunit/phpunit": "^7.0" 918 | }, 919 | "suggest": { 920 | "ext-xdebug": "^2.6.0" 921 | }, 922 | "type": "library", 923 | "extra": { 924 | "branch-alias": { 925 | "dev-master": "6.0-dev" 926 | } 927 | }, 928 | "autoload": { 929 | "classmap": [ 930 | "src/" 931 | ] 932 | }, 933 | "notification-url": "https://packagist.org/downloads/", 934 | "license": [ 935 | "BSD-3-Clause" 936 | ], 937 | "authors": [ 938 | { 939 | "name": "Sebastian Bergmann", 940 | "email": "sebastian@phpunit.de", 941 | "role": "lead" 942 | } 943 | ], 944 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 945 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 946 | "keywords": [ 947 | "coverage", 948 | "testing", 949 | "xunit" 950 | ], 951 | "time": "2018-04-29T14:59:09+00:00" 952 | }, 953 | { 954 | "name": "phpunit/php-file-iterator", 955 | "version": "1.4.5", 956 | "source": { 957 | "type": "git", 958 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 959 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" 960 | }, 961 | "dist": { 962 | "type": "zip", 963 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", 964 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", 965 | "shasum": "" 966 | }, 967 | "require": { 968 | "php": ">=5.3.3" 969 | }, 970 | "type": "library", 971 | "extra": { 972 | "branch-alias": { 973 | "dev-master": "1.4.x-dev" 974 | } 975 | }, 976 | "autoload": { 977 | "classmap": [ 978 | "src/" 979 | ] 980 | }, 981 | "notification-url": "https://packagist.org/downloads/", 982 | "license": [ 983 | "BSD-3-Clause" 984 | ], 985 | "authors": [ 986 | { 987 | "name": "Sebastian Bergmann", 988 | "email": "sb@sebastian-bergmann.de", 989 | "role": "lead" 990 | } 991 | ], 992 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 993 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 994 | "keywords": [ 995 | "filesystem", 996 | "iterator" 997 | ], 998 | "time": "2017-11-27T13:52:08+00:00" 999 | }, 1000 | { 1001 | "name": "phpunit/php-text-template", 1002 | "version": "1.2.1", 1003 | "source": { 1004 | "type": "git", 1005 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1006 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 1007 | }, 1008 | "dist": { 1009 | "type": "zip", 1010 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1011 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1012 | "shasum": "" 1013 | }, 1014 | "require": { 1015 | "php": ">=5.3.3" 1016 | }, 1017 | "type": "library", 1018 | "autoload": { 1019 | "classmap": [ 1020 | "src/" 1021 | ] 1022 | }, 1023 | "notification-url": "https://packagist.org/downloads/", 1024 | "license": [ 1025 | "BSD-3-Clause" 1026 | ], 1027 | "authors": [ 1028 | { 1029 | "name": "Sebastian Bergmann", 1030 | "email": "sebastian@phpunit.de", 1031 | "role": "lead" 1032 | } 1033 | ], 1034 | "description": "Simple template engine.", 1035 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1036 | "keywords": [ 1037 | "template" 1038 | ], 1039 | "time": "2015-06-21T13:50:34+00:00" 1040 | }, 1041 | { 1042 | "name": "phpunit/php-timer", 1043 | "version": "2.0.0", 1044 | "source": { 1045 | "type": "git", 1046 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1047 | "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" 1048 | }, 1049 | "dist": { 1050 | "type": "zip", 1051 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", 1052 | "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", 1053 | "shasum": "" 1054 | }, 1055 | "require": { 1056 | "php": "^7.1" 1057 | }, 1058 | "require-dev": { 1059 | "phpunit/phpunit": "^7.0" 1060 | }, 1061 | "type": "library", 1062 | "extra": { 1063 | "branch-alias": { 1064 | "dev-master": "2.0-dev" 1065 | } 1066 | }, 1067 | "autoload": { 1068 | "classmap": [ 1069 | "src/" 1070 | ] 1071 | }, 1072 | "notification-url": "https://packagist.org/downloads/", 1073 | "license": [ 1074 | "BSD-3-Clause" 1075 | ], 1076 | "authors": [ 1077 | { 1078 | "name": "Sebastian Bergmann", 1079 | "email": "sebastian@phpunit.de", 1080 | "role": "lead" 1081 | } 1082 | ], 1083 | "description": "Utility class for timing", 1084 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1085 | "keywords": [ 1086 | "timer" 1087 | ], 1088 | "time": "2018-02-01T13:07:23+00:00" 1089 | }, 1090 | { 1091 | "name": "phpunit/php-token-stream", 1092 | "version": "3.0.0", 1093 | "source": { 1094 | "type": "git", 1095 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 1096 | "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" 1097 | }, 1098 | "dist": { 1099 | "type": "zip", 1100 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", 1101 | "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", 1102 | "shasum": "" 1103 | }, 1104 | "require": { 1105 | "ext-tokenizer": "*", 1106 | "php": "^7.1" 1107 | }, 1108 | "require-dev": { 1109 | "phpunit/phpunit": "^7.0" 1110 | }, 1111 | "type": "library", 1112 | "extra": { 1113 | "branch-alias": { 1114 | "dev-master": "3.0-dev" 1115 | } 1116 | }, 1117 | "autoload": { 1118 | "classmap": [ 1119 | "src/" 1120 | ] 1121 | }, 1122 | "notification-url": "https://packagist.org/downloads/", 1123 | "license": [ 1124 | "BSD-3-Clause" 1125 | ], 1126 | "authors": [ 1127 | { 1128 | "name": "Sebastian Bergmann", 1129 | "email": "sebastian@phpunit.de" 1130 | } 1131 | ], 1132 | "description": "Wrapper around PHP's tokenizer extension.", 1133 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 1134 | "keywords": [ 1135 | "tokenizer" 1136 | ], 1137 | "time": "2018-02-01T13:16:43+00:00" 1138 | }, 1139 | { 1140 | "name": "phpunit/phpunit", 1141 | "version": "7.1.5", 1142 | "source": { 1143 | "type": "git", 1144 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1145 | "reference": "ca64dba53b88aba6af32aebc6b388068db95c435" 1146 | }, 1147 | "dist": { 1148 | "type": "zip", 1149 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ca64dba53b88aba6af32aebc6b388068db95c435", 1150 | "reference": "ca64dba53b88aba6af32aebc6b388068db95c435", 1151 | "shasum": "" 1152 | }, 1153 | "require": { 1154 | "ext-dom": "*", 1155 | "ext-json": "*", 1156 | "ext-libxml": "*", 1157 | "ext-mbstring": "*", 1158 | "ext-xml": "*", 1159 | "myclabs/deep-copy": "^1.6.1", 1160 | "phar-io/manifest": "^1.0.1", 1161 | "phar-io/version": "^1.0", 1162 | "php": "^7.1", 1163 | "phpspec/prophecy": "^1.7", 1164 | "phpunit/php-code-coverage": "^6.0.1", 1165 | "phpunit/php-file-iterator": "^1.4.3", 1166 | "phpunit/php-text-template": "^1.2.1", 1167 | "phpunit/php-timer": "^2.0", 1168 | "phpunit/phpunit-mock-objects": "^6.1.1", 1169 | "sebastian/comparator": "^3.0", 1170 | "sebastian/diff": "^3.0", 1171 | "sebastian/environment": "^3.1", 1172 | "sebastian/exporter": "^3.1", 1173 | "sebastian/global-state": "^2.0", 1174 | "sebastian/object-enumerator": "^3.0.3", 1175 | "sebastian/resource-operations": "^1.0", 1176 | "sebastian/version": "^2.0.1" 1177 | }, 1178 | "require-dev": { 1179 | "ext-pdo": "*" 1180 | }, 1181 | "suggest": { 1182 | "ext-xdebug": "*", 1183 | "phpunit/php-invoker": "^2.0" 1184 | }, 1185 | "bin": [ 1186 | "phpunit" 1187 | ], 1188 | "type": "library", 1189 | "extra": { 1190 | "branch-alias": { 1191 | "dev-master": "7.1-dev" 1192 | } 1193 | }, 1194 | "autoload": { 1195 | "classmap": [ 1196 | "src/" 1197 | ] 1198 | }, 1199 | "notification-url": "https://packagist.org/downloads/", 1200 | "license": [ 1201 | "BSD-3-Clause" 1202 | ], 1203 | "authors": [ 1204 | { 1205 | "name": "Sebastian Bergmann", 1206 | "email": "sebastian@phpunit.de", 1207 | "role": "lead" 1208 | } 1209 | ], 1210 | "description": "The PHP Unit Testing framework.", 1211 | "homepage": "https://phpunit.de/", 1212 | "keywords": [ 1213 | "phpunit", 1214 | "testing", 1215 | "xunit" 1216 | ], 1217 | "time": "2018-04-29T15:09:19+00:00" 1218 | }, 1219 | { 1220 | "name": "phpunit/phpunit-mock-objects", 1221 | "version": "6.1.1", 1222 | "source": { 1223 | "type": "git", 1224 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 1225 | "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157" 1226 | }, 1227 | "dist": { 1228 | "type": "zip", 1229 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/70c740bde8fd9ea9ea295be1cd875dd7b267e157", 1230 | "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157", 1231 | "shasum": "" 1232 | }, 1233 | "require": { 1234 | "doctrine/instantiator": "^1.0.5", 1235 | "php": "^7.1", 1236 | "phpunit/php-text-template": "^1.2.1", 1237 | "sebastian/exporter": "^3.1" 1238 | }, 1239 | "require-dev": { 1240 | "phpunit/phpunit": "^7.0" 1241 | }, 1242 | "suggest": { 1243 | "ext-soap": "*" 1244 | }, 1245 | "type": "library", 1246 | "extra": { 1247 | "branch-alias": { 1248 | "dev-master": "6.1-dev" 1249 | } 1250 | }, 1251 | "autoload": { 1252 | "classmap": [ 1253 | "src/" 1254 | ] 1255 | }, 1256 | "notification-url": "https://packagist.org/downloads/", 1257 | "license": [ 1258 | "BSD-3-Clause" 1259 | ], 1260 | "authors": [ 1261 | { 1262 | "name": "Sebastian Bergmann", 1263 | "email": "sebastian@phpunit.de", 1264 | "role": "lead" 1265 | } 1266 | ], 1267 | "description": "Mock Object library for PHPUnit", 1268 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 1269 | "keywords": [ 1270 | "mock", 1271 | "xunit" 1272 | ], 1273 | "time": "2018-04-11T04:50:36+00:00" 1274 | }, 1275 | { 1276 | "name": "sebastian/code-unit-reverse-lookup", 1277 | "version": "1.0.1", 1278 | "source": { 1279 | "type": "git", 1280 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1281 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 1282 | }, 1283 | "dist": { 1284 | "type": "zip", 1285 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 1286 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 1287 | "shasum": "" 1288 | }, 1289 | "require": { 1290 | "php": "^5.6 || ^7.0" 1291 | }, 1292 | "require-dev": { 1293 | "phpunit/phpunit": "^5.7 || ^6.0" 1294 | }, 1295 | "type": "library", 1296 | "extra": { 1297 | "branch-alias": { 1298 | "dev-master": "1.0.x-dev" 1299 | } 1300 | }, 1301 | "autoload": { 1302 | "classmap": [ 1303 | "src/" 1304 | ] 1305 | }, 1306 | "notification-url": "https://packagist.org/downloads/", 1307 | "license": [ 1308 | "BSD-3-Clause" 1309 | ], 1310 | "authors": [ 1311 | { 1312 | "name": "Sebastian Bergmann", 1313 | "email": "sebastian@phpunit.de" 1314 | } 1315 | ], 1316 | "description": "Looks up which function or method a line of code belongs to", 1317 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1318 | "time": "2017-03-04T06:30:41+00:00" 1319 | }, 1320 | { 1321 | "name": "sebastian/comparator", 1322 | "version": "3.0.0", 1323 | "source": { 1324 | "type": "git", 1325 | "url": "https://github.com/sebastianbergmann/comparator.git", 1326 | "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5" 1327 | }, 1328 | "dist": { 1329 | "type": "zip", 1330 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5", 1331 | "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5", 1332 | "shasum": "" 1333 | }, 1334 | "require": { 1335 | "php": "^7.1", 1336 | "sebastian/diff": "^3.0", 1337 | "sebastian/exporter": "^3.1" 1338 | }, 1339 | "require-dev": { 1340 | "phpunit/phpunit": "^7.1" 1341 | }, 1342 | "type": "library", 1343 | "extra": { 1344 | "branch-alias": { 1345 | "dev-master": "3.0-dev" 1346 | } 1347 | }, 1348 | "autoload": { 1349 | "classmap": [ 1350 | "src/" 1351 | ] 1352 | }, 1353 | "notification-url": "https://packagist.org/downloads/", 1354 | "license": [ 1355 | "BSD-3-Clause" 1356 | ], 1357 | "authors": [ 1358 | { 1359 | "name": "Jeff Welch", 1360 | "email": "whatthejeff@gmail.com" 1361 | }, 1362 | { 1363 | "name": "Volker Dusch", 1364 | "email": "github@wallbash.com" 1365 | }, 1366 | { 1367 | "name": "Bernhard Schussek", 1368 | "email": "bschussek@2bepublished.at" 1369 | }, 1370 | { 1371 | "name": "Sebastian Bergmann", 1372 | "email": "sebastian@phpunit.de" 1373 | } 1374 | ], 1375 | "description": "Provides the functionality to compare PHP values for equality", 1376 | "homepage": "https://github.com/sebastianbergmann/comparator", 1377 | "keywords": [ 1378 | "comparator", 1379 | "compare", 1380 | "equality" 1381 | ], 1382 | "time": "2018-04-18T13:33:00+00:00" 1383 | }, 1384 | { 1385 | "name": "sebastian/diff", 1386 | "version": "3.0.0", 1387 | "source": { 1388 | "type": "git", 1389 | "url": "https://github.com/sebastianbergmann/diff.git", 1390 | "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" 1391 | }, 1392 | "dist": { 1393 | "type": "zip", 1394 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", 1395 | "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", 1396 | "shasum": "" 1397 | }, 1398 | "require": { 1399 | "php": "^7.1" 1400 | }, 1401 | "require-dev": { 1402 | "phpunit/phpunit": "^7.0", 1403 | "symfony/process": "^2 || ^3.3 || ^4" 1404 | }, 1405 | "type": "library", 1406 | "extra": { 1407 | "branch-alias": { 1408 | "dev-master": "3.0-dev" 1409 | } 1410 | }, 1411 | "autoload": { 1412 | "classmap": [ 1413 | "src/" 1414 | ] 1415 | }, 1416 | "notification-url": "https://packagist.org/downloads/", 1417 | "license": [ 1418 | "BSD-3-Clause" 1419 | ], 1420 | "authors": [ 1421 | { 1422 | "name": "Kore Nordmann", 1423 | "email": "mail@kore-nordmann.de" 1424 | }, 1425 | { 1426 | "name": "Sebastian Bergmann", 1427 | "email": "sebastian@phpunit.de" 1428 | } 1429 | ], 1430 | "description": "Diff implementation", 1431 | "homepage": "https://github.com/sebastianbergmann/diff", 1432 | "keywords": [ 1433 | "diff", 1434 | "udiff", 1435 | "unidiff", 1436 | "unified diff" 1437 | ], 1438 | "time": "2018-02-01T13:45:15+00:00" 1439 | }, 1440 | { 1441 | "name": "sebastian/environment", 1442 | "version": "3.1.0", 1443 | "source": { 1444 | "type": "git", 1445 | "url": "https://github.com/sebastianbergmann/environment.git", 1446 | "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" 1447 | }, 1448 | "dist": { 1449 | "type": "zip", 1450 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", 1451 | "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", 1452 | "shasum": "" 1453 | }, 1454 | "require": { 1455 | "php": "^7.0" 1456 | }, 1457 | "require-dev": { 1458 | "phpunit/phpunit": "^6.1" 1459 | }, 1460 | "type": "library", 1461 | "extra": { 1462 | "branch-alias": { 1463 | "dev-master": "3.1.x-dev" 1464 | } 1465 | }, 1466 | "autoload": { 1467 | "classmap": [ 1468 | "src/" 1469 | ] 1470 | }, 1471 | "notification-url": "https://packagist.org/downloads/", 1472 | "license": [ 1473 | "BSD-3-Clause" 1474 | ], 1475 | "authors": [ 1476 | { 1477 | "name": "Sebastian Bergmann", 1478 | "email": "sebastian@phpunit.de" 1479 | } 1480 | ], 1481 | "description": "Provides functionality to handle HHVM/PHP environments", 1482 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1483 | "keywords": [ 1484 | "Xdebug", 1485 | "environment", 1486 | "hhvm" 1487 | ], 1488 | "time": "2017-07-01T08:51:00+00:00" 1489 | }, 1490 | { 1491 | "name": "sebastian/exporter", 1492 | "version": "3.1.0", 1493 | "source": { 1494 | "type": "git", 1495 | "url": "https://github.com/sebastianbergmann/exporter.git", 1496 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" 1497 | }, 1498 | "dist": { 1499 | "type": "zip", 1500 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", 1501 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", 1502 | "shasum": "" 1503 | }, 1504 | "require": { 1505 | "php": "^7.0", 1506 | "sebastian/recursion-context": "^3.0" 1507 | }, 1508 | "require-dev": { 1509 | "ext-mbstring": "*", 1510 | "phpunit/phpunit": "^6.0" 1511 | }, 1512 | "type": "library", 1513 | "extra": { 1514 | "branch-alias": { 1515 | "dev-master": "3.1.x-dev" 1516 | } 1517 | }, 1518 | "autoload": { 1519 | "classmap": [ 1520 | "src/" 1521 | ] 1522 | }, 1523 | "notification-url": "https://packagist.org/downloads/", 1524 | "license": [ 1525 | "BSD-3-Clause" 1526 | ], 1527 | "authors": [ 1528 | { 1529 | "name": "Jeff Welch", 1530 | "email": "whatthejeff@gmail.com" 1531 | }, 1532 | { 1533 | "name": "Volker Dusch", 1534 | "email": "github@wallbash.com" 1535 | }, 1536 | { 1537 | "name": "Bernhard Schussek", 1538 | "email": "bschussek@2bepublished.at" 1539 | }, 1540 | { 1541 | "name": "Sebastian Bergmann", 1542 | "email": "sebastian@phpunit.de" 1543 | }, 1544 | { 1545 | "name": "Adam Harvey", 1546 | "email": "aharvey@php.net" 1547 | } 1548 | ], 1549 | "description": "Provides the functionality to export PHP variables for visualization", 1550 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1551 | "keywords": [ 1552 | "export", 1553 | "exporter" 1554 | ], 1555 | "time": "2017-04-03T13:19:02+00:00" 1556 | }, 1557 | { 1558 | "name": "sebastian/global-state", 1559 | "version": "2.0.0", 1560 | "source": { 1561 | "type": "git", 1562 | "url": "https://github.com/sebastianbergmann/global-state.git", 1563 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 1564 | }, 1565 | "dist": { 1566 | "type": "zip", 1567 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1568 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1569 | "shasum": "" 1570 | }, 1571 | "require": { 1572 | "php": "^7.0" 1573 | }, 1574 | "require-dev": { 1575 | "phpunit/phpunit": "^6.0" 1576 | }, 1577 | "suggest": { 1578 | "ext-uopz": "*" 1579 | }, 1580 | "type": "library", 1581 | "extra": { 1582 | "branch-alias": { 1583 | "dev-master": "2.0-dev" 1584 | } 1585 | }, 1586 | "autoload": { 1587 | "classmap": [ 1588 | "src/" 1589 | ] 1590 | }, 1591 | "notification-url": "https://packagist.org/downloads/", 1592 | "license": [ 1593 | "BSD-3-Clause" 1594 | ], 1595 | "authors": [ 1596 | { 1597 | "name": "Sebastian Bergmann", 1598 | "email": "sebastian@phpunit.de" 1599 | } 1600 | ], 1601 | "description": "Snapshotting of global state", 1602 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1603 | "keywords": [ 1604 | "global state" 1605 | ], 1606 | "time": "2017-04-27T15:39:26+00:00" 1607 | }, 1608 | { 1609 | "name": "sebastian/object-enumerator", 1610 | "version": "3.0.3", 1611 | "source": { 1612 | "type": "git", 1613 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1614 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 1615 | }, 1616 | "dist": { 1617 | "type": "zip", 1618 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1619 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1620 | "shasum": "" 1621 | }, 1622 | "require": { 1623 | "php": "^7.0", 1624 | "sebastian/object-reflector": "^1.1.1", 1625 | "sebastian/recursion-context": "^3.0" 1626 | }, 1627 | "require-dev": { 1628 | "phpunit/phpunit": "^6.0" 1629 | }, 1630 | "type": "library", 1631 | "extra": { 1632 | "branch-alias": { 1633 | "dev-master": "3.0.x-dev" 1634 | } 1635 | }, 1636 | "autoload": { 1637 | "classmap": [ 1638 | "src/" 1639 | ] 1640 | }, 1641 | "notification-url": "https://packagist.org/downloads/", 1642 | "license": [ 1643 | "BSD-3-Clause" 1644 | ], 1645 | "authors": [ 1646 | { 1647 | "name": "Sebastian Bergmann", 1648 | "email": "sebastian@phpunit.de" 1649 | } 1650 | ], 1651 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1652 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1653 | "time": "2017-08-03T12:35:26+00:00" 1654 | }, 1655 | { 1656 | "name": "sebastian/object-reflector", 1657 | "version": "1.1.1", 1658 | "source": { 1659 | "type": "git", 1660 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1661 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 1662 | }, 1663 | "dist": { 1664 | "type": "zip", 1665 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 1666 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 1667 | "shasum": "" 1668 | }, 1669 | "require": { 1670 | "php": "^7.0" 1671 | }, 1672 | "require-dev": { 1673 | "phpunit/phpunit": "^6.0" 1674 | }, 1675 | "type": "library", 1676 | "extra": { 1677 | "branch-alias": { 1678 | "dev-master": "1.1-dev" 1679 | } 1680 | }, 1681 | "autoload": { 1682 | "classmap": [ 1683 | "src/" 1684 | ] 1685 | }, 1686 | "notification-url": "https://packagist.org/downloads/", 1687 | "license": [ 1688 | "BSD-3-Clause" 1689 | ], 1690 | "authors": [ 1691 | { 1692 | "name": "Sebastian Bergmann", 1693 | "email": "sebastian@phpunit.de" 1694 | } 1695 | ], 1696 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1697 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1698 | "time": "2017-03-29T09:07:27+00:00" 1699 | }, 1700 | { 1701 | "name": "sebastian/recursion-context", 1702 | "version": "3.0.0", 1703 | "source": { 1704 | "type": "git", 1705 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1706 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 1707 | }, 1708 | "dist": { 1709 | "type": "zip", 1710 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1711 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1712 | "shasum": "" 1713 | }, 1714 | "require": { 1715 | "php": "^7.0" 1716 | }, 1717 | "require-dev": { 1718 | "phpunit/phpunit": "^6.0" 1719 | }, 1720 | "type": "library", 1721 | "extra": { 1722 | "branch-alias": { 1723 | "dev-master": "3.0.x-dev" 1724 | } 1725 | }, 1726 | "autoload": { 1727 | "classmap": [ 1728 | "src/" 1729 | ] 1730 | }, 1731 | "notification-url": "https://packagist.org/downloads/", 1732 | "license": [ 1733 | "BSD-3-Clause" 1734 | ], 1735 | "authors": [ 1736 | { 1737 | "name": "Jeff Welch", 1738 | "email": "whatthejeff@gmail.com" 1739 | }, 1740 | { 1741 | "name": "Sebastian Bergmann", 1742 | "email": "sebastian@phpunit.de" 1743 | }, 1744 | { 1745 | "name": "Adam Harvey", 1746 | "email": "aharvey@php.net" 1747 | } 1748 | ], 1749 | "description": "Provides functionality to recursively process PHP variables", 1750 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1751 | "time": "2017-03-03T06:23:57+00:00" 1752 | }, 1753 | { 1754 | "name": "sebastian/resource-operations", 1755 | "version": "1.0.0", 1756 | "source": { 1757 | "type": "git", 1758 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1759 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" 1760 | }, 1761 | "dist": { 1762 | "type": "zip", 1763 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1764 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1765 | "shasum": "" 1766 | }, 1767 | "require": { 1768 | "php": ">=5.6.0" 1769 | }, 1770 | "type": "library", 1771 | "extra": { 1772 | "branch-alias": { 1773 | "dev-master": "1.0.x-dev" 1774 | } 1775 | }, 1776 | "autoload": { 1777 | "classmap": [ 1778 | "src/" 1779 | ] 1780 | }, 1781 | "notification-url": "https://packagist.org/downloads/", 1782 | "license": [ 1783 | "BSD-3-Clause" 1784 | ], 1785 | "authors": [ 1786 | { 1787 | "name": "Sebastian Bergmann", 1788 | "email": "sebastian@phpunit.de" 1789 | } 1790 | ], 1791 | "description": "Provides a list of PHP built-in functions that operate on resources", 1792 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1793 | "time": "2015-07-28T20:34:47+00:00" 1794 | }, 1795 | { 1796 | "name": "sebastian/version", 1797 | "version": "2.0.1", 1798 | "source": { 1799 | "type": "git", 1800 | "url": "https://github.com/sebastianbergmann/version.git", 1801 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1802 | }, 1803 | "dist": { 1804 | "type": "zip", 1805 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1806 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1807 | "shasum": "" 1808 | }, 1809 | "require": { 1810 | "php": ">=5.6" 1811 | }, 1812 | "type": "library", 1813 | "extra": { 1814 | "branch-alias": { 1815 | "dev-master": "2.0.x-dev" 1816 | } 1817 | }, 1818 | "autoload": { 1819 | "classmap": [ 1820 | "src/" 1821 | ] 1822 | }, 1823 | "notification-url": "https://packagist.org/downloads/", 1824 | "license": [ 1825 | "BSD-3-Clause" 1826 | ], 1827 | "authors": [ 1828 | { 1829 | "name": "Sebastian Bergmann", 1830 | "email": "sebastian@phpunit.de", 1831 | "role": "lead" 1832 | } 1833 | ], 1834 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1835 | "homepage": "https://github.com/sebastianbergmann/version", 1836 | "time": "2016-10-03T07:35:21+00:00" 1837 | }, 1838 | { 1839 | "name": "symfony/yaml", 1840 | "version": "v4.0.9", 1841 | "source": { 1842 | "type": "git", 1843 | "url": "https://github.com/symfony/yaml.git", 1844 | "reference": "275ad099e4cbe612a2acbca14a16dd1c5311324d" 1845 | }, 1846 | "dist": { 1847 | "type": "zip", 1848 | "url": "https://api.github.com/repos/symfony/yaml/zipball/275ad099e4cbe612a2acbca14a16dd1c5311324d", 1849 | "reference": "275ad099e4cbe612a2acbca14a16dd1c5311324d", 1850 | "shasum": "" 1851 | }, 1852 | "require": { 1853 | "php": "^7.1.3" 1854 | }, 1855 | "conflict": { 1856 | "symfony/console": "<3.4" 1857 | }, 1858 | "require-dev": { 1859 | "symfony/console": "~3.4|~4.0" 1860 | }, 1861 | "suggest": { 1862 | "symfony/console": "For validating YAML files using the lint command" 1863 | }, 1864 | "type": "library", 1865 | "extra": { 1866 | "branch-alias": { 1867 | "dev-master": "4.0-dev" 1868 | } 1869 | }, 1870 | "autoload": { 1871 | "psr-4": { 1872 | "Symfony\\Component\\Yaml\\": "" 1873 | }, 1874 | "exclude-from-classmap": [ 1875 | "/Tests/" 1876 | ] 1877 | }, 1878 | "notification-url": "https://packagist.org/downloads/", 1879 | "license": [ 1880 | "MIT" 1881 | ], 1882 | "authors": [ 1883 | { 1884 | "name": "Fabien Potencier", 1885 | "email": "fabien@symfony.com" 1886 | }, 1887 | { 1888 | "name": "Symfony Community", 1889 | "homepage": "https://symfony.com/contributors" 1890 | } 1891 | ], 1892 | "description": "Symfony Yaml Component", 1893 | "homepage": "https://symfony.com", 1894 | "time": "2018-04-08T08:49:08+00:00" 1895 | }, 1896 | { 1897 | "name": "theseer/tokenizer", 1898 | "version": "1.1.0", 1899 | "source": { 1900 | "type": "git", 1901 | "url": "https://github.com/theseer/tokenizer.git", 1902 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" 1903 | }, 1904 | "dist": { 1905 | "type": "zip", 1906 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", 1907 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", 1908 | "shasum": "" 1909 | }, 1910 | "require": { 1911 | "ext-dom": "*", 1912 | "ext-tokenizer": "*", 1913 | "ext-xmlwriter": "*", 1914 | "php": "^7.0" 1915 | }, 1916 | "type": "library", 1917 | "autoload": { 1918 | "classmap": [ 1919 | "src/" 1920 | ] 1921 | }, 1922 | "notification-url": "https://packagist.org/downloads/", 1923 | "license": [ 1924 | "BSD-3-Clause" 1925 | ], 1926 | "authors": [ 1927 | { 1928 | "name": "Arne Blankerts", 1929 | "email": "arne@blankerts.de", 1930 | "role": "Developer" 1931 | } 1932 | ], 1933 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1934 | "time": "2017-04-07T12:08:54+00:00" 1935 | }, 1936 | { 1937 | "name": "webmozart/assert", 1938 | "version": "1.3.0", 1939 | "source": { 1940 | "type": "git", 1941 | "url": "https://github.com/webmozart/assert.git", 1942 | "reference": "0df1908962e7a3071564e857d86874dad1ef204a" 1943 | }, 1944 | "dist": { 1945 | "type": "zip", 1946 | "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", 1947 | "reference": "0df1908962e7a3071564e857d86874dad1ef204a", 1948 | "shasum": "" 1949 | }, 1950 | "require": { 1951 | "php": "^5.3.3 || ^7.0" 1952 | }, 1953 | "require-dev": { 1954 | "phpunit/phpunit": "^4.6", 1955 | "sebastian/version": "^1.0.1" 1956 | }, 1957 | "type": "library", 1958 | "extra": { 1959 | "branch-alias": { 1960 | "dev-master": "1.3-dev" 1961 | } 1962 | }, 1963 | "autoload": { 1964 | "psr-4": { 1965 | "Webmozart\\Assert\\": "src/" 1966 | } 1967 | }, 1968 | "notification-url": "https://packagist.org/downloads/", 1969 | "license": [ 1970 | "MIT" 1971 | ], 1972 | "authors": [ 1973 | { 1974 | "name": "Bernhard Schussek", 1975 | "email": "bschussek@gmail.com" 1976 | } 1977 | ], 1978 | "description": "Assertions to validate method input/output with nice error messages.", 1979 | "keywords": [ 1980 | "assert", 1981 | "check", 1982 | "validate" 1983 | ], 1984 | "time": "2018-01-29T19:49:41+00:00" 1985 | } 1986 | ], 1987 | "aliases": [], 1988 | "minimum-stability": "stable", 1989 | "stability-flags": [], 1990 | "prefer-stable": false, 1991 | "prefer-lowest": false, 1992 | "platform": [], 1993 | "platform-dev": [] 1994 | } 1995 | --------------------------------------------------------------------------------