├── phpcs.xml.dist ├── src ├── conditions │ ├── LikeConditionBuilder.php │ └── InConditionBuilder.php ├── ColumnSchemaBuilder.php ├── ExpressionBuilder.php ├── ColumnSchema.php ├── PdoAdapter.php ├── Command.php ├── Connection.php ├── Transaction.php ├── QueryBuilder.php └── Schema.php ├── composer.json ├── LICENSE.md ├── README.md └── composer.lock /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | A custom coding standard 4 | 5 | 6 | 7 | 8 | 9 | /tests/bootstrap.php$ 10 | 11 | 12 | /vendor/* 13 | -------------------------------------------------------------------------------- /src/conditions/LikeConditionBuilder.php: -------------------------------------------------------------------------------- 1 | '\%', 27 | '_' => '\_', 28 | '\\' => '\\\\', 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /src/ColumnSchemaBuilder.php: -------------------------------------------------------------------------------- 1 | 14 | * @since 2.0 15 | */ 16 | class ColumnSchemaBuilder extends \yii\db\ColumnSchemaBuilder 17 | { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public function __toString() 23 | { 24 | switch ($this->getTypeCategory()) { 25 | case self::CATEGORY_PK: 26 | $format = '{type}{length}{check}'; 27 | break; 28 | default: 29 | $format = '{type}{length}{default}{notnull}{unique}{check}'; 30 | } 31 | return $this->buildCompleteString($format); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ExpressionBuilder.php: -------------------------------------------------------------------------------- 1 | params); 30 | $string = $expression->__toString(); 31 | 32 | static $expressionMap = [ 33 | "strftime('%Y')" => "EXTRACT(YEAR FROM TIMESTAMP 'now')" 34 | ]; 35 | 36 | if (isset($expressionMap[$string])) { 37 | return $expressionMap[$string]; 38 | } 39 | 40 | return $string; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/conditions/InConditionBuilder.php: -------------------------------------------------------------------------------- 1 | $column) { 22 | $quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column; 23 | } 24 | $vss = []; 25 | foreach ($values as $value) { 26 | $vs = []; 27 | foreach ($columns as $i => $column) { 28 | if (isset($value[$column])) { 29 | $phName = $this->queryBuilder->bindParam($value[$column], $params); 30 | $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName; 31 | } else { 32 | $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL'; 33 | } 34 | } 35 | $vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')'; 36 | } 37 | 38 | return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edgardmessias/yii2-firebird", 3 | "description": "Firebird connector for Yii2 framework", 4 | "keywords": ["yii2", "firebird", "active-record"], 5 | "type": "yii2-extension", 6 | "license": "BSD-3-Clause", 7 | "support": { 8 | "issues": "https://github.com/edgardmessias/yii2-firebird/issues", 9 | "forum": "http://www.yiiframework.com/forum/", 10 | "wiki": "http://www.yiiframework.com/wiki/", 11 | "irc": "irc://irc.freenode.net/yii", 12 | "source": "https://github.com/edgardmessias/yii2-firebird" 13 | }, 14 | "authors": [ 15 | { 16 | "name": "Edgard Lorraine Messias", 17 | "email": "edgardmessias@gmail.com" 18 | } 19 | ], 20 | "require": { 21 | "yiisoft/yii2": "^2.0.15.0" 22 | }, 23 | "require-dev": { 24 | "yiisoft/yii2-dev": "~2.0.15.0", 25 | "yiisoft/yii2-coding-standards": "^2.0.2", 26 | "phpunit/phpunit": "4.8.34" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "edgardmessias\\db\\firebird\\": "src" 31 | } 32 | }, 33 | "config": { 34 | "platform": { 35 | "php": "5.4" 36 | }, 37 | "fxp-asset": { 38 | "installer-paths": { 39 | "npm-asset-library": "vendor/npm", 40 | "bower-asset-library": "vendor/bower" 41 | }, 42 | "vcs-driver-options": { 43 | "github-no-api": true 44 | }, 45 | "pattern-skip-version": "(-build|-patch)" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The yii2-firebird is free software. It is released under the terms of 2 | the following BSD License. 3 | 4 | Copyright © 2016 by Edgard Lorraine Messias (https://github.com/edgardmessias) 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 9 | are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Edgard Lorraine Messias nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /src/ColumnSchema.php: -------------------------------------------------------------------------------- 1 | 17 | * @since 2.0 18 | */ 19 | class ColumnSchema extends \yii\db\ColumnSchema 20 | { 21 | 22 | /** 23 | * Converts the input value according to [[phpType]] after retrieval from the database. 24 | * If the value is null or an [[Expression]], it will not be converted. 25 | * @param mixed $value input value 26 | * @return mixed converted value 27 | * @since 2.0.3 28 | */ 29 | protected function typecast($value) 30 | { 31 | if ($value === '' 32 | && !in_array( 33 | $this->type, 34 | [ 35 | Schema::TYPE_TEXT, 36 | Schema::TYPE_STRING, 37 | Schema::TYPE_BINARY, 38 | Schema::TYPE_CHAR 39 | ], 40 | true) 41 | ) { 42 | return null; 43 | } 44 | 45 | if ($value === null 46 | || gettype($value) === $this->phpType 47 | || $value instanceof ExpressionInterface 48 | || $value instanceof Query 49 | ) { 50 | return $value; 51 | } 52 | 53 | if (is_array($value) 54 | && count($value) === 2 55 | && isset($value[1]) 56 | && in_array($value[1], $this->getPdoParamTypes(), true) 57 | ) { 58 | return new PdoValue($value[0], $value[1]); 59 | } 60 | 61 | switch ($this->phpType) { 62 | case 'resource': 63 | case 'string': 64 | if (is_resource($value)) { 65 | return $value; 66 | } 67 | if (is_float($value)) { 68 | // ensure type cast always has . as decimal separator in all locales 69 | return str_replace(',', '.', (string) $value); 70 | } 71 | return (string) $value; 72 | case 'integer': 73 | if (is_bool($value)) { 74 | return ($value) ? 1 : 0; 75 | } 76 | return (int) $value; 77 | case 'boolean': 78 | return (boolean) $value; 79 | case 'double': 80 | return (double) $value; 81 | } 82 | 83 | return $value; 84 | } 85 | 86 | /** 87 | * @return int[] array of numbers that represent possible PDO parameter types 88 | */ 89 | private function getPdoParamTypes() 90 | { 91 | return [\PDO::PARAM_BOOL, \PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_LOB, \PDO::PARAM_NULL, \PDO::PARAM_STMT]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/PdoAdapter.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class PdoAdapter extends PDO 19 | { 20 | 21 | private $_inTransaction = false; 22 | 23 | /** 24 | * Do some basic setup for Firebird. 25 | * o Force use of exceptions on error. 26 | * o Force all metadata to lower case. 27 | * Yii will behave in unpredicatable ways if 28 | * metadata is not lowercase. 29 | * o Ensure that table names are not prefixed to 30 | * fieldnames when returning metadata. 31 | * Finally call parent constructor. 32 | * 33 | */ 34 | public function __construct($dsn, $username, $password, $driver_options = []) 35 | { 36 | // Windows OS paths with backslashes should be changed 37 | $dsn = str_replace('\\', '/', $dsn); 38 | // apply error mode 39 | $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; 40 | // lower case column names in results are necessary for Yii ActiveRecord proper functioning 41 | $driver_options[PDO::ATTR_CASE] = PDO::CASE_LOWER; 42 | // ensure we only receive fieldname not tablename.fieldname. 43 | $driver_options[PDO::ATTR_FETCH_TABLE_NAMES] = false; 44 | parent::__construct($dsn, $username, $password, $driver_options); 45 | } 46 | 47 | /** 48 | * Initiates a transaction 49 | * @return bool TRUE on success or FALSE on failure. 50 | */ 51 | public function beginTransaction($isolationLevel = null) 52 | { 53 | $this->setAttribute(PDO::ATTR_AUTOCOMMIT, false); 54 | 55 | if ($isolationLevel === false) { 56 | $this->_inTransaction = true; 57 | return true; 58 | } 59 | 60 | if ($isolationLevel === null) { 61 | $r = $this->exec('SET TRANSACTION'); 62 | $success = ($r !== false); 63 | if ($success) { 64 | $this->_inTransaction = true; 65 | } 66 | return ($success); 67 | } 68 | 69 | $r = $this->exec("SET TRANSACTION ISOLATION LEVEL $isolationLevel"); 70 | $success = ($r !== false); 71 | if ($success) { 72 | $this->_inTransaction = true; 73 | } 74 | return ($success); 75 | } 76 | 77 | /** 78 | * Commits a transaction 79 | * @return bool TRUE on success or FALSE on failure. 80 | */ 81 | public function commit() 82 | { 83 | $r = $this->exec('COMMIT'); 84 | $this->setAttribute(PDO::ATTR_AUTOCOMMIT, true); 85 | $success = ($r !== false); 86 | if ($success) { 87 | $this->_inTransaction = false; 88 | } 89 | return ($success); 90 | } 91 | 92 | /** 93 | * Rolls back a transaction 94 | * @return bool TRUE on success or FALSE on failure. 95 | */ 96 | public function rollBack() 97 | { 98 | $r = $this->exec('ROLLBACK'); 99 | $this->setAttribute(PDO::ATTR_AUTOCOMMIT, true); 100 | $success = ($r !== false); 101 | if ($success) { 102 | $this->_inTransaction = false; 103 | } 104 | return ($success); 105 | } 106 | 107 | /** 108 | * Checks if inside a transaction 109 | * @return bool TRUE if a transaction is currently active, and FALSE if not. 110 | */ 111 | public function inTransaction() 112 | { 113 | return $this->_inTransaction; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | 13 | * @since 2.0 14 | */ 15 | class Command extends \yii\db\Command 16 | { 17 | 18 | /** 19 | * Binds a parameter to the SQL statement to be executed. 20 | * @param string|integer $name parameter identifier. For a prepared statement 21 | * using named placeholders, this will be a parameter name of 22 | * the form `:name`. For a prepared statement using question mark 23 | * placeholders, this will be the 1-indexed position of the parameter. 24 | * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter 25 | * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. 26 | * @param integer $length length of the data type 27 | * @param mixed $driverOptions the driver-specific options 28 | * @return static the current command being executed 29 | * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php 30 | */ 31 | public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) 32 | { 33 | if ($dataType === null) { 34 | $dataType = $this->db->getSchema()->getPdoType($value); 35 | } 36 | if ($dataType == \PDO::PARAM_BOOL) { 37 | $dataType = \PDO::PARAM_INT; 38 | } 39 | return parent::bindParam($name, $value, $dataType, $length, $driverOptions); 40 | } 41 | 42 | /** 43 | * Specifies the SQL statement to be executed. 44 | * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well. 45 | * @param string $sql the SQL statement to be set. 46 | * @return static this command instance 47 | */ 48 | public function setSql($sql) 49 | { 50 | $matches = null; 51 | if (preg_match("/^\s*DROP TABLE IF EXISTS (['\"]?([^\s\;]+)['\"]?);?\s*$/i", $sql, $matches)) { 52 | if ($this->db->getSchema()->getTableSchema($matches[2]) !== null) { 53 | $sql = $this->db->getQueryBuilder()->dropTable($matches[2]); 54 | } else { 55 | $sql = 'select 1 from RDB$DATABASE;'; //Prevent Drop Table 56 | } 57 | } 58 | 59 | return parent::setSql($sql); 60 | } 61 | 62 | public function getSql() { 63 | $sql = parent::getSql(); 64 | if (is_string($sql)) { 65 | // Unquote the {{@table@}} to {{table}} for insert 66 | $sql = preg_replace('/(\{\{)@(%?[\w\-\. ]+%?)@(\}\})/', '\1\2\3', $sql); 67 | } 68 | return $sql; 69 | } 70 | 71 | /** 72 | * Binds a value to a parameter. 73 | * @param string|integer $name Parameter identifier. For a prepared statement 74 | * using named placeholders, this will be a parameter name of 75 | * the form `:name`. For a prepared statement using question mark 76 | * placeholders, this will be the 1-indexed position of the parameter. 77 | * @param mixed $value The value to bind to the parameter 78 | * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. 79 | * @return static the current command being executed 80 | * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php 81 | */ 82 | public function bindValue($name, $value, $dataType = null) 83 | { 84 | if ($dataType === null) { 85 | $dataType = $this->db->getSchema()->getPdoType($value); 86 | } 87 | if ($dataType == \PDO::PARAM_BOOL) { 88 | $dataType = \PDO::PARAM_INT; 89 | } 90 | 91 | return parent::bindValue($name, $value, $dataType); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Firebird Extension for Yii 2 2 | ========================== 3 | [![Latest Stable Version](https://poser.pugx.org/edgardmessias/yii2-firebird/v/stable)](https://packagist.org/packages/edgardmessias/yii2-firebird) 4 | [![Total Downloads](https://poser.pugx.org/edgardmessias/yii2-firebird/downloads)](https://packagist.org/packages/edgardmessias/yii2-firebird) 5 | [![Latest Unstable Version](https://poser.pugx.org/edgardmessias/yii2-firebird/v/unstable)](https://packagist.org/packages/edgardmessias/yii2-firebird) 6 | [![License](https://poser.pugx.org/edgardmessias/yii2-firebird/license)](https://packagist.org/packages/edgardmessias/yii2-firebird) 7 | 8 | This branch use last stable version of Yii2 (dev) 9 | 10 | This extension adds [Firebird](http://www.firebirdsql.org/) database engine extension for the [Yii framework 2.0](http://www.yiiframework.com). 11 | 12 | [![Yii2](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](http://www.yiiframework.com/) 13 | [![Build Status](https://travis-ci.org/edgardmessias/yii2-firebird.svg?branch=master)](https://travis-ci.org/edgardmessias/yii2-firebird) 14 | [![Dependency Status](https://www.versioneye.com/php/edgardmessias:yii2-firebird/dev-master/badge.png)](https://www.versioneye.com/php/edgardmessias:yii2-firebird/dev-master) 15 | [![Reference Status](https://www.versioneye.com/php/edgardmessias:yii2-firebird/reference_badge.svg)](https://www.versioneye.com/php/edgardmessias:yii2-firebird/references) 16 | [![Code Coverage](https://scrutinizer-ci.com/g/edgardmessias/yii2-firebird/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/edgardmessias/yii2-firebird/?branch=master) 17 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/edgardmessias/yii2-firebird/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/edgardmessias/yii2-firebird/?branch=master) 18 | 19 | Requirements 20 | ------------ 21 | 22 | At least Firebird version 2.0 is required. However, in order to use all extension features. 23 | 24 | Partial support with Firebird 3.0 25 | 26 | Unsupported 27 | ------------ 28 | 29 | Functions not supported by the Firebird database: 30 | 31 | * Rename Table - [See this FAQ](http://www.firebirdfaq.org/faq363/) 32 | * Check Integrity - [Track](http://tracker.firebirdsql.org/browse/CORE-1924) 33 | * BLOB data type for pdo_firebird <= 7.0.13 - [See this bug](https://bugs.php.net/bug.php?id=61183) 34 | * BOOLEAN data type for pdo_firebird - [See this bug](https://bugs.php.net/bug.php?id=74462) 35 | 36 | Installation 37 | ------------ 38 | 39 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 40 | 41 | Either run 42 | 43 | ```bash 44 | php composer.phar require --prefer-dist "edgardmessias/yii2-firebird:*" 45 | ``` 46 | 47 | or add 48 | 49 | ```json 50 | "edgardmessias/yii2-firebird": "*" 51 | ``` 52 | 53 | to the require section of your composer.json. 54 | 55 | 56 | Configuration 57 | ------------- 58 | 59 | To use this extension, simply add the following code in your application configuration: 60 | 61 | ```php 62 | return [ 63 | //.... 64 | 'components' => [ 65 | 'db' => [ 66 | 'class' => 'edgardmessias\db\firebird\Connection', 67 | 'dsn' => 'firebird:dbname=localhost:/tmp/TEST.FDB;charset=ISO8859_1', 68 | 'username' => 'username', 69 | 'password' => 'password', 70 | ], 71 | ], 72 | ]; 73 | ``` 74 | 75 | ## Donations: 76 | * Donation is as per your goodwill to support my development. 77 | * If you are interested in my future developments, i would really appreciate a small donation to support this project. 78 | ```html 79 | My Monero Wallet Address (XMR) 80 | 429VTmDsAw4aKgibxkk4PzZbxzj8txYtq5XrKHc28pXsUtMDWniL749WbwaVe4vUMveKAzAiA4j8xgUi29TpKXpm41bmrwQ 81 | ``` 82 | ```html 83 | My Bitcoin Wallet Address (BTC) 84 | 38hcARGVzgYrcdYPkXxBXKTqScdixvFhZ4 85 | ``` 86 | ```html 87 | My Ethereum Wallet Address (ETH) 88 | 0xdb77aa3d0e496c73a0dac816ac33ea389cf54681 89 | ``` 90 | Another Cryptocurrency: https://freewallet.org/id/edgardmessias 91 | -------------------------------------------------------------------------------- /src/Connection.php: -------------------------------------------------------------------------------- 1 | 13 | * @since 2.0 14 | */ 15 | class Connection extends \yii\db\Connection 16 | { 17 | /** 18 | * Firebird server version 19 | */ 20 | public $firebird_version = null; 21 | 22 | /** 23 | * @see https://www.firebirdsql.org/file/documentation/release_notes/html/en/3_0/rnfb30-ddl-enhance.html#rnfb30-ddl-identity 24 | * @var boolean|null 25 | */ 26 | public $supportColumnIdentity = null; 27 | 28 | /** 29 | * @see https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-insert.html#fblangref25-dml-insert-select-unstable 30 | * @var boolean|null 31 | */ 32 | public $supportStableCursor = null; 33 | 34 | /** 35 | * @see https://bugs.php.net/bug.php?id=72931 36 | * @var boolean|null 37 | */ 38 | public $supportReturningInsert = null; 39 | 40 | /** 41 | * @see https://bugs.php.net/bug.php?id=61183 42 | * @var boolean|null 43 | */ 44 | public $supportBlobDataType = null; 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public $schemaMap = [ 50 | 'firebird' => 'edgardmessias\db\firebird\Schema', // Firebird 51 | ]; 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public $pdoClass = 'edgardmessias\db\firebird\PdoAdapter'; 57 | 58 | /** 59 | * @inheritdoc 60 | */ 61 | public $commandClass = 'edgardmessias\db\firebird\Command'; 62 | /** 63 | * @var Transaction the currently active transaction 64 | */ 65 | private $_transaction; 66 | 67 | /** 68 | * Returns the currently active transaction. 69 | * @return Transaction the currently active transaction. Null if no active transaction. 70 | */ 71 | public function getTransaction() 72 | { 73 | return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null; 74 | } 75 | 76 | /** 77 | * Starts a transaction. 78 | * @param string|null $isolationLevel The isolation level to use for this transaction. 79 | * See [[Transaction::begin()]] for details. 80 | * @return Transaction the transaction initiated 81 | */ 82 | public function beginTransaction($isolationLevel = null) 83 | { 84 | $this->open(); 85 | 86 | if (($transaction = $this->getTransaction()) === null) { 87 | $transaction = $this->_transaction = new Transaction(['db' => $this]); 88 | } 89 | $transaction->begin($isolationLevel); 90 | 91 | return $transaction; 92 | } 93 | 94 | public function close() 95 | { 96 | if ($this->pdo !== null) { 97 | $this->_transaction = null; 98 | } 99 | parent::close(); 100 | } 101 | 102 | public function init() 103 | { 104 | parent::init(); 105 | 106 | if ($this->firebird_version) { 107 | return; 108 | } 109 | 110 | try { 111 | $pdo = $this->createPdoInstance(); 112 | 113 | $server_version = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION); 114 | 115 | if (preg_match('/\w{2}-[TV](\d+\.\d+\.\d+).*remote server/', $server_version, $matches)) { 116 | $this->firebird_version = $matches[1]; 117 | } 118 | } catch (\Exception $ex) { 119 | } 120 | 121 | $supports = [ 122 | 'supportColumnIdentity' => version_compare($this->firebird_version, '3.0.0', '>='), 123 | 'supportStableCursor' => version_compare($this->firebird_version, '3.0.0', '>='), 124 | 'supportReturningInsert' => version_compare($this->firebird_version, '3.0.0', '<') || version_compare(phpversion('pdo_firebird'), '7.0.15', '>='), 125 | 'supportBlobDataType' => version_compare(phpversion('pdo_firebird'), '7.0.13', '>'), 126 | ]; 127 | 128 | foreach ($supports as $key => $value) { 129 | if ($this->{$key} === null) { 130 | $this->{$key} = $value; 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * Reset the connection after cloning. 137 | */ 138 | public function __clone() 139 | { 140 | parent::__clone(); 141 | 142 | $this->_transaction = null; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Transaction.php: -------------------------------------------------------------------------------- 1 | beginTransaction(); 25 | * try { 26 | * $connection->createCommand($sql1)->execute(); 27 | * $connection->createCommand($sql2)->execute(); 28 | * //.... other SQL executions 29 | * $transaction->commit(); 30 | * } catch (Exception $e) { 31 | * $transaction->rollBack(); 32 | * } 33 | * ~~~ 34 | * 35 | * @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]] 36 | * or [[rollBack()]]. This property is read-only. 37 | * @property string $isolationLevel The transaction isolation level to use for this transaction. This can be 38 | * one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but also a string 39 | * containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is 40 | * write-only. 41 | * 42 | * @author Edgard Lorraine Messias 43 | * @since 1.0 44 | */ 45 | class Transaction extends \yii\db\Transaction 46 | { 47 | 48 | /** 49 | * @var integer the nesting level of the transaction. 0 means the outermost level. 50 | */ 51 | private $_level = 0; 52 | 53 | /** 54 | * Returns a value indicating whether this transaction is active. 55 | * @return boolean whether this transaction is active. Only an active transaction 56 | * can [[commit()]] or [[rollBack()]]. 57 | */ 58 | public function getIsActive() 59 | { 60 | return $this->_level > 0 && $this->db && $this->db->isActive; 61 | } 62 | 63 | /** 64 | * Begins a transaction. 65 | * @param string|null $isolationLevel The [isolation level][] to use for this transaction. 66 | * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but 67 | * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. 68 | * If not specified (`null`) the isolation level will not be set explicitly and the DBMS default will be used. 69 | * 70 | * > Note: This setting does not work for PostgreSQL, where setting the isolation level before the transaction 71 | * has no effect. You have to call [[setIsolationLevel()]] in this case after the transaction has started. 72 | * 73 | * > Note: Some DBMS allow setting of the isolation level only for the whole connection so subsequent transactions 74 | * may get the same isolation level even if you did not specify any. When using this feature 75 | * you may need to set the isolation level for all transactions explicitly to avoid conflicting settings. 76 | * At the time of this writing affected DBMS are MSSQL and SQLite. 77 | * 78 | * [isolation level]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels 79 | * @throws InvalidConfigException if [[db]] is `null`. 80 | */ 81 | public function begin($isolationLevel = null) 82 | { 83 | if ($this->db === null) { 84 | throw new InvalidConfigException('Transaction::db must be set.'); 85 | } 86 | $this->db->open(); 87 | 88 | if ($this->_level == 0) { 89 | Yii::trace('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__); 90 | 91 | $this->db->trigger(Connection::EVENT_BEGIN_TRANSACTION); 92 | if ($isolationLevel !== null) { 93 | $this->db->pdo->beginTransaction(false); 94 | $this->db->getSchema()->setTransactionIsolationLevel($isolationLevel); 95 | } else { 96 | $this->db->pdo->beginTransaction(); 97 | } 98 | $this->_level = 1; 99 | 100 | return; 101 | } 102 | 103 | $schema = $this->db->getSchema(); 104 | if ($schema->supportsSavepoint()) { 105 | Yii::trace('Set savepoint ' . $this->_level, __METHOD__); 106 | $schema->createSavepoint('LEVEL' . $this->_level); 107 | } else { 108 | Yii::info('Transaction not started: nested transaction not supported', __METHOD__); 109 | } 110 | $this->_level++; 111 | } 112 | 113 | /** 114 | * Commits a transaction. 115 | * @throws Exception if the transaction is not active 116 | */ 117 | public function commit() 118 | { 119 | if (!$this->getIsActive()) { 120 | throw new Exception('Failed to commit transaction: transaction was inactive.'); 121 | } 122 | 123 | $this->_level--; 124 | if ($this->_level == 0) { 125 | Yii::trace('Commit transaction', __METHOD__); 126 | $this->db->pdo->commit(); 127 | $this->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); 128 | return; 129 | } 130 | 131 | $schema = $this->db->getSchema(); 132 | if ($schema->supportsSavepoint()) { 133 | Yii::trace('Release savepoint ' . $this->_level, __METHOD__); 134 | $schema->releaseSavepoint('LEVEL' . $this->_level); 135 | } else { 136 | Yii::info('Transaction not committed: nested transaction not supported', __METHOD__); 137 | } 138 | } 139 | 140 | /** 141 | * Rolls back a transaction. 142 | * @throws Exception if the transaction is not active 143 | */ 144 | public function rollBack() 145 | { 146 | if (!$this->getIsActive()) { 147 | // do nothing if transaction is not active: this could be the transaction is committed 148 | // but the event handler to "commitTransaction" throw an exception 149 | return; 150 | } 151 | 152 | $this->_level--; 153 | if ($this->_level == 0) { 154 | Yii::trace('Roll back transaction', __METHOD__); 155 | $this->db->pdo->rollBack(); 156 | $this->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); 157 | return; 158 | } 159 | 160 | $schema = $this->db->getSchema(); 161 | if ($schema->supportsSavepoint()) { 162 | Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__); 163 | $schema->rollBackSavepoint('LEVEL' . $this->_level); 164 | } else { 165 | Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__); 166 | // throw an exception to fail the outer transaction 167 | throw new Exception('Roll back failed: nested transaction not supported.'); 168 | } 169 | } 170 | 171 | /** 172 | * Sets the transaction isolation level for this transaction. 173 | * 174 | * This method can be used to set the isolation level while the transaction is already active. 175 | * However this is not supported by all DBMS so you might rather specify the isolation level directly 176 | * when calling [[begin()]]. 177 | * @param string $level The transaction isolation level to use for this transaction. 178 | * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but 179 | * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. 180 | * @throws Exception if the transaction is not active 181 | * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels 182 | */ 183 | public function setIsolationLevel($level) 184 | { 185 | if (!$this->getIsActive()) { 186 | throw new Exception('Failed to set isolation level: transaction was inactive.'); 187 | } 188 | Yii::trace('Setting transaction isolation level to ' . $level, __METHOD__); 189 | $this->db->getSchema()->setTransactionIsolationLevel($level); 190 | } 191 | 192 | /** 193 | * @return integer The current nesting level of the transaction. 194 | * @since 2.0.8 195 | */ 196 | public function getLevel() 197 | { 198 | return $this->_level; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | 19 | * @since 2.0 20 | */ 21 | class QueryBuilder extends \yii\db\QueryBuilder 22 | { 23 | 24 | /** 25 | * @var array mapping from abstract column types (keys) to physical column types (values). 26 | */ 27 | public $typeMap = [ 28 | Schema::TYPE_PK => 'integer NOT NULL PRIMARY KEY', 29 | Schema::TYPE_UPK => 'integer NOT NULL PRIMARY KEY', 30 | Schema::TYPE_BIGPK => 'bigint NOT NULL PRIMARY KEY', 31 | Schema::TYPE_UBIGPK => 'bigint NOT NULL PRIMARY KEY', 32 | Schema::TYPE_CHAR => 'char(1)', 33 | Schema::TYPE_STRING => 'varchar(255)', 34 | Schema::TYPE_TEXT => 'blob sub_type text', 35 | Schema::TYPE_SMALLINT => 'smallint', 36 | Schema::TYPE_INTEGER => 'integer', 37 | Schema::TYPE_BIGINT => 'bigint', 38 | Schema::TYPE_FLOAT => 'float', 39 | Schema::TYPE_DOUBLE => 'double precision', 40 | Schema::TYPE_DECIMAL => 'numeric(10,0)', 41 | Schema::TYPE_DATETIME => 'timestamp', 42 | Schema::TYPE_TIMESTAMP => 'timestamp', 43 | Schema::TYPE_TIME => 'time', 44 | Schema::TYPE_DATE => 'date', 45 | Schema::TYPE_BINARY => 'blob', 46 | Schema::TYPE_BOOLEAN => 'smallint', 47 | Schema::TYPE_MONEY => 'numeric(18,4)', 48 | ]; 49 | 50 | public function init() 51 | { 52 | if ($this->db->supportColumnIdentity) { 53 | $this->typeMap[Schema::TYPE_PK] = 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'; 54 | $this->typeMap[Schema::TYPE_UPK] = 'integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'; 55 | $this->typeMap[Schema::TYPE_BIGPK] = 'bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'; 56 | $this->typeMap[Schema::TYPE_UBIGPK] = 'bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'; 57 | $this->typeMap[Schema::TYPE_BOOLEAN] = 'boolean'; 58 | } 59 | 60 | parent::init(); 61 | } 62 | 63 | protected function defaultExpressionBuilders() 64 | { 65 | return array_merge(parent::defaultExpressionBuilders(), [ 66 | 'yii\db\Expression' => 'edgardmessias\db\firebird\ExpressionBuilder', 67 | 'yii\db\conditions\InCondition' => 'edgardmessias\db\firebird\conditions\InConditionBuilder', 68 | 'yii\db\conditions\LikeCondition' => 'edgardmessias\db\firebird\conditions\LikeConditionBuilder', 69 | ]); 70 | } 71 | 72 | /** 73 | * Generates a SELECT SQL statement from a [[Query]] object. 74 | * @param Query $query the [[Query]] object from which the SQL statement will be generated. 75 | * @param array $params the parameters to be bound to the generated SQL statement. These parameters will 76 | * be included in the result with the additional parameters generated during the query building process. 77 | * @return array the generated SQL statement (the first array element) and the corresponding 78 | * parameters to be bound to the SQL statement (the second array element). The parameters returned 79 | * include those provided in `$params`. 80 | */ 81 | public function build($query, $params = []) 82 | { 83 | $query = $query->prepare($this); 84 | 85 | $params = empty($params) ? $query->params : array_merge($params, $query->params); 86 | 87 | $clauses = [ 88 | $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption), 89 | $this->buildFrom($query->from, $params), 90 | $this->buildJoin($query->join, $params), 91 | $this->buildWhere($query->where, $params), 92 | $this->buildGroupBy($query->groupBy), 93 | $this->buildHaving($query->having, $params), 94 | ]; 95 | 96 | $sql = implode($this->separator, array_filter($clauses)); 97 | $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset); 98 | 99 | if (!empty($query->orderBy)) { 100 | foreach ($query->orderBy as $expression) { 101 | if ($expression instanceof Expression) { 102 | $params = array_merge($params, $expression->params); 103 | } 104 | } 105 | } 106 | if (!empty($query->groupBy)) { 107 | foreach ($query->groupBy as $expression) { 108 | if ($expression instanceof Expression) { 109 | $params = array_merge($params, $expression->params); 110 | } 111 | } 112 | } 113 | 114 | $union = $this->buildUnion($query->union, $params); 115 | if ($union !== '') { 116 | $sql = "$sql{$this->separator}$union"; 117 | } 118 | 119 | return [$sql, $params]; 120 | } 121 | 122 | /** 123 | * @inheritdoc 124 | */ 125 | public function buildSelect($columns, &$params, $distinct = false, $selectOption = null) 126 | { 127 | if (is_array($columns)) { 128 | foreach ($columns as $i => $column) { 129 | if (!is_string($column)) { 130 | continue; 131 | } 132 | $matches = []; 133 | if (preg_match('/^(COUNT|SUM|AVG|MIN|MAX)\([\{\[]{0,2}(\w+|\*)[\}\]]{0,2}\)$/i', $column, $matches)) { 134 | $function = $matches[1]; 135 | $alias = $matches[2] != '*' ? $matches[2] : 'ALL'; 136 | 137 | $columns[$i] = "{$column} AS {$function}_{$alias}"; 138 | } 139 | } 140 | } 141 | 142 | return parent::buildSelect($columns, $params, $distinct, $selectOption); 143 | } 144 | 145 | public function buildLimit($limit, $offset) 146 | { 147 | $sql = ''; 148 | if ($this->hasLimit($limit)) { 149 | if ($limit instanceof \yii\db\ExpressionInterface) { 150 | $limit = "($limit)"; 151 | } 152 | $sql = 'FIRST ' . $limit; 153 | } 154 | if ($this->hasOffset($offset)) { 155 | if ($offset instanceof \yii\db\ExpressionInterface) { 156 | $offset = "($offset)"; 157 | } 158 | $sql .= ' SKIP ' . $offset; 159 | } 160 | 161 | return ltrim($sql); 162 | } 163 | 164 | /** 165 | * @inheritdoc 166 | */ 167 | public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset) 168 | { 169 | 170 | $orderBy = $this->buildOrderBy($orderBy); 171 | if ($orderBy !== '') { 172 | $sql .= $this->separator . $orderBy; 173 | } 174 | 175 | $limit = $this->buildLimit($limit, $offset); 176 | if ($limit !== '') { 177 | $sql = preg_replace('/^SELECT /i', 'SELECT ' . $limit . ' ', $sql, 1); 178 | } 179 | 180 | return $sql; 181 | } 182 | 183 | /** 184 | * @param array $unions 185 | * @param array $params the binding parameters to be populated 186 | * @return string the UNION clause built from [[Query::$union]]. 187 | */ 188 | public function buildUnion($unions, &$params) 189 | { 190 | if (empty($unions)) { 191 | return ''; 192 | } 193 | 194 | $result = ''; 195 | 196 | foreach ($unions as $i => $union) { 197 | $query = $union['query']; 198 | if ($query instanceof Query) { 199 | list($unions[$i]['query'], $params) = $this->build($query, $params); 200 | } 201 | 202 | $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . $unions[$i]['query'] . ' '; 203 | } 204 | 205 | return trim($result); 206 | } 207 | 208 | /** 209 | * @inheritdoc 210 | */ 211 | public function prepareInsertValues($table, $columns, $params = []) 212 | { 213 | $schema = $this->db->getSchema(); 214 | $tableSchema = $schema->getTableSchema($table); 215 | $columnSchemas = $tableSchema !== null ? $tableSchema->columns : []; 216 | 217 | //Empty insert 218 | if (empty($columns) && !empty($columnSchemas)) { 219 | $columns = []; 220 | foreach ($columnSchemas as $columnSchema) { 221 | if (!$columnSchema->autoIncrement) { 222 | $columns[$columnSchema->name] = $columnSchema->defaultValue; 223 | } 224 | } 225 | } 226 | 227 | if (is_array($columns)) { 228 | foreach ($columns as $name => $value) { 229 | if ($value instanceof \yii\db\ExpressionInterface) { 230 | continue; 231 | } 232 | if ($value instanceof \yii\db\PdoValue) { 233 | continue; 234 | } 235 | if (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) { 236 | $columns[$name] = [$value, \PDO::PARAM_LOB]; 237 | } 238 | } 239 | } 240 | 241 | return parent::prepareInsertValues($table, $columns, $params); 242 | } 243 | 244 | /** 245 | * @inheritdoc 246 | */ 247 | protected function prepareInsertSelectSubQuery($columns, $schema, $params = []) 248 | { 249 | /** 250 | * @see https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-insert.html#fblangref25-dml-insert-select-unstable 251 | */ 252 | if (!$this->db->supportStableCursor) { 253 | throw new NotSupportedException('Firebird < 3.0.0 has the "Unstable Cursor" problem'); 254 | } 255 | 256 | return parent::prepareInsertSelectSubQuery($columns, $schema, $params); 257 | } 258 | 259 | /** 260 | * @inheritdoc 261 | */ 262 | public function prepareUpdateSets($table, $columns, $params = []) 263 | { 264 | $schema = $this->db->getSchema(); 265 | $tableSchema = $schema->getTableSchema($table); 266 | $columnSchemas = $tableSchema !== null ? $tableSchema->columns : []; 267 | 268 | foreach ($columns as $name => $value) { 269 | if ($value instanceof \yii\db\ExpressionInterface) { 270 | continue; 271 | } 272 | if ($value instanceof \yii\db\PdoValue) { 273 | continue; 274 | } 275 | if (isset($columnSchemas[$name]) && in_array($columnSchemas[$name]->type, [Schema::TYPE_TEXT, Schema::TYPE_BINARY])) { 276 | $columns[$name] = [$value, \PDO::PARAM_LOB]; 277 | } 278 | } 279 | return parent::prepareUpdateSets($table, $columns, $params); 280 | } 281 | 282 | public function rawInsert($table, $columns, $values, &$params) 283 | { 284 | $schema = $this->db->getSchema(); 285 | if (($tableSchema = $schema->getTableSchema($table)) !== null) { 286 | $columnSchemas = $tableSchema->columns; 287 | } else { 288 | $columnSchemas = []; 289 | } 290 | 291 | $cs = []; 292 | $vs = []; 293 | foreach ($values as $i => $value) { 294 | if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) { 295 | $value = $columnSchemas[$columns[$i]]->dbTypecast($value); 296 | } 297 | if (is_string($value)) { 298 | // Quote the {{table}} to {{@table@}} for insert 299 | $value = preg_replace('/(\{\{)(%?[\w\-\. ]+%?)(\}\})/', '\1@\2@\3', $value); 300 | $value = $schema->quoteValue($value); 301 | } elseif (is_float($value)) { 302 | // ensure type cast always has . as decimal separator in all locales 303 | $value = \yii\helpers\StringHelper::floatToString($value); 304 | } elseif ($value === false) { 305 | $value = 0; 306 | } elseif ($value === null) { 307 | $value = 'NULL'; 308 | } elseif ($value instanceof ExpressionInterface) { 309 | $value = $this->buildExpression($value, $params); 310 | } 311 | 312 | if (isset($columns[$i])) { 313 | $cs[] = $schema->quoteColumnName($columns[$i]); 314 | } 315 | $vs[] = $value; 316 | } 317 | 318 | if (empty($vs)) { 319 | return 'INSERT INTO ' . $schema->quoteTableName($table) 320 | . ' DEFAULT VALUES'; 321 | } 322 | 323 | return 'INSERT INTO ' . $schema->quoteTableName($table) 324 | . ' (' . implode(', ', $cs) . ') VALUES (' . implode(', ', $vs) . ')'; 325 | } 326 | 327 | /** 328 | * @inheritdoc 329 | */ 330 | public function batchInsert($table, $columns, $rows, &$params = []) 331 | { 332 | if (empty($rows)) { 333 | return ''; 334 | } 335 | 336 | $schema = $this->db->getSchema(); 337 | if (($tableSchema = $schema->getTableSchema($table)) !== null) { 338 | $columnSchemas = $tableSchema->columns; 339 | } else { 340 | $columnSchemas = []; 341 | } 342 | 343 | $values = []; 344 | foreach ($rows as $row) { 345 | $values[] = $this->rawInsert($table, $columns, $row, $params) . ';'; 346 | } 347 | 348 | return 'EXECUTE block AS BEGIN ' . implode(' ', $values) . ' END;'; 349 | } 350 | 351 | /** 352 | * {@inheritdoc} 353 | * @see https://www.firebirdsql.org/refdocs/langrefupd21-update-or-insert.html 354 | * @see https://www.firebirdsql.org/refdocs/langrefupd21-merge.html 355 | */ 356 | public function upsert($table, $insertColumns, $updateColumns, &$params) 357 | { 358 | /** @var Constraint[] $constraints */ 359 | list($uniqueNames, $insertNames, $updateNames) = $this->prepareUpsertColumns($table, $insertColumns, $updateColumns, $constraints); 360 | if (empty($uniqueNames)) { 361 | return 'UPDATE OR ' . $this->insert($table, $insertColumns, $params); 362 | } 363 | 364 | $onCondition = ['or']; 365 | $quotedTableName = $this->db->quoteTableName($table); 366 | foreach ($constraints as $constraint) { 367 | $constraintCondition = ['and']; 368 | foreach ($constraint->columnNames as $name) { 369 | $quotedName = $this->db->quoteColumnName($name); 370 | $constraintCondition[] = "$quotedTableName.$quotedName=\"EXCLUDED\".$quotedName"; 371 | } 372 | $onCondition[] = $constraintCondition; 373 | } 374 | $on = $this->buildCondition($onCondition, $params); 375 | list(, $placeholders, $values, $params) = $this->prepareInsertValues($table, $insertColumns, $params); 376 | if (!empty($placeholders)) { 377 | /** @var Schema $schema */ 378 | $schema = $this->db->getSchema(); 379 | $tableSchema = $schema->getTableSchema($table); 380 | $columnSchemas = $tableSchema !== null ? $tableSchema->columns : []; 381 | 382 | $usingSelectValues = []; 383 | foreach ($insertNames as $index => $name) { 384 | if (isset($columnSchemas[$name])) { 385 | $usingSelectValues[$name] = new Expression("CAST({$placeholders[$index]} AS {$columnSchemas[$name]->dbType})"); 386 | } 387 | } 388 | $usingSubQuery = (new Query()) 389 | ->select($usingSelectValues) 390 | ->from('RDB$DATABASE'); 391 | list($usingValues, $params) = $this->build($usingSubQuery, $params); 392 | } 393 | $mergeSql = 'MERGE INTO ' . $this->db->quoteTableName($table) . ' ' 394 | . 'USING (' . (isset($usingValues) ? $usingValues : ltrim($values, ' ')) . ') "EXCLUDED" ' 395 | . "ON ($on)"; 396 | $insertValues = []; 397 | foreach ($insertNames as $name) { 398 | $quotedName = $this->db->quoteColumnName($name); 399 | if (strrpos($quotedName, '.') === false) { 400 | $quotedName = '"EXCLUDED".' . $quotedName; 401 | } 402 | $insertValues[] = $quotedName; 403 | } 404 | $insertSql = 'INSERT (' . implode(', ', $insertNames) . ')' 405 | . ' VALUES (' . implode(', ', $insertValues) . ')'; 406 | if ($updateColumns === false) { 407 | return "$mergeSql WHEN NOT MATCHED THEN $insertSql"; 408 | } 409 | 410 | if ($updateColumns === true) { 411 | $updateColumns = []; 412 | foreach ($updateNames as $name) { 413 | $quotedName = $this->db->quoteColumnName($name); 414 | if (strrpos($quotedName, '.') === false) { 415 | $quotedName = '"EXCLUDED".' . $quotedName; 416 | } 417 | $updateColumns[$name] = new Expression($quotedName); 418 | } 419 | } 420 | list($updates, $params) = $this->prepareUpdateSets($table, $updateColumns, $params); 421 | $updateSql = 'UPDATE SET ' . implode(', ', $updates); 422 | return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql"; 423 | } 424 | 425 | /** 426 | * @inheritdoc 427 | */ 428 | public function renameTable($oldName, $newName) 429 | { 430 | throw new \yii\base\NotSupportedException($this->db->getDriverName() . ' does not support rename table.'); 431 | } 432 | 433 | /** 434 | * @inheritdoc 435 | */ 436 | public function truncateTable($table) 437 | { 438 | return 'DELETE FROM ' . $this->db->quoteTableName($table); 439 | } 440 | 441 | /** 442 | * @inheritdoc 443 | */ 444 | public function dropColumn($table, $column) 445 | { 446 | return 'ALTER TABLE ' . $this->db->quoteTableName($table) 447 | . ' DROP ' . $this->db->quoteColumnName($column); 448 | } 449 | 450 | /** 451 | * @inheritdoc 452 | */ 453 | public function renameColumn($table, $oldName, $newName) 454 | { 455 | return 'ALTER TABLE ' . $this->db->quoteTableName($table) 456 | . ' ALTER ' . $this->db->quoteColumnName($oldName) 457 | . ' TO ' . $this->db->quoteColumnName($newName); 458 | } 459 | 460 | /** 461 | * @inheritdoc 462 | */ 463 | public function alterColumn($table, $column, $type) 464 | { 465 | $schema = $this->db->getSchema(); 466 | $tableSchema = $schema->getTableSchema($table); 467 | $columnSchema = $tableSchema->getColumn($column); 468 | 469 | $allowNullNewType = !preg_match('/not +null/i', $type); 470 | 471 | $type = preg_replace('/ +(not)? *null/i', '', $type); 472 | 473 | $hasType = false; 474 | 475 | $matches = []; 476 | if (isset($this->typeMap[$type])) { 477 | $hasType = true; 478 | } elseif (preg_match('/^(\w+)[\( ]/', $type, $matches)) { 479 | if (isset($this->typeMap[$matches[1]])) { 480 | $hasType = true; 481 | } 482 | } 483 | 484 | $baseSql = 'ALTER TABLE ' . $this->db->quoteTableName($table) 485 | . ' ALTER ' . $this->db->quoteColumnName($column) 486 | . (($hasType) ? ' TYPE ' : ' ') . $this->getColumnType($type); 487 | 488 | if (version_compare($this->db->firebird_version, '3.0.0', '>=')) { 489 | $nullSql = false; 490 | 491 | if ($columnSchema->allowNull != $allowNullNewType) { 492 | $nullSql = 'ALTER TABLE ' . $this->db->quoteTableName($table) 493 | . ' ALTER ' . $this->db->quoteColumnName($column) 494 | . ($allowNullNewType ? ' DROP' : ' SET') 495 | . ' NOT NULL'; 496 | } 497 | 498 | $sql = 'EXECUTE BLOCK AS BEGIN' 499 | . ' EXECUTE STATEMENT ' . $this->db->quoteValue($baseSql) . ';'; 500 | 501 | /** 502 | * In any case (whichever option you choose), make sure that the column doesn't have any NULLs. 503 | * Firebird will not check it for you. Later when you backup the database, everything is fine, 504 | * but restore will fail as the NOT NULL column has NULLs in it. To be safe, each time you change from NULL to NOT NULL. 505 | */ 506 | if (!$allowNullNewType) { 507 | $sql .= ' UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . $this->db->quoteColumnName($column) . ' = 0' 508 | . ' WHERE ' . $this->db->quoteColumnName($column) . ' IS NULL;'; 509 | } 510 | 511 | if ($nullSql) { 512 | $sql .= ' EXECUTE STATEMENT ' . $this->db->quoteValue($nullSql) . ';'; 513 | } 514 | 515 | $sql .= ' END'; 516 | return $sql; 517 | } 518 | 519 | if ($columnSchema->allowNull == $allowNullNewType) { 520 | return $baseSql; 521 | } else { 522 | $sql = 'EXECUTE BLOCK AS BEGIN' 523 | . ' EXECUTE STATEMENT ' . $this->db->quoteValue($baseSql) . ';' 524 | . ' UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = ' . ($allowNullNewType ? 'NULL' : '1') 525 | . ' WHERE UPPER(RDB$FIELD_NAME) = UPPER(\'' . $column . '\') AND UPPER(RDB$RELATION_NAME) = UPPER(\'' . $table . '\');'; 526 | /** 527 | * In any case (whichever option you choose), make sure that the column doesn't have any NULLs. 528 | * Firebird will not check it for you. Later when you backup the database, everything is fine, 529 | * but restore will fail as the NOT NULL column has NULLs in it. To be safe, each time you change from NULL to NOT NULL. 530 | */ 531 | if (!$allowNullNewType) { 532 | $sql .= ' UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . $this->db->quoteColumnName($column) . ' = 0' 533 | . ' WHERE ' . $this->db->quoteColumnName($column) . ' IS NULL;'; 534 | } 535 | $sql .= ' END'; 536 | return $sql; 537 | } 538 | } 539 | 540 | /** 541 | * @inheritdoc 542 | */ 543 | public function dropIndex($name, $table) 544 | { 545 | return 'DROP INDEX ' . $this->db->quoteTableName($name); 546 | } 547 | 548 | /** 549 | * {@inheritdoc} 550 | */ 551 | public function addDefaultValue($name, $table, $column, $value) 552 | { 553 | return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' 554 | . $this->db->quoteColumnName($column) . ' SET DEFAULT ' . $this->db->quoteValue($value); 555 | } 556 | 557 | /** 558 | * {@inheritdoc} 559 | */ 560 | public function dropDefaultValue($name, $table) 561 | { 562 | return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' 563 | . $this->db->quoteColumnName($name) . ' DROP DEFAULT'; 564 | } 565 | 566 | /** 567 | * @inheritdoc 568 | */ 569 | public function resetSequence($table, $value = null) 570 | { 571 | $tableSchema = $this->db->getTableSchema($table); 572 | if ($tableSchema === null) { 573 | throw new InvalidParamException("Table not found: $table"); 574 | } 575 | if ($tableSchema->sequenceName === null) { 576 | throw new InvalidParamException("There is not sequence associated with table '$table'."); 577 | } 578 | 579 | if ($value !== null) { 580 | $value = (int) $value; 581 | } else { 582 | // use master connection to get the biggest PK value 583 | $value = $this->db->useMaster(function (Connection $db) use ($tableSchema) { 584 | $key = false; 585 | foreach ($tableSchema->primaryKey as $name) { 586 | if ($tableSchema->columns[$name]->autoIncrement) { 587 | $key = $name; 588 | break; 589 | } 590 | } 591 | if ($key === false) { 592 | return 0; 593 | } 594 | return $db->createCommand("SELECT MAX({$this->db->quoteColumnName($key)}) FROM {$this->db->quoteTableName($tableSchema->name)}")->queryScalar(); 595 | }) + 1; 596 | } 597 | 598 | return "ALTER SEQUENCE {$this->db->quoteColumnName($tableSchema->sequenceName)} RESTART WITH $value"; 599 | } 600 | 601 | /** 602 | * @inheritdoc 603 | */ 604 | public function createTable($table, $columns, $options = null) 605 | { 606 | $sql = parent::createTable($table, $columns, $options); 607 | 608 | if ($this->db->supportColumnIdentity) { 609 | return $sql; 610 | } 611 | 612 | foreach ($columns as $name => $type) { 613 | if (!is_string($name)) { 614 | continue; 615 | } 616 | 617 | if (strpos($type, Schema::TYPE_PK) === 0 || strpos($type, Schema::TYPE_BIGPK) === 0) { 618 | $sqlTrigger = <<db->quoteTableName($table)} 620 | ACTIVE BEFORE INSERT POSITION 0 621 | AS 622 | BEGIN 623 | if (NEW.{$this->db->quoteColumnName($name)} is NULL) then NEW.{$this->db->quoteColumnName($name)} = NEXT VALUE FOR seq_{$table}_{$name}; 624 | END 625 | SQLTRIGGER; 626 | 627 | $sqlBlock = <<db->quoteValue($sql)}; 631 | EXECUTE STATEMENT {$this->db->quoteValue("CREATE SEQUENCE seq_{$table}_{$name}")}; 632 | EXECUTE STATEMENT {$this->db->quoteValue($sqlTrigger)}; 633 | END; 634 | SQL; 635 | 636 | return $sqlBlock; 637 | } 638 | } 639 | 640 | return $sql; 641 | } 642 | 643 | /** 644 | * @inheritdoc 645 | */ 646 | public function dropTable($table) 647 | { 648 | $sql = parent::dropTable($table); 649 | 650 | $tableSchema = $this->db->getTableSchema($table); 651 | if ($tableSchema === null || $tableSchema->sequenceName === null) { 652 | return $sql; 653 | } 654 | 655 | /** 656 | * Not drop sequence for sequence "GENERATED BY DEFAULT AS IDENTITY" 657 | */ 658 | if ($this->db->supportColumnIdentity) { 659 | $sqlUserSquence = 'SELECT 1 FROM RDB$GENERATORS 660 | WHERE RDB$SYSTEM_FLAG = 0 AND RDB$GENERATOR_NAME = :name'; 661 | 662 | $is_user_sequence = $this->db->createCommand($sqlUserSquence, [':name' => $tableSchema->sequenceName])->queryScalar(); 663 | 664 | if (!$is_user_sequence) { 665 | return $sql; 666 | } 667 | } 668 | 669 | $sqlBlock = <<db->quoteValue($sql)}; 673 | EXECUTE STATEMENT {$this->db->quoteValue("DROP SEQUENCE {$tableSchema->sequenceName}")}; 674 | END; 675 | SQL; 676 | return $sqlBlock; 677 | } 678 | 679 | /** 680 | * Creates a SELECT EXISTS() SQL statement. 681 | * @param string $rawSql the subquery in a raw form to select from. 682 | * @return string the SELECT EXISTS() SQL statement. 683 | * 684 | * @since 2.0.8 685 | */ 686 | public function selectExists($rawSql) 687 | { 688 | return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END FROM RDB$DATABASE'; 689 | } 690 | } 691 | -------------------------------------------------------------------------------- /src/Schema.php: -------------------------------------------------------------------------------- 1 | index type. This 33 | * property is read-only. 34 | * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only. 35 | * 36 | * @author Edgard Lorraine Messias 37 | * @since 2.0 38 | */ 39 | class Schema extends BaseSchema implements ConstraintFinderInterface 40 | { 41 | use ViewFinderTrait; 42 | use ConstraintFinderTrait; 43 | 44 | private $_lastInsertID = null; 45 | 46 | /** 47 | * @var array map of DB errors and corresponding exceptions 48 | * If left part is found in DB error message exception class from the right part is used. 49 | */ 50 | public $exceptionMap = [ 51 | 'SQLSTATE[23' => 'yii\db\IntegrityException', 52 | 'SQLSTATE[HY000]: General error: -803 violation of PRIMARY' => 'yii\db\IntegrityException', 53 | ]; 54 | public $reservedWords = [ 55 | 'ADD', 56 | 'ADMIN', 57 | 'ALL', 58 | 'ALTER', 59 | 'AND', 60 | 'ANY', 61 | 'AS', 62 | 'AT', 63 | 'AVG', 64 | 'BEGIN', 65 | 'BETWEEN', 66 | 'BIGINT', 67 | 'BIT_LENGTH', 68 | 'BLOB', 69 | 'BOTH', 70 | 'BOOLEAN', 71 | 'BY', 72 | 'CASE', 73 | 'CAST', 74 | 'CHAR', 75 | 'CHAR_LENGTH', 76 | 'CHARACTER', 77 | 'CHARACTER_LENGTH', 78 | 'CHECK', 79 | 'CLOSE', 80 | 'COLLATE', 81 | 'COLUMN', 82 | 'COMMIT', 83 | 'CONNECT', 84 | 'CONSTRAINT', 85 | 'CORR', 86 | 'COUNT', 87 | 'COVAR_POP', 88 | 'CREATE', 89 | 'CROSS', 90 | 'CURRENT', 91 | 'CURRENT_CONNECTION', 92 | 'CURRENT_DATE', 93 | 'CURRENT_ROLE', 94 | 'CURRENT_TIME', 95 | 'CURRENT_TIMESTAMP', 96 | 'CURRENT_TRANSACTION', 97 | 'CURRENT_USER', 98 | 'CURSOR', 99 | 'DATE', 100 | 'DAY', 101 | 'DEC', 102 | 'DECIMAL', 103 | 'DECLARE', 104 | 'DEFAULT', 105 | 'DELETE', 106 | 'DELETING', 107 | 'DETERMINISTIC', 108 | 'DISCONNECT', 109 | 'DISTINCT', 110 | 'DOUBLE', 111 | 'DROP', 112 | 'ELSE', 113 | 'END', 114 | 'ESCAPE', 115 | 'EXECUTE', 116 | 'EXISTS', 117 | 'EXTERNAL', 118 | 'EXRACT', 119 | 'FALSE', 120 | 'FETCH', 121 | 'FILTER', 122 | 'FLOAT', 123 | 'FOR', 124 | 'FOREIGN', 125 | 'FROM', 126 | 'FULL', 127 | 'FUNCTION', 128 | 'GDSCODE', 129 | 'GLOBAL', 130 | 'GRANT', 131 | 'GROUP', 132 | 'HAVING', 133 | 'HOUR', 134 | 'IN', 135 | 'INDEX', 136 | 'INNER', 137 | 'INSENSITIVE', 138 | 'INSERT', 139 | 'INSERTING', 140 | 'INT', 141 | 'INTEGER', 142 | 'INTO', 143 | 'IS', 144 | 'JOIN', 145 | 'LEADING', 146 | 'LEFT', 147 | 'LIKE', 148 | 'LONG', 149 | 'LOWER', 150 | 'MAX', 151 | 'MAXIMUM_SEGMENT', 152 | 'MERGE', 153 | 'MIN', 154 | 'MINUTE', 155 | 'MONTH', 156 | 'NATIONAL', 157 | 'NATURAL', 158 | 'NCHAR', 159 | 'NO', 160 | 'NOT', 161 | 'NULL', 162 | 'NUMERIC', 163 | 'OCTET_LENGTH', 164 | 'OF', 165 | 'OFFSET', 166 | 'ON', 167 | 'OPEN', 168 | 'OR', 169 | 'ORDER', 170 | 'OUTER', 171 | 'OVER', 172 | 'PARAMETER', 173 | 'PASSWORD', 174 | 'PLAN', 175 | 'POSITION', 176 | 'POST_EVENT', 177 | 'PRECISION', 178 | 'PRIMARY', 179 | 'PROCEDURE', 180 | 'RDB$DB_KEY', 181 | 'RDB$RECORD_VERSION', 182 | 'REAL', 183 | 'RECORD_VERSION', 184 | 'RECREATE', 185 | 'RECURSIVE', 186 | 'REFERENCES', 187 | 'REGR_AVGX', 188 | 'REGR_AVGY', 189 | 'REGR_COUNT', 190 | 'REGR_INTERCEPT', 191 | 'REGR_R2', 192 | 'REGR_SLOPE', 193 | 'REGR_SXX', 194 | 'REGR_SXY', 195 | 'REGR_SYY', 196 | 'RELEASE', 197 | 'RETURN', 198 | 'RETURNING_VALUES', 199 | 'RETURNS', 200 | 'REVOKE', 201 | 'RIGHT', 202 | 'ROLLBACK', 203 | 'ROW', 204 | 'ROWS', 205 | 'ROW_COUNT', 206 | 'SAVEPOINT', 207 | 'SCROLL', 208 | 'SECOND', 209 | 'SELECT', 210 | 'SENSITIVE', 211 | 'SET', 212 | 'SIMILAR', 213 | 'SOME', 214 | 'SQLCODE', 215 | 'SQLSTATE', 216 | 'START', 217 | 'STDDEV_POP', 218 | 'STDDEV_SAMP', 219 | 'SUM', 220 | 'TABLE', 221 | 'THEN', 222 | 'TIME', 223 | 'TIMESTAMP', 224 | 'TO', 225 | 'TRAILING', 226 | 'TRIGGER', 227 | 'TRIM', 228 | 'TRUE', 229 | 'UNION', 230 | 'UNIQUE', 231 | 'UNKNOWN', 232 | 'UPDATE', 233 | 'UPDATING', 234 | 'UPPER', 235 | 'USER', 236 | 'USING', 237 | 'VALUE', 238 | 'VALUES', 239 | 'VARCHAR', 240 | 'VARIABLE', 241 | 'VARYING', 242 | 'VAR_POP', 243 | 'VAR_SAMP', 244 | 'VIEW', 245 | 'WHEN', 246 | 'WHERE', 247 | 'WHILE', 248 | 'WITH', 249 | 'YEAR', 250 | ]; 251 | 252 | /** 253 | * @var array mapping from physical column types (keys) to abstract column types (values) 254 | */ 255 | public $typeMap = [ 256 | 'bigint' => self::TYPE_BIGINT, 257 | 'char' => self::TYPE_CHAR, 258 | 'varchar' => self::TYPE_STRING, 259 | 'timestamp' => self::TYPE_TIMESTAMP, 260 | 'decimal' => self::TYPE_DECIMAL, 261 | 'float' => self::TYPE_FLOAT, 262 | 'blob' => self::TYPE_BINARY, 263 | 'integer' => self::TYPE_INTEGER, 264 | 'blob sub_type text' => self::TYPE_TEXT, 265 | 'numeric' => self::TYPE_DECIMAL, 266 | 'double precision' => self::TYPE_DOUBLE, 267 | 'smallint' => self::TYPE_SMALLINT, 268 | ]; 269 | 270 | /** 271 | * {@inheritdoc} 272 | */ 273 | protected function resolveTableName($name) 274 | { 275 | $resolvedName = new TableSchema(); 276 | $this->resolveTableNames($resolvedName, $name); 277 | return $resolvedName; 278 | } 279 | 280 | /** 281 | * Creates a query builder for the database. 282 | * This method may be overridden by child classes to create a DBMS-specific query builder. 283 | * @return QueryBuilder query builder instance 284 | */ 285 | public function createQueryBuilder() 286 | { 287 | return new QueryBuilder($this->db); 288 | } 289 | 290 | /** 291 | * @inheritdoc 292 | */ 293 | public function createColumnSchemaBuilder($type, $length = null) 294 | { 295 | return new ColumnSchemaBuilder($type, $length); 296 | } 297 | 298 | public function quoteSimpleTableName($name) 299 | { 300 | if ($this->db->tablePrefix !== '') { 301 | return $name; 302 | } 303 | 304 | $word = strtoupper(str_replace('%', '', $name)); 305 | if (in_array($word, $this->reservedWords)) { 306 | return strpos($name, '"') !== false ? $name : '"' . $name . '"'; 307 | } 308 | 309 | return $name; 310 | } 311 | 312 | public function quoteSimpleColumnName($name) 313 | { 314 | if (in_array(strtoupper($name), $this->reservedWords)) { 315 | return parent::quoteSimpleColumnName($name); 316 | } 317 | return $name; 318 | } 319 | 320 | protected function loadTableSchema($name) 321 | { 322 | $table = $this->resolveTableName($name); 323 | if ($this->findColumns($table)) { 324 | $this->findConstraints($table); 325 | return $table; 326 | } 327 | return null; 328 | } 329 | 330 | public function getPdoType($data) 331 | { 332 | static $typeMap = [ 333 | // php type => PDO type 334 | 'boolean' => \PDO::PARAM_INT, 335 | 'integer' => \PDO::PARAM_INT, 336 | 'string' => \PDO::PARAM_STR, 337 | 'resource' => \PDO::PARAM_LOB, 338 | 'NULL' => \PDO::PARAM_NULL, 339 | ]; 340 | $type = gettype($data); 341 | 342 | return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; 343 | } 344 | 345 | /** 346 | * 347 | * @param TableSchema $table 348 | * @param string $name 349 | */ 350 | protected function resolveTableNames($table, $name) 351 | { 352 | $parts = explode('.', str_replace('"', '', $name)); 353 | if (isset($parts[1])) { 354 | $table->schemaName = $parts[0]; 355 | $table->name = strtolower($parts[1]); 356 | $table->fullName = $this->quoteTableName($table->schemaName) . '.' . $this->quoteTableName($table->name); 357 | } else { 358 | $table->name = strtolower($parts[0]); 359 | $table->fullName = $this->quoteTableName($table->name); 360 | } 361 | } 362 | 363 | /** 364 | * Collects the table column metadata. 365 | * 366 | * @param TableSchema $table the table metadata 367 | * @return boolean whether the table exists in the database 368 | */ 369 | protected function findColumns($table) 370 | { 371 | // Zoggo - Converted sql to use join syntax 372 | // robregonm - Added isAutoInc 373 | $sql = 'SELECT 374 | rel.rdb$field_name AS fname, 375 | rel.rdb$default_source AS fdefault, 376 | fld.rdb$field_type AS fcodtype, 377 | fld.rdb$field_sub_type AS fcodsubtype, 378 | fld.rdb$field_length AS flength, 379 | fld.rdb$character_length AS fcharlength, 380 | fld.rdb$field_scale AS fscale, 381 | fld.rdb$field_precision AS fprecision, 382 | rel.rdb$null_flag AS fnull, 383 | rel.rdb$description AS fcomment, 384 | fld.rdb$default_value AS fdefault_value,'; 385 | 386 | if ($this->db->supportColumnIdentity) { 387 | $sql .= ' 388 | rel.rdb$generator_name AS fgenerator_name,'; 389 | } 390 | 391 | $sql .= ' 392 | (SELECT RDB$TRIGGER_SOURCE FROM RDB$TRIGGERS 393 | WHERE RDB$SYSTEM_FLAG = 0 394 | AND UPPER(RDB$RELATION_NAME)=UPPER(\'' . $table->name . '\') 395 | AND RDB$TRIGGER_TYPE = 1 396 | AND RDB$TRIGGER_INACTIVE = 0 397 | AND (UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=GEN_ID%\' 398 | OR UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=NEXTVALUEFOR%\')) 399 | AS fautoinc 400 | FROM 401 | rdb$relation_fields rel 402 | JOIN rdb$fields fld ON rel.rdb$field_source=fld.rdb$field_name 403 | WHERE 404 | UPPER(rel.rdb$relation_name)=UPPER(\'' . $table->name . '\') 405 | ORDER BY 406 | rel.rdb$field_position;'; 407 | try { 408 | $columns = $this->db->createCommand($sql)->queryAll(); 409 | if (empty($columns)) { 410 | return false; 411 | } 412 | } catch (Exception $e) { 413 | return false; 414 | } 415 | $sql = 'SELECT 416 | idx.rdb$field_name AS fname 417 | FROM 418 | rdb$relation_constraints rc 419 | JOIN rdb$index_segments idx ON idx.rdb$index_name=rc.rdb$index_name 420 | WHERE rc.rdb$constraint_type=\'PRIMARY KEY\' 421 | AND UPPER(rc.rdb$relation_name)=UPPER(\'' . $table->name . '\')'; 422 | try { 423 | $pkeys = $this->db->createCommand($sql)->queryColumn(); 424 | } catch (Exception $e) { 425 | return false; 426 | } 427 | $pkeys = array_map('rtrim', $pkeys); 428 | $pkeys = array_map('strtolower', $pkeys); 429 | foreach ($columns as $key => $column) { 430 | $column = array_map('strtolower', $column); 431 | $columns[$key]['fprimary'] = in_array(rtrim($column['fname']), $pkeys); 432 | } 433 | foreach ($columns as $column) { 434 | $c = $this->loadColumnSchema($column); 435 | if ($table->sequenceName === null && $c->autoIncrement) { 436 | $matches = []; 437 | 438 | if (isset($column['fgenerator_name']) && $column['fgenerator_name']) { 439 | $table->sequenceName = $column['fgenerator_name']; 440 | } elseif (preg_match("/NEW.{$c->name}\s*=\s*GEN_ID\((\w+)/i", $column['fautoinc'], $matches)) { 441 | $table->sequenceName = $matches[1]; 442 | } elseif (preg_match("/NEW.{$c->name}\s*=\s*NEXT\s+VALUE\s+FOR\s+(\w+)/i", $column['fautoinc'], $matches)) { 443 | $table->sequenceName = $matches[1]; 444 | } 445 | } 446 | $table->columns[$c->name] = $c; 447 | if ($c->isPrimaryKey) { 448 | $table->primaryKey[] = $c->name; 449 | } 450 | } 451 | return (count($table->columns) > 0); 452 | } 453 | 454 | /** 455 | * @return ColumnSchema 456 | * @throws \yii\base\InvalidConfigException 457 | */ 458 | protected function createColumnSchema() 459 | { 460 | return \Yii::createObject('\edgardmessias\db\firebird\ColumnSchema'); 461 | } 462 | 463 | /** 464 | * Creates a table column. 465 | * 466 | * @param array $column column metadata 467 | * @return ColumnSchema normalized column metadata 468 | */ 469 | protected function loadColumnSchema($column) 470 | { 471 | $c = $this->createColumnSchema(); 472 | $c->name = strtolower(rtrim($column['fname'])); 473 | $c->allowNull = (int) $column['fnull'] !== 1; 474 | $c->isPrimaryKey = $column['fprimary']; 475 | $c->autoIncrement = (isset($column['fgenerator_name']) && $column['fgenerator_name']) || (boolean) $column['fautoinc']; 476 | $c->comment = $column['fcomment'] === null ? '' : $column['fcomment']; 477 | 478 | $c->type = self::TYPE_STRING; 479 | 480 | $defaultValue = null; 481 | if (!empty($column['fdefault'])) { 482 | // remove whitespace, 'DEFAULT ' prefix and surrounding single quotes; all optional 483 | if (preg_match("/\s*(DEFAULT\s+){0,1}('(.*)'|(.*))\s*/i", $column['fdefault'], $parts)) { 484 | $defaultValue = array_pop($parts); 485 | } 486 | // handle escaped single quotes like in "funny''quoted''string" 487 | $defaultValue = str_replace('\'\'', '\'', $defaultValue); 488 | } 489 | if ($defaultValue === null) { 490 | $defaultValue = $column['fdefault_value']; 491 | } 492 | $dbType = ''; 493 | $baseTypes = [ 494 | 7 => 'SMALLINT', 495 | 8 => 'INTEGER', 496 | 16 => 'INT64', 497 | 9 => 'QUAD', 498 | 10 => 'FLOAT', 499 | 11 => 'D_FLOAT', 500 | 17 => 'BOOLEAN', 501 | 27 => 'DOUBLE PRECISION', 502 | 12 => 'DATE', 503 | 13 => 'TIME', 504 | 35 => 'TIMESTAMP', 505 | 261 => 'BLOB', 506 | 40 => 'CSTRING', 507 | 45 => 'BLOB_ID', 508 | ]; 509 | $baseCharTypes = [ 510 | 37 => 'VARCHAR', 511 | 14 => 'CHAR', 512 | ]; 513 | if (array_key_exists((int) $column['fcodtype'], $baseTypes)) { 514 | $dbType = $baseTypes[(int) $column['fcodtype']]; 515 | } elseif (array_key_exists((int) $column['fcodtype'], $baseCharTypes)) { 516 | $c->size = (int) $column['fcharlength']; 517 | $c->precision = $c->size; 518 | $dbType = $baseCharTypes[(int) $column['fcodtype']] . "($c->size)"; 519 | } 520 | switch ((int) $column['fcodtype']) { 521 | case 7: 522 | case 8: 523 | switch ((int) $column['fcodsubtype']) { 524 | case 1: 525 | $c->precision = (int) $column['fprecision']; 526 | $c->size = $c->precision; 527 | $c->scale = abs((int) $column['fscale']); 528 | $dbType = "NUMERIC({$c->precision},{$c->scale})"; 529 | break; 530 | case 2: 531 | $c->precision = (int) $column['fprecision']; 532 | $c->size = $c->precision; 533 | $c->scale = abs((int) $column['fscale']); 534 | $dbType = "DECIMAL({$c->precision},{$c->scale})"; 535 | break; 536 | } 537 | break; 538 | case 16: 539 | switch ((int) $column['fcodsubtype']) { 540 | case 1: 541 | $c->precision = (int) $column['fprecision']; 542 | $c->size = $c->precision; 543 | $c->scale = abs((int) $column['fscale']); 544 | $dbType = "NUMERIC({$c->precision},{$c->scale})"; 545 | break; 546 | case 2: 547 | $c->precision = (int) $column['fprecision']; 548 | $c->size = $c->precision; 549 | $c->scale = abs((int) $column['fscale']); 550 | $dbType = "DECIMAL({$c->precision},{$c->scale})"; 551 | break; 552 | default: 553 | $dbType = 'BIGINT'; 554 | break; 555 | } 556 | break; 557 | case 261: 558 | switch ((int) $column['fcodsubtype']) { 559 | case 1: 560 | $dbType = 'BLOB SUB_TYPE TEXT'; 561 | $c->size = null; 562 | break; 563 | } 564 | break; 565 | } 566 | 567 | $c->dbType = strtolower($dbType); 568 | 569 | $c->type = self::TYPE_STRING; 570 | if (preg_match('/^([\w\ ]+)(?:\(([^\)]+)\))?/', $c->dbType, $matches)) { 571 | $type = strtolower($matches[1]); 572 | if (isset($this->typeMap[$type])) { 573 | $c->type = $this->typeMap[$type]; 574 | } 575 | } 576 | 577 | 578 | $c->phpType = $this->getColumnPhpType($c); 579 | 580 | $c->defaultValue = null; 581 | if ($defaultValue !== null) { 582 | if (in_array($c->type, [self::TYPE_DATE, self::TYPE_DATETIME, self::TYPE_TIME, self::TYPE_TIMESTAMP]) 583 | && preg_match('/(CURRENT_|NOW|NULL|TODAY|TOMORROW|YESTERDAY)/i', $defaultValue)) { 584 | $c->defaultValue = new Expression(trim($defaultValue)); 585 | } else { 586 | $c->defaultValue = $c->phpTypecast($defaultValue); 587 | } 588 | } 589 | 590 | return $c; 591 | } 592 | 593 | /** 594 | * Collects the foreign key column details for the given table. 595 | * 596 | * @param TableSchema $table the table metadata 597 | */ 598 | protected function findConstraints($table) 599 | { 600 | // Zoggo - Converted sql to use join syntax 601 | $sql = 'SELECT 602 | a.rdb$constraint_name as fconstraint, 603 | c.rdb$relation_name AS ftable, 604 | d.rdb$field_name AS pfield, 605 | e.rdb$field_name AS ffield 606 | FROM 607 | rdb$ref_constraints b 608 | JOIN rdb$relation_constraints a ON a.rdb$constraint_name=b.rdb$constraint_name 609 | JOIN rdb$relation_constraints c ON b.rdb$const_name_uq=c.rdb$constraint_name 610 | JOIN rdb$index_segments d ON c.rdb$index_name=d.rdb$index_name 611 | JOIN rdb$index_segments e ON a.rdb$index_name=e.rdb$index_name AND e.rdb$field_position = d.rdb$field_position 612 | WHERE 613 | a.rdb$constraint_type=\'FOREIGN KEY\' AND 614 | UPPER(a.rdb$relation_name)=UPPER(\'' . $table->name . '\') '; 615 | try { 616 | $fkeys = $this->db->createCommand($sql)->queryAll(); 617 | } catch (Exception $e) { 618 | return false; 619 | } 620 | 621 | $constraints = []; 622 | foreach ($fkeys as $fkey) { 623 | // Zoggo - Added strtolower here to guarantee that values are 624 | // returned lower case. Otherwise gii generates wrong code. 625 | $fkey = array_map('rtrim', $fkey); 626 | $fkey = array_map('strtolower', $fkey); 627 | 628 | if (!isset($constraints[$fkey['fconstraint']])) { 629 | $constraints[$fkey['fconstraint']] = [ 630 | $fkey['ftable'] 631 | ]; 632 | } 633 | $constraints[$fkey['fconstraint']][$fkey['ffield']] = $fkey['pfield']; 634 | } 635 | $table->foreignKeys = $constraints; 636 | } 637 | 638 | protected function findTableNames($schema = '') 639 | { 640 | $sql = 'SELECT 641 | rdb$relation_name 642 | FROM 643 | rdb$relations 644 | WHERE 645 | (rdb$system_flag is null OR rdb$system_flag=0)'; 646 | try { 647 | $tables = $this->db->createCommand($sql)->queryColumn(); 648 | } catch (Exception $e) { 649 | return false; 650 | } 651 | 652 | $tables = array_map('rtrim', $tables); 653 | $tables = array_map('strtolower', $tables); 654 | 655 | return $tables; 656 | } 657 | 658 | /** 659 | * Returns all unique indexes for the given table. 660 | * Each array element is of the following structure: 661 | * 662 | * ~~~ 663 | * [ 664 | * 'IndexName1' => ['col1' [, ...]], 665 | * 'IndexName2' => ['col2' [, ...]], 666 | * ] 667 | * ~~~ 668 | * 669 | * @param TableSchema $table the table metadata 670 | * @return array all unique indexes for the given table. 671 | * @since 2.0.4 672 | */ 673 | public function findUniqueIndexes($table) 674 | { 675 | $query = ' 676 | SELECT id.RDB$INDEX_NAME as index_name, ids.RDB$FIELD_NAME as column_name 677 | FROM RDB$INDICES id 678 | INNER JOIN RDB$INDEX_SEGMENTS ids ON ids.RDB$INDEX_NAME = id.RDB$INDEX_NAME 679 | WHERE id.RDB$UNIQUE_FLAG = 1 680 | AND id.RDB$SYSTEM_FLAG = 0 681 | AND UPPER(id.RDB$RELATION_NAME) = UPPER(\'' . $table->name . '\') 682 | ORDER BY id.RDB$RELATION_NAME, id.RDB$INDEX_NAME, ids.RDB$FIELD_POSITION'; 683 | $result = []; 684 | $command = $this->db->createCommand($query); 685 | foreach ($command->queryAll() as $row) { 686 | $result[strtolower(rtrim($row['index_name']))][] = strtolower(rtrim($row['column_name'])); 687 | } 688 | return $result; 689 | } 690 | 691 | /** 692 | * Sets the isolation level of the current transaction. 693 | * @param string $level The transaction isolation level to use for this transaction. 694 | * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]] 695 | * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used 696 | * after `SET TRANSACTION ISOLATION LEVEL`. 697 | * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels 698 | */ 699 | public function setTransactionIsolationLevel($level) 700 | { 701 | if ($level == Transaction::READ_UNCOMMITTED) { 702 | parent::setTransactionIsolationLevel('READ COMMITTED RECORD_VERSION'); 703 | } elseif ($level == Transaction::REPEATABLE_READ) { 704 | parent::setTransactionIsolationLevel('SNAPSHOT'); 705 | } elseif ($level == Transaction::SERIALIZABLE) { 706 | parent::setTransactionIsolationLevel('SNAPSHOT TABLE STABILITY'); 707 | } else { 708 | parent::setTransactionIsolationLevel($level); 709 | } 710 | } 711 | 712 | /** 713 | * @inheritdoc 714 | */ 715 | public function insert($table, $columns) 716 | { 717 | $this->_lastInsertID = false; 718 | $params = []; 719 | $sql = ""; 720 | $returnColumns = $this->getTableSchema($table)->primaryKey; 721 | if (!empty($returnColumns)) { 722 | if (!$this->db->supportReturningInsert) { 723 | $returs = []; 724 | $returning = []; 725 | $columnSchemas = $this->getTableSchema($table)->columns; 726 | foreach ((array) $returnColumns as $name) { 727 | $returs[] = $this->quoteColumnName($name) . ' ' . $columnSchemas[$name]->dbType; 728 | $returning[] = $this->quoteColumnName($name); 729 | } 730 | 731 | $sql = $this->db->getQueryBuilder()->rawInsert($table, array_keys($columns), array_values($columns), $params); 732 | 733 | $sql = "EXECUTE block RETURNS (" 734 | . implode(', ',$returs) 735 | . ") AS BEGIN\n" 736 | . $sql . ' RETURNING ' . implode(', ', $returning) 737 | . ' INTO ' . implode(', ', $returning) 738 | . ";\nSUSPEND;\nEND;"; 739 | } else { 740 | $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); 741 | $returning = []; 742 | foreach ((array) $returnColumns as $name) { 743 | $returning[] = $this->quoteColumnName($name); 744 | } 745 | $sql .= ' RETURNING ' . implode(', ', $returning); 746 | } 747 | } 748 | 749 | $command = $this->db->createCommand($sql, $params); 750 | $command->prepare(false); 751 | $result = $command->queryOne(); 752 | 753 | if (!$command->pdoStatement->rowCount()) { 754 | return false; 755 | } else { 756 | if (!empty($returnColumns)) { 757 | foreach ((array) $returnColumns as $name) { 758 | if ($this->getTableSchema($table)->getColumn($name)->autoIncrement) { 759 | $this->_lastInsertID = $result[$name]; 760 | break; 761 | } 762 | } 763 | } 764 | return $result; 765 | } 766 | } 767 | 768 | /** 769 | * @inheritdoc 770 | */ 771 | public function getLastInsertID($sequenceName = '') 772 | { 773 | if (!$this->db->isActive) { 774 | throw new InvalidCallException('DB Connection is not active.'); 775 | } 776 | 777 | if ($sequenceName !== '') { 778 | return $this->db->createCommand('SELECT GEN_ID(' . $this->db->quoteTableName($sequenceName) . ', 0 ) FROM RDB$DATABASE;')->queryScalar(); 779 | } 780 | 781 | if ($this->_lastInsertID !== false) { 782 | return $this->_lastInsertID; 783 | } 784 | return null; 785 | } 786 | 787 | protected function loadTablePrimaryKey($tableName) 788 | { 789 | static $sql = <<<'SQL' 790 | SELECT RC.RDB$CONSTRAINT_NAME AS NAME, IDX.RDB$FIELD_NAME AS COLUMN_NAME 791 | FROM RDB$RELATION_CONSTRAINTS RC 792 | JOIN RDB$INDEX_SEGMENTS IDX 793 | ON IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME 794 | WHERE RC.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' 795 | AND UPPER(RC.RDB$RELATION_NAME) = UPPER(:tableName) 796 | ORDER BY IDX.RDB$FIELD_POSITION 797 | SQL; 798 | 799 | $resolvedName = $this->resolveTableName($tableName); 800 | $constraints = $this->db->createCommand($sql, [ 801 | ':tableName' => $resolvedName->name, 802 | ])->queryAll(); 803 | $constraints = $this->normalizePdoRowKeyCase($constraints, true); 804 | $constraints = ArrayHelper::index($constraints, null, ['name']); 805 | 806 | foreach ($constraints as $name => $constraint) { 807 | $columns = ArrayHelper::getColumn($constraint, 'column_name'); 808 | $columns = array_map('trim', $columns); 809 | $columns = array_map('strtolower', $columns); 810 | return new Constraint([ 811 | 'name' => strtolower(trim($name)), 812 | 'columnNames' => $columns, 813 | ]); 814 | } 815 | 816 | return null; 817 | } 818 | 819 | protected function loadTableUniques($tableName) 820 | { 821 | static $sql = <<<'SQL' 822 | SELECT RC.RDB$CONSTRAINT_NAME AS NAME, IDX.RDB$FIELD_NAME AS COLUMN_NAME 823 | FROM RDB$RELATION_CONSTRAINTS RC 824 | JOIN RDB$INDEX_SEGMENTS IDX 825 | ON IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME 826 | WHERE RC.RDB$CONSTRAINT_TYPE = 'UNIQUE' 827 | AND UPPER(RC.RDB$RELATION_NAME) = UPPER(:tableName) 828 | ORDER BY IDX.RDB$FIELD_POSITION 829 | SQL; 830 | 831 | $resolvedName = $this->resolveTableName($tableName); 832 | $constraints = $this->db->createCommand($sql, [ 833 | ':tableName' => $resolvedName->name, 834 | ])->queryAll(); 835 | $constraints = $this->normalizePdoRowKeyCase($constraints, true); 836 | $constraints = ArrayHelper::index($constraints, null, ['name']); 837 | 838 | $result = []; 839 | foreach ($constraints as $name => $rows) { 840 | $columns = ArrayHelper::getColumn($rows, 'column_name'); 841 | $columns = array_map('trim', $columns); 842 | $columns = array_map('strtolower', $columns); 843 | $result[] = new Constraint([ 844 | 'name' => strtolower(trim($name)), 845 | 'columnNames' => $columns, 846 | ]); 847 | } 848 | 849 | return $result; 850 | } 851 | 852 | protected function loadTableChecks($tableName) 853 | { 854 | // DISTINCT not work on blob, need cast to varchar 855 | // 8191 Is max for UTF-8 856 | static $sql = <<<'SQL' 857 | SELECT DISTINCT RC.RDB$CONSTRAINT_NAME AS NAME, 858 | DEP.RDB$FIELD_NAME AS COLUMN_NAME, 859 | CAST(TRIG.RDB$TRIGGER_SOURCE AS VARCHAR(8191)) AS CHECK_EXPR 860 | FROM RDB$RELATION_CONSTRAINTS RC 861 | JOIN RDB$CHECK_CONSTRAINTS CH_CONST 862 | ON CH_CONST.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME 863 | JOIN RDB$TRIGGERS TRIG 864 | ON TRIG.RDB$TRIGGER_NAME = CH_CONST.RDB$TRIGGER_NAME 865 | JOIN RDB$DEPENDENCIES DEP 866 | ON DEP.RDB$DEPENDENT_NAME = TRIG.RDB$TRIGGER_NAME 867 | AND DEP.RDB$DEPENDED_ON_NAME = TRIG.RDB$RELATION_NAME 868 | WHERE RC.RDB$CONSTRAINT_TYPE = 'CHECK' 869 | AND UPPER(RC.RDB$RELATION_NAME) = UPPER(:tableName) 870 | SQL; 871 | 872 | $resolvedName = $this->resolveTableName($tableName); 873 | $constraints = $this->db->createCommand($sql, [ 874 | ':tableName' => $resolvedName->name, 875 | ])->queryAll(); 876 | $constraints = $this->normalizePdoRowKeyCase($constraints, true); 877 | $constraints = ArrayHelper::index($constraints, null, ['name']); 878 | 879 | $result = []; 880 | foreach ($constraints as $name => $constraint) { 881 | $columns = ArrayHelper::getColumn($constraint, 'column_name'); 882 | $columns = array_map('trim', $columns); 883 | $columns = array_map('strtolower', $columns); 884 | 885 | $check_expr = $constraint[0]['check_expr']; 886 | $check_expr = preg_replace('/^\s*CHECK\s*/i', '', $check_expr); // remove "CHECK " at begin 887 | $check_expr = preg_replace('/^\((.*)\)$/i', '\1', $check_expr); // remove bracket () at begin and end 888 | 889 | $result[] = new CheckConstraint([ 890 | 'name' => strtolower(trim($name)), 891 | 'columnNames' => $columns, 892 | 'expression' => $check_expr, 893 | ]); 894 | } 895 | 896 | return $result; 897 | } 898 | 899 | protected function loadTableIndexes($tableName) 900 | { 901 | static $sql = <<<'SQL' 902 | SELECT IDX.RDB$INDEX_NAME AS NAME, 903 | SEG.RDB$FIELD_NAME AS COLUMN_NAME, 904 | IDX.RDB$UNIQUE_FLAG AS INDEX_IS_UNIQUE, 905 | CASE WHEN CONST.RDB$CONSTRAINT_NAME IS NOT NULL THEN 1 ELSE 0 END AS INDEX_IS_PRIMARY 906 | FROM RDB$INDICES IDX 907 | JOIN RDB$INDEX_SEGMENTS SEG 908 | LEFT JOIN RDB$RELATION_CONSTRAINTS CONST ON CONST.RDB$INDEX_NAME = SEG.RDB$INDEX_NAME AND CONST.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' 909 | ON SEG.RDB$INDEX_NAME = IDX.RDB$INDEX_NAME 910 | WHERE UPPER(IDX.RDB$RELATION_NAME) = UPPER(:tableName) 911 | ORDER BY SEG.RDB$FIELD_POSITION 912 | SQL; 913 | 914 | $resolvedName = $this->resolveTableName($tableName); 915 | $indexes = $this->db->createCommand($sql, [ 916 | ':tableName' => $resolvedName->name, 917 | ])->queryAll(); 918 | $indexes = $this->normalizePdoRowKeyCase($indexes, true); 919 | $indexes = ArrayHelper::index($indexes, null, 'name'); 920 | $result = []; 921 | foreach ($indexes as $name => $index) { 922 | $columns = ArrayHelper::getColumn($index, 'column_name'); 923 | $columns = array_map('trim', $columns); 924 | $columns = array_map('strtolower', $columns); 925 | 926 | $result[] = new IndexConstraint([ 927 | 'isPrimary' => (bool) $index[0]['index_is_primary'], 928 | 'isUnique' => (bool) $index[0]['index_is_unique'], 929 | 'name' => strtolower(trim($name)), 930 | 'columnNames' => $columns, 931 | ]); 932 | } 933 | 934 | return $result; 935 | } 936 | 937 | protected function loadTableDefaultValues($tableName) 938 | { 939 | throw new \yii\base\NotSupportedException('FirebirdSQL does not support default value constraints.'); 940 | } 941 | 942 | protected function loadTableForeignKeys($tableName) 943 | { 944 | static $sql = <<<'SQL' 945 | SELECT A.RDB$CONSTRAINT_NAME AS NAME, 946 | E.RDB$FIELD_NAME AS COLUMN_NAME, 947 | C.RDB$RELATION_NAME AS FOREIGN_TABLE_NAME, 948 | D.RDB$FIELD_NAME AS FOREIGN_COLUMN_NAME, 949 | B.RDB$UPDATE_RULE AS ON_UPDATE, 950 | B.RDB$DELETE_RULE AS ON_DELETE 951 | FROM RDB$REF_CONSTRAINTS B 952 | JOIN RDB$RELATION_CONSTRAINTS A 953 | ON A.RDB$CONSTRAINT_NAME = B.RDB$CONSTRAINT_NAME 954 | JOIN RDB$RELATION_CONSTRAINTS C 955 | ON B.RDB$CONST_NAME_UQ = C.RDB$CONSTRAINT_NAME 956 | JOIN RDB$INDEX_SEGMENTS D 957 | ON C.RDB$INDEX_NAME = D.RDB$INDEX_NAME 958 | JOIN RDB$INDEX_SEGMENTS E 959 | ON A.RDB$INDEX_NAME = E.RDB$INDEX_NAME 960 | AND E.RDB$FIELD_POSITION = D.RDB$FIELD_POSITION 961 | WHERE A.RDB$CONSTRAINT_TYPE = 'FOREIGN KEY' 962 | AND UPPER(A.RDB$RELATION_NAME) = UPPER(:tableName) 963 | ORDER BY E.RDB$FIELD_POSITION 964 | SQL; 965 | 966 | $resolvedName = $this->resolveTableName($tableName); 967 | $constraints = $this->db->createCommand($sql, [ 968 | ':tableName' => $resolvedName->name, 969 | ])->queryAll(); 970 | $constraints = $this->normalizePdoRowKeyCase($constraints, true); 971 | $constraints = ArrayHelper::index($constraints, null, ['name']); 972 | 973 | $result = []; 974 | foreach ($constraints as $name => $constraint) { 975 | $columnNames = ArrayHelper::getColumn($constraint, 'column_name'); 976 | $columnNames = array_map('trim', $columnNames); 977 | $columnNames = array_map('strtolower', $columnNames); 978 | 979 | $foreignColumnNames = ArrayHelper::getColumn($constraint, 'foreign_column_name'); 980 | $foreignColumnNames = array_map('trim', $foreignColumnNames); 981 | $foreignColumnNames = array_map('strtolower', $foreignColumnNames); 982 | 983 | $result[] = new ForeignKeyConstraint([ 984 | 'name' => strtolower(trim($name)), 985 | 'columnNames' => $columnNames, 986 | 'foreignTableName' => strtolower(trim($constraint[0]['foreign_table_name'])), 987 | 'foreignColumnNames' => $foreignColumnNames, 988 | 'onDelete' => trim($constraint[0]['on_delete']), 989 | 'onUpdate' => trim($constraint[0]['on_update']), 990 | ]); 991 | } 992 | 993 | return $result; 994 | } 995 | 996 | protected function findViewNames($schema = '') 997 | { 998 | $sql = <<<'SQL' 999 | SELECT RDB$RELATION_NAME 1000 | FROM RDB$RELATIONS 1001 | WHERE RDB$VIEW_BLR IS NOT NULL 1002 | AND (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0) 1003 | SQL; 1004 | 1005 | $views = $this->db->createCommand($sql)->queryColumn(); 1006 | $views = array_map('trim', $views); 1007 | $views = array_map('strtolower', $views); 1008 | 1009 | return $views; 1010 | } 1011 | } 1012 | -------------------------------------------------------------------------------- /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": "5aaeea677f3ba305bc64ad566e0d7c14", 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": "" 22 | }, 23 | "require": { 24 | "bower-asset/jquery": ">=1.7" 25 | }, 26 | "type": "bower-asset-library", 27 | "extra": { 28 | "bower-asset-main": [ 29 | "./dist/inputmask/inputmask.js", 30 | "./dist/inputmask/inputmask.extensions.js", 31 | "./dist/inputmask/inputmask.date.extensions.js", 32 | "./dist/inputmask/inputmask.numeric.extensions.js", 33 | "./dist/inputmask/inputmask.phone.extensions.js", 34 | "./dist/inputmask/jquery.inputmask.js", 35 | "./dist/inputmask/global/document.js", 36 | "./dist/inputmask/global/window.js", 37 | "./dist/inputmask/phone-codes/phone.js", 38 | "./dist/inputmask/phone-codes/phone-be.js", 39 | "./dist/inputmask/phone-codes/phone-nl.js", 40 | "./dist/inputmask/phone-codes/phone-ru.js", 41 | "./dist/inputmask/phone-codes/phone-uk.js", 42 | "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.jqlite.js", 43 | "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.jquery.js", 44 | "./dist/inputmask/dependencyLibs/inputmask.dependencyLib.js", 45 | "./dist/inputmask/bindings/inputmask.binding.js" 46 | ], 47 | "bower-asset-ignore": [ 48 | "**/*", 49 | "!dist/*", 50 | "!dist/inputmask/*", 51 | "!dist/min/*", 52 | "!dist/min/inputmask/*" 53 | ] 54 | }, 55 | "license": [ 56 | "http://opensource.org/licenses/mit-license.php" 57 | ], 58 | "description": "Inputmask is a javascript library which creates an input mask. Inputmask can run against vanilla javascript, jQuery and jqlite.", 59 | "keywords": [ 60 | "form", 61 | "input", 62 | "inputmask", 63 | "jquery", 64 | "mask", 65 | "plugins" 66 | ], 67 | "time": "2017-11-21T11:46:23+00:00" 68 | }, 69 | { 70 | "name": "bower-asset/jquery", 71 | "version": "3.2.1", 72 | "source": { 73 | "type": "git", 74 | "url": "https://github.com/jquery/jquery-dist.git", 75 | "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e" 76 | }, 77 | "dist": { 78 | "type": "zip", 79 | "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/77d2a51d0520d2ee44173afdf4e40a9201f5964e", 80 | "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e", 81 | "shasum": "" 82 | }, 83 | "type": "bower-asset-library", 84 | "extra": { 85 | "bower-asset-main": "dist/jquery.js", 86 | "bower-asset-ignore": [ 87 | "package.json" 88 | ] 89 | }, 90 | "license": [ 91 | "MIT" 92 | ], 93 | "keywords": [ 94 | "browser", 95 | "javascript", 96 | "jquery", 97 | "library" 98 | ], 99 | "time": "2017-03-20T19:02:00+00:00" 100 | }, 101 | { 102 | "name": "bower-asset/punycode", 103 | "version": "v1.3.2", 104 | "source": { 105 | "type": "git", 106 | "url": "https://github.com/bestiejs/punycode.js.git", 107 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" 108 | }, 109 | "dist": { 110 | "type": "zip", 111 | "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", 112 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", 113 | "shasum": "" 114 | }, 115 | "type": "bower-asset-library", 116 | "extra": { 117 | "bower-asset-main": "punycode.js", 118 | "bower-asset-ignore": [ 119 | "coverage", 120 | "tests", 121 | ".*", 122 | "component.json", 123 | "Gruntfile.js", 124 | "node_modules", 125 | "package.json" 126 | ] 127 | } 128 | }, 129 | { 130 | "name": "bower-asset/yii2-pjax", 131 | "version": "2.0.7.1", 132 | "source": { 133 | "type": "git", 134 | "url": "https://github.com/yiisoft/jquery-pjax.git", 135 | "reference": "aef7b953107264f00234902a3880eb50dafc48be" 136 | }, 137 | "dist": { 138 | "type": "zip", 139 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/aef7b953107264f00234902a3880eb50dafc48be", 140 | "reference": "aef7b953107264f00234902a3880eb50dafc48be", 141 | "shasum": "" 142 | }, 143 | "require": { 144 | "bower-asset/jquery": ">=1.8" 145 | }, 146 | "type": "bower-asset-library", 147 | "extra": { 148 | "bower-asset-main": "./jquery.pjax.js", 149 | "bower-asset-ignore": [ 150 | ".travis.yml", 151 | "Gemfile", 152 | "Gemfile.lock", 153 | "CONTRIBUTING.md", 154 | "vendor/", 155 | "script/", 156 | "test/" 157 | ] 158 | }, 159 | "license": [ 160 | "MIT" 161 | ], 162 | "time": "2017-10-12T10:11:14+00:00" 163 | }, 164 | { 165 | "name": "cebe/markdown", 166 | "version": "1.1.2", 167 | "source": { 168 | "type": "git", 169 | "url": "https://github.com/cebe/markdown.git", 170 | "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e" 171 | }, 172 | "dist": { 173 | "type": "zip", 174 | "url": "https://api.github.com/repos/cebe/markdown/zipball/25b28bae8a6f185b5030673af77b32e1163d5c6e", 175 | "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e", 176 | "shasum": "" 177 | }, 178 | "require": { 179 | "lib-pcre": "*", 180 | "php": ">=5.4.0" 181 | }, 182 | "require-dev": { 183 | "cebe/indent": "*", 184 | "facebook/xhprof": "*@dev", 185 | "phpunit/phpunit": "4.1.*" 186 | }, 187 | "bin": [ 188 | "bin/markdown" 189 | ], 190 | "type": "library", 191 | "extra": { 192 | "branch-alias": { 193 | "dev-master": "1.1.x-dev" 194 | } 195 | }, 196 | "autoload": { 197 | "psr-4": { 198 | "cebe\\markdown\\": "" 199 | } 200 | }, 201 | "notification-url": "https://packagist.org/downloads/", 202 | "license": [ 203 | "MIT" 204 | ], 205 | "authors": [ 206 | { 207 | "name": "Carsten Brandt", 208 | "email": "mail@cebe.cc", 209 | "homepage": "http://cebe.cc/", 210 | "role": "Creator" 211 | } 212 | ], 213 | "description": "A super fast, highly extensible markdown parser for PHP", 214 | "homepage": "https://github.com/cebe/markdown#readme", 215 | "keywords": [ 216 | "extensible", 217 | "fast", 218 | "gfm", 219 | "markdown", 220 | "markdown-extra" 221 | ], 222 | "time": "2017-07-16T21:13:23+00:00" 223 | }, 224 | { 225 | "name": "ezyang/htmlpurifier", 226 | "version": "v4.10.0", 227 | "source": { 228 | "type": "git", 229 | "url": "https://github.com/ezyang/htmlpurifier.git", 230 | "reference": "d85d39da4576a6934b72480be6978fb10c860021" 231 | }, 232 | "dist": { 233 | "type": "zip", 234 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d85d39da4576a6934b72480be6978fb10c860021", 235 | "reference": "d85d39da4576a6934b72480be6978fb10c860021", 236 | "shasum": "" 237 | }, 238 | "require": { 239 | "php": ">=5.2" 240 | }, 241 | "require-dev": { 242 | "simpletest/simpletest": "^1.1" 243 | }, 244 | "type": "library", 245 | "autoload": { 246 | "psr-0": { 247 | "HTMLPurifier": "library/" 248 | }, 249 | "files": [ 250 | "library/HTMLPurifier.composer.php" 251 | ] 252 | }, 253 | "notification-url": "https://packagist.org/downloads/", 254 | "license": [ 255 | "LGPL" 256 | ], 257 | "authors": [ 258 | { 259 | "name": "Edward Z. Yang", 260 | "email": "admin@htmlpurifier.org", 261 | "homepage": "http://ezyang.com" 262 | } 263 | ], 264 | "description": "Standards compliant HTML filter written in PHP", 265 | "homepage": "http://htmlpurifier.org/", 266 | "keywords": [ 267 | "html" 268 | ], 269 | "time": "2018-02-23T01:58:20+00:00" 270 | }, 271 | { 272 | "name": "yiisoft/yii2-composer", 273 | "version": "2.0.6", 274 | "source": { 275 | "type": "git", 276 | "url": "https://github.com/yiisoft/yii2-composer.git", 277 | "reference": "163419f1f197e02f015713b0d4f85598d8f8aa80" 278 | }, 279 | "dist": { 280 | "type": "zip", 281 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/163419f1f197e02f015713b0d4f85598d8f8aa80", 282 | "reference": "163419f1f197e02f015713b0d4f85598d8f8aa80", 283 | "shasum": "" 284 | }, 285 | "require": { 286 | "composer-plugin-api": "^1.0" 287 | }, 288 | "require-dev": { 289 | "composer/composer": "^1.0" 290 | }, 291 | "type": "composer-plugin", 292 | "extra": { 293 | "class": "yii\\composer\\Plugin", 294 | "branch-alias": { 295 | "dev-master": "2.0.x-dev" 296 | } 297 | }, 298 | "autoload": { 299 | "psr-4": { 300 | "yii\\composer\\": "" 301 | } 302 | }, 303 | "notification-url": "https://packagist.org/downloads/", 304 | "license": [ 305 | "BSD-3-Clause" 306 | ], 307 | "authors": [ 308 | { 309 | "name": "Qiang Xue", 310 | "email": "qiang.xue@gmail.com" 311 | }, 312 | { 313 | "name": "Carsten Brandt", 314 | "email": "mail@cebe.cc" 315 | } 316 | ], 317 | "description": "The composer plugin for Yii extension installer", 318 | "keywords": [ 319 | "composer", 320 | "extension installer", 321 | "yii2" 322 | ], 323 | "time": "2018-03-21T16:15:55+00:00" 324 | }, 325 | { 326 | "name": "yiisoft/yii2-dev", 327 | "version": "2.0.15.1", 328 | "source": { 329 | "type": "git", 330 | "url": "https://github.com/yiisoft/yii2.git", 331 | "reference": "20aa5080189cbacd4180d3b115752817921eb364" 332 | }, 333 | "dist": { 334 | "type": "zip", 335 | "url": "https://api.github.com/repos/yiisoft/yii2/zipball/20aa5080189cbacd4180d3b115752817921eb364", 336 | "reference": "20aa5080189cbacd4180d3b115752817921eb364", 337 | "shasum": "" 338 | }, 339 | "require": { 340 | "bower-asset/inputmask": "~3.2.2 | ~3.3.5", 341 | "bower-asset/jquery": "3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", 342 | "bower-asset/punycode": "1.3.*", 343 | "bower-asset/yii2-pjax": "~2.0.1", 344 | "cebe/markdown": "~1.0.0 | ~1.1.0", 345 | "ext-ctype": "*", 346 | "ext-mbstring": "*", 347 | "ezyang/htmlpurifier": "~4.6", 348 | "lib-pcre": "*", 349 | "php": ">=5.4.0", 350 | "yiisoft/yii2-composer": "~2.0.4" 351 | }, 352 | "replace": { 353 | "yiisoft/yii2": "self.version" 354 | }, 355 | "require-dev": { 356 | "cebe/indent": "~1.0.2", 357 | "friendsofphp/php-cs-fixer": "~2.2.3", 358 | "phpunit/phpunit": "4.8.34" 359 | }, 360 | "suggest": { 361 | "yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii" 362 | }, 363 | "bin": [ 364 | "framework/yii" 365 | ], 366 | "type": "yii2-extension", 367 | "extra": { 368 | "branch-alias": { 369 | "dev-master": "2.0.x-dev" 370 | } 371 | }, 372 | "autoload": { 373 | "psr-4": { 374 | "yii\\": "framework/", 375 | "yii\\cs\\": "cs/src/" 376 | } 377 | }, 378 | "notification-url": "https://packagist.org/downloads/", 379 | "license": [ 380 | "BSD-3-Clause" 381 | ], 382 | "authors": [ 383 | { 384 | "name": "Qiang Xue", 385 | "email": "qiang.xue@gmail.com", 386 | "homepage": "http://www.yiiframework.com/", 387 | "role": "Founder and project lead" 388 | }, 389 | { 390 | "name": "Alexander Makarov", 391 | "email": "sam@rmcreative.ru", 392 | "homepage": "http://rmcreative.ru/", 393 | "role": "Core framework development" 394 | }, 395 | { 396 | "name": "Maurizio Domba", 397 | "homepage": "http://mdomba.info/", 398 | "role": "Core framework development" 399 | }, 400 | { 401 | "name": "Carsten Brandt", 402 | "email": "mail@cebe.cc", 403 | "homepage": "http://cebe.cc/", 404 | "role": "Core framework development" 405 | }, 406 | { 407 | "name": "Timur Ruziev", 408 | "email": "resurtm@gmail.com", 409 | "homepage": "http://resurtm.com/", 410 | "role": "Core framework development" 411 | }, 412 | { 413 | "name": "Paul Klimov", 414 | "email": "klimov.paul@gmail.com", 415 | "role": "Core framework development" 416 | }, 417 | { 418 | "name": "Dmitry Naumenko", 419 | "email": "d.naumenko.a@gmail.com", 420 | "role": "Core framework development" 421 | }, 422 | { 423 | "name": "Boudewijn Vahrmeijer", 424 | "email": "info@dynasource.eu", 425 | "homepage": "http://dynasource.eu", 426 | "role": "Core framework development" 427 | } 428 | ], 429 | "description": "Yii PHP Framework Version 2 - Development Package", 430 | "homepage": "http://www.yiiframework.com/", 431 | "keywords": [ 432 | "framework", 433 | "yii2" 434 | ], 435 | "time": "2018-03-21T18:36:53+00:00" 436 | } 437 | ], 438 | "packages-dev": [ 439 | { 440 | "name": "doctrine/instantiator", 441 | "version": "1.0.5", 442 | "source": { 443 | "type": "git", 444 | "url": "https://github.com/doctrine/instantiator.git", 445 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 446 | }, 447 | "dist": { 448 | "type": "zip", 449 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 450 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 451 | "shasum": "" 452 | }, 453 | "require": { 454 | "php": ">=5.3,<8.0-DEV" 455 | }, 456 | "require-dev": { 457 | "athletic/athletic": "~0.1.8", 458 | "ext-pdo": "*", 459 | "ext-phar": "*", 460 | "phpunit/phpunit": "~4.0", 461 | "squizlabs/php_codesniffer": "~2.0" 462 | }, 463 | "type": "library", 464 | "extra": { 465 | "branch-alias": { 466 | "dev-master": "1.0.x-dev" 467 | } 468 | }, 469 | "autoload": { 470 | "psr-4": { 471 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 472 | } 473 | }, 474 | "notification-url": "https://packagist.org/downloads/", 475 | "license": [ 476 | "MIT" 477 | ], 478 | "authors": [ 479 | { 480 | "name": "Marco Pivetta", 481 | "email": "ocramius@gmail.com", 482 | "homepage": "http://ocramius.github.com/" 483 | } 484 | ], 485 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 486 | "homepage": "https://github.com/doctrine/instantiator", 487 | "keywords": [ 488 | "constructor", 489 | "instantiate" 490 | ], 491 | "time": "2015-06-14T21:17:01+00:00" 492 | }, 493 | { 494 | "name": "phpdocumentor/reflection-docblock", 495 | "version": "2.0.5", 496 | "source": { 497 | "type": "git", 498 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 499 | "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" 500 | }, 501 | "dist": { 502 | "type": "zip", 503 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", 504 | "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", 505 | "shasum": "" 506 | }, 507 | "require": { 508 | "php": ">=5.3.3" 509 | }, 510 | "require-dev": { 511 | "phpunit/phpunit": "~4.0" 512 | }, 513 | "suggest": { 514 | "dflydev/markdown": "~1.0", 515 | "erusev/parsedown": "~1.0" 516 | }, 517 | "type": "library", 518 | "extra": { 519 | "branch-alias": { 520 | "dev-master": "2.0.x-dev" 521 | } 522 | }, 523 | "autoload": { 524 | "psr-0": { 525 | "phpDocumentor": [ 526 | "src/" 527 | ] 528 | } 529 | }, 530 | "notification-url": "https://packagist.org/downloads/", 531 | "license": [ 532 | "MIT" 533 | ], 534 | "authors": [ 535 | { 536 | "name": "Mike van Riel", 537 | "email": "mike.vanriel@naenius.com" 538 | } 539 | ], 540 | "time": "2016-01-25T08:17:30+00:00" 541 | }, 542 | { 543 | "name": "phpspec/prophecy", 544 | "version": "1.7.5", 545 | "source": { 546 | "type": "git", 547 | "url": "https://github.com/phpspec/prophecy.git", 548 | "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" 549 | }, 550 | "dist": { 551 | "type": "zip", 552 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", 553 | "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", 554 | "shasum": "" 555 | }, 556 | "require": { 557 | "doctrine/instantiator": "^1.0.2", 558 | "php": "^5.3|^7.0", 559 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 560 | "sebastian/comparator": "^1.1|^2.0", 561 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 562 | }, 563 | "require-dev": { 564 | "phpspec/phpspec": "^2.5|^3.2", 565 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" 566 | }, 567 | "type": "library", 568 | "extra": { 569 | "branch-alias": { 570 | "dev-master": "1.7.x-dev" 571 | } 572 | }, 573 | "autoload": { 574 | "psr-0": { 575 | "Prophecy\\": "src/" 576 | } 577 | }, 578 | "notification-url": "https://packagist.org/downloads/", 579 | "license": [ 580 | "MIT" 581 | ], 582 | "authors": [ 583 | { 584 | "name": "Konstantin Kudryashov", 585 | "email": "ever.zet@gmail.com", 586 | "homepage": "http://everzet.com" 587 | }, 588 | { 589 | "name": "Marcello Duarte", 590 | "email": "marcello.duarte@gmail.com" 591 | } 592 | ], 593 | "description": "Highly opinionated mocking framework for PHP 5.3+", 594 | "homepage": "https://github.com/phpspec/prophecy", 595 | "keywords": [ 596 | "Double", 597 | "Dummy", 598 | "fake", 599 | "mock", 600 | "spy", 601 | "stub" 602 | ], 603 | "time": "2018-02-19T10:16:54+00:00" 604 | }, 605 | { 606 | "name": "phpunit/php-code-coverage", 607 | "version": "2.2.4", 608 | "source": { 609 | "type": "git", 610 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 611 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" 612 | }, 613 | "dist": { 614 | "type": "zip", 615 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", 616 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", 617 | "shasum": "" 618 | }, 619 | "require": { 620 | "php": ">=5.3.3", 621 | "phpunit/php-file-iterator": "~1.3", 622 | "phpunit/php-text-template": "~1.2", 623 | "phpunit/php-token-stream": "~1.3", 624 | "sebastian/environment": "^1.3.2", 625 | "sebastian/version": "~1.0" 626 | }, 627 | "require-dev": { 628 | "ext-xdebug": ">=2.1.4", 629 | "phpunit/phpunit": "~4" 630 | }, 631 | "suggest": { 632 | "ext-dom": "*", 633 | "ext-xdebug": ">=2.2.1", 634 | "ext-xmlwriter": "*" 635 | }, 636 | "type": "library", 637 | "extra": { 638 | "branch-alias": { 639 | "dev-master": "2.2.x-dev" 640 | } 641 | }, 642 | "autoload": { 643 | "classmap": [ 644 | "src/" 645 | ] 646 | }, 647 | "notification-url": "https://packagist.org/downloads/", 648 | "license": [ 649 | "BSD-3-Clause" 650 | ], 651 | "authors": [ 652 | { 653 | "name": "Sebastian Bergmann", 654 | "email": "sb@sebastian-bergmann.de", 655 | "role": "lead" 656 | } 657 | ], 658 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 659 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 660 | "keywords": [ 661 | "coverage", 662 | "testing", 663 | "xunit" 664 | ], 665 | "time": "2015-10-06T15:47:00+00:00" 666 | }, 667 | { 668 | "name": "phpunit/php-file-iterator", 669 | "version": "1.4.5", 670 | "source": { 671 | "type": "git", 672 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 673 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" 674 | }, 675 | "dist": { 676 | "type": "zip", 677 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", 678 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", 679 | "shasum": "" 680 | }, 681 | "require": { 682 | "php": ">=5.3.3" 683 | }, 684 | "type": "library", 685 | "extra": { 686 | "branch-alias": { 687 | "dev-master": "1.4.x-dev" 688 | } 689 | }, 690 | "autoload": { 691 | "classmap": [ 692 | "src/" 693 | ] 694 | }, 695 | "notification-url": "https://packagist.org/downloads/", 696 | "license": [ 697 | "BSD-3-Clause" 698 | ], 699 | "authors": [ 700 | { 701 | "name": "Sebastian Bergmann", 702 | "email": "sb@sebastian-bergmann.de", 703 | "role": "lead" 704 | } 705 | ], 706 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 707 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 708 | "keywords": [ 709 | "filesystem", 710 | "iterator" 711 | ], 712 | "time": "2017-11-27T13:52:08+00:00" 713 | }, 714 | { 715 | "name": "phpunit/php-text-template", 716 | "version": "1.2.1", 717 | "source": { 718 | "type": "git", 719 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 720 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 721 | }, 722 | "dist": { 723 | "type": "zip", 724 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 725 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 726 | "shasum": "" 727 | }, 728 | "require": { 729 | "php": ">=5.3.3" 730 | }, 731 | "type": "library", 732 | "autoload": { 733 | "classmap": [ 734 | "src/" 735 | ] 736 | }, 737 | "notification-url": "https://packagist.org/downloads/", 738 | "license": [ 739 | "BSD-3-Clause" 740 | ], 741 | "authors": [ 742 | { 743 | "name": "Sebastian Bergmann", 744 | "email": "sebastian@phpunit.de", 745 | "role": "lead" 746 | } 747 | ], 748 | "description": "Simple template engine.", 749 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 750 | "keywords": [ 751 | "template" 752 | ], 753 | "time": "2015-06-21T13:50:34+00:00" 754 | }, 755 | { 756 | "name": "phpunit/php-timer", 757 | "version": "1.0.9", 758 | "source": { 759 | "type": "git", 760 | "url": "https://github.com/sebastianbergmann/php-timer.git", 761 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 762 | }, 763 | "dist": { 764 | "type": "zip", 765 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 766 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 767 | "shasum": "" 768 | }, 769 | "require": { 770 | "php": "^5.3.3 || ^7.0" 771 | }, 772 | "require-dev": { 773 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 774 | }, 775 | "type": "library", 776 | "extra": { 777 | "branch-alias": { 778 | "dev-master": "1.0-dev" 779 | } 780 | }, 781 | "autoload": { 782 | "classmap": [ 783 | "src/" 784 | ] 785 | }, 786 | "notification-url": "https://packagist.org/downloads/", 787 | "license": [ 788 | "BSD-3-Clause" 789 | ], 790 | "authors": [ 791 | { 792 | "name": "Sebastian Bergmann", 793 | "email": "sb@sebastian-bergmann.de", 794 | "role": "lead" 795 | } 796 | ], 797 | "description": "Utility class for timing", 798 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 799 | "keywords": [ 800 | "timer" 801 | ], 802 | "time": "2017-02-26T11:10:40+00:00" 803 | }, 804 | { 805 | "name": "phpunit/php-token-stream", 806 | "version": "1.4.12", 807 | "source": { 808 | "type": "git", 809 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 810 | "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" 811 | }, 812 | "dist": { 813 | "type": "zip", 814 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", 815 | "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", 816 | "shasum": "" 817 | }, 818 | "require": { 819 | "ext-tokenizer": "*", 820 | "php": ">=5.3.3" 821 | }, 822 | "require-dev": { 823 | "phpunit/phpunit": "~4.2" 824 | }, 825 | "type": "library", 826 | "extra": { 827 | "branch-alias": { 828 | "dev-master": "1.4-dev" 829 | } 830 | }, 831 | "autoload": { 832 | "classmap": [ 833 | "src/" 834 | ] 835 | }, 836 | "notification-url": "https://packagist.org/downloads/", 837 | "license": [ 838 | "BSD-3-Clause" 839 | ], 840 | "authors": [ 841 | { 842 | "name": "Sebastian Bergmann", 843 | "email": "sebastian@phpunit.de" 844 | } 845 | ], 846 | "description": "Wrapper around PHP's tokenizer extension.", 847 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 848 | "keywords": [ 849 | "tokenizer" 850 | ], 851 | "time": "2017-12-04T08:55:13+00:00" 852 | }, 853 | { 854 | "name": "phpunit/phpunit", 855 | "version": "4.8.34", 856 | "source": { 857 | "type": "git", 858 | "url": "https://github.com/sebastianbergmann/phpunit.git", 859 | "reference": "7eb45205d27edd94bd2b3614085ea158bd1e2bca" 860 | }, 861 | "dist": { 862 | "type": "zip", 863 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7eb45205d27edd94bd2b3614085ea158bd1e2bca", 864 | "reference": "7eb45205d27edd94bd2b3614085ea158bd1e2bca", 865 | "shasum": "" 866 | }, 867 | "require": { 868 | "ext-dom": "*", 869 | "ext-json": "*", 870 | "ext-pcre": "*", 871 | "ext-reflection": "*", 872 | "ext-spl": "*", 873 | "php": ">=5.3.3", 874 | "phpspec/prophecy": "^1.3.1", 875 | "phpunit/php-code-coverage": "~2.1", 876 | "phpunit/php-file-iterator": "~1.4", 877 | "phpunit/php-text-template": "~1.2", 878 | "phpunit/php-timer": "^1.0.6", 879 | "phpunit/phpunit-mock-objects": "~2.3", 880 | "sebastian/comparator": "~1.2.2", 881 | "sebastian/diff": "~1.2", 882 | "sebastian/environment": "~1.3", 883 | "sebastian/exporter": "~1.2", 884 | "sebastian/global-state": "~1.0", 885 | "sebastian/version": "~1.0", 886 | "symfony/yaml": "~2.1|~3.0" 887 | }, 888 | "suggest": { 889 | "phpunit/php-invoker": "~1.1" 890 | }, 891 | "bin": [ 892 | "phpunit" 893 | ], 894 | "type": "library", 895 | "extra": { 896 | "branch-alias": { 897 | "dev-master": "4.8.x-dev" 898 | } 899 | }, 900 | "autoload": { 901 | "classmap": [ 902 | "src/" 903 | ] 904 | }, 905 | "notification-url": "https://packagist.org/downloads/", 906 | "license": [ 907 | "BSD-3-Clause" 908 | ], 909 | "authors": [ 910 | { 911 | "name": "Sebastian Bergmann", 912 | "email": "sebastian@phpunit.de", 913 | "role": "lead" 914 | } 915 | ], 916 | "description": "The PHP Unit Testing framework.", 917 | "homepage": "https://phpunit.de/", 918 | "keywords": [ 919 | "phpunit", 920 | "testing", 921 | "xunit" 922 | ], 923 | "time": "2017-01-26T16:15:36+00:00" 924 | }, 925 | { 926 | "name": "phpunit/phpunit-mock-objects", 927 | "version": "2.3.8", 928 | "source": { 929 | "type": "git", 930 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 931 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" 932 | }, 933 | "dist": { 934 | "type": "zip", 935 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", 936 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", 937 | "shasum": "" 938 | }, 939 | "require": { 940 | "doctrine/instantiator": "^1.0.2", 941 | "php": ">=5.3.3", 942 | "phpunit/php-text-template": "~1.2", 943 | "sebastian/exporter": "~1.2" 944 | }, 945 | "require-dev": { 946 | "phpunit/phpunit": "~4.4" 947 | }, 948 | "suggest": { 949 | "ext-soap": "*" 950 | }, 951 | "type": "library", 952 | "extra": { 953 | "branch-alias": { 954 | "dev-master": "2.3.x-dev" 955 | } 956 | }, 957 | "autoload": { 958 | "classmap": [ 959 | "src/" 960 | ] 961 | }, 962 | "notification-url": "https://packagist.org/downloads/", 963 | "license": [ 964 | "BSD-3-Clause" 965 | ], 966 | "authors": [ 967 | { 968 | "name": "Sebastian Bergmann", 969 | "email": "sb@sebastian-bergmann.de", 970 | "role": "lead" 971 | } 972 | ], 973 | "description": "Mock Object library for PHPUnit", 974 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 975 | "keywords": [ 976 | "mock", 977 | "xunit" 978 | ], 979 | "time": "2015-10-02T06:51:40+00:00" 980 | }, 981 | { 982 | "name": "sebastian/comparator", 983 | "version": "1.2.4", 984 | "source": { 985 | "type": "git", 986 | "url": "https://github.com/sebastianbergmann/comparator.git", 987 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" 988 | }, 989 | "dist": { 990 | "type": "zip", 991 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 992 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 993 | "shasum": "" 994 | }, 995 | "require": { 996 | "php": ">=5.3.3", 997 | "sebastian/diff": "~1.2", 998 | "sebastian/exporter": "~1.2 || ~2.0" 999 | }, 1000 | "require-dev": { 1001 | "phpunit/phpunit": "~4.4" 1002 | }, 1003 | "type": "library", 1004 | "extra": { 1005 | "branch-alias": { 1006 | "dev-master": "1.2.x-dev" 1007 | } 1008 | }, 1009 | "autoload": { 1010 | "classmap": [ 1011 | "src/" 1012 | ] 1013 | }, 1014 | "notification-url": "https://packagist.org/downloads/", 1015 | "license": [ 1016 | "BSD-3-Clause" 1017 | ], 1018 | "authors": [ 1019 | { 1020 | "name": "Jeff Welch", 1021 | "email": "whatthejeff@gmail.com" 1022 | }, 1023 | { 1024 | "name": "Volker Dusch", 1025 | "email": "github@wallbash.com" 1026 | }, 1027 | { 1028 | "name": "Bernhard Schussek", 1029 | "email": "bschussek@2bepublished.at" 1030 | }, 1031 | { 1032 | "name": "Sebastian Bergmann", 1033 | "email": "sebastian@phpunit.de" 1034 | } 1035 | ], 1036 | "description": "Provides the functionality to compare PHP values for equality", 1037 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1038 | "keywords": [ 1039 | "comparator", 1040 | "compare", 1041 | "equality" 1042 | ], 1043 | "time": "2017-01-29T09:50:25+00:00" 1044 | }, 1045 | { 1046 | "name": "sebastian/diff", 1047 | "version": "1.4.3", 1048 | "source": { 1049 | "type": "git", 1050 | "url": "https://github.com/sebastianbergmann/diff.git", 1051 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" 1052 | }, 1053 | "dist": { 1054 | "type": "zip", 1055 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", 1056 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", 1057 | "shasum": "" 1058 | }, 1059 | "require": { 1060 | "php": "^5.3.3 || ^7.0" 1061 | }, 1062 | "require-dev": { 1063 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 1064 | }, 1065 | "type": "library", 1066 | "extra": { 1067 | "branch-alias": { 1068 | "dev-master": "1.4-dev" 1069 | } 1070 | }, 1071 | "autoload": { 1072 | "classmap": [ 1073 | "src/" 1074 | ] 1075 | }, 1076 | "notification-url": "https://packagist.org/downloads/", 1077 | "license": [ 1078 | "BSD-3-Clause" 1079 | ], 1080 | "authors": [ 1081 | { 1082 | "name": "Kore Nordmann", 1083 | "email": "mail@kore-nordmann.de" 1084 | }, 1085 | { 1086 | "name": "Sebastian Bergmann", 1087 | "email": "sebastian@phpunit.de" 1088 | } 1089 | ], 1090 | "description": "Diff implementation", 1091 | "homepage": "https://github.com/sebastianbergmann/diff", 1092 | "keywords": [ 1093 | "diff" 1094 | ], 1095 | "time": "2017-05-22T07:24:03+00:00" 1096 | }, 1097 | { 1098 | "name": "sebastian/environment", 1099 | "version": "1.3.8", 1100 | "source": { 1101 | "type": "git", 1102 | "url": "https://github.com/sebastianbergmann/environment.git", 1103 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" 1104 | }, 1105 | "dist": { 1106 | "type": "zip", 1107 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", 1108 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", 1109 | "shasum": "" 1110 | }, 1111 | "require": { 1112 | "php": "^5.3.3 || ^7.0" 1113 | }, 1114 | "require-dev": { 1115 | "phpunit/phpunit": "^4.8 || ^5.0" 1116 | }, 1117 | "type": "library", 1118 | "extra": { 1119 | "branch-alias": { 1120 | "dev-master": "1.3.x-dev" 1121 | } 1122 | }, 1123 | "autoload": { 1124 | "classmap": [ 1125 | "src/" 1126 | ] 1127 | }, 1128 | "notification-url": "https://packagist.org/downloads/", 1129 | "license": [ 1130 | "BSD-3-Clause" 1131 | ], 1132 | "authors": [ 1133 | { 1134 | "name": "Sebastian Bergmann", 1135 | "email": "sebastian@phpunit.de" 1136 | } 1137 | ], 1138 | "description": "Provides functionality to handle HHVM/PHP environments", 1139 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1140 | "keywords": [ 1141 | "Xdebug", 1142 | "environment", 1143 | "hhvm" 1144 | ], 1145 | "time": "2016-08-18T05:49:44+00:00" 1146 | }, 1147 | { 1148 | "name": "sebastian/exporter", 1149 | "version": "1.2.2", 1150 | "source": { 1151 | "type": "git", 1152 | "url": "https://github.com/sebastianbergmann/exporter.git", 1153 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" 1154 | }, 1155 | "dist": { 1156 | "type": "zip", 1157 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", 1158 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", 1159 | "shasum": "" 1160 | }, 1161 | "require": { 1162 | "php": ">=5.3.3", 1163 | "sebastian/recursion-context": "~1.0" 1164 | }, 1165 | "require-dev": { 1166 | "ext-mbstring": "*", 1167 | "phpunit/phpunit": "~4.4" 1168 | }, 1169 | "type": "library", 1170 | "extra": { 1171 | "branch-alias": { 1172 | "dev-master": "1.3.x-dev" 1173 | } 1174 | }, 1175 | "autoload": { 1176 | "classmap": [ 1177 | "src/" 1178 | ] 1179 | }, 1180 | "notification-url": "https://packagist.org/downloads/", 1181 | "license": [ 1182 | "BSD-3-Clause" 1183 | ], 1184 | "authors": [ 1185 | { 1186 | "name": "Jeff Welch", 1187 | "email": "whatthejeff@gmail.com" 1188 | }, 1189 | { 1190 | "name": "Volker Dusch", 1191 | "email": "github@wallbash.com" 1192 | }, 1193 | { 1194 | "name": "Bernhard Schussek", 1195 | "email": "bschussek@2bepublished.at" 1196 | }, 1197 | { 1198 | "name": "Sebastian Bergmann", 1199 | "email": "sebastian@phpunit.de" 1200 | }, 1201 | { 1202 | "name": "Adam Harvey", 1203 | "email": "aharvey@php.net" 1204 | } 1205 | ], 1206 | "description": "Provides the functionality to export PHP variables for visualization", 1207 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1208 | "keywords": [ 1209 | "export", 1210 | "exporter" 1211 | ], 1212 | "time": "2016-06-17T09:04:28+00:00" 1213 | }, 1214 | { 1215 | "name": "sebastian/global-state", 1216 | "version": "1.1.1", 1217 | "source": { 1218 | "type": "git", 1219 | "url": "https://github.com/sebastianbergmann/global-state.git", 1220 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 1221 | }, 1222 | "dist": { 1223 | "type": "zip", 1224 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 1225 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 1226 | "shasum": "" 1227 | }, 1228 | "require": { 1229 | "php": ">=5.3.3" 1230 | }, 1231 | "require-dev": { 1232 | "phpunit/phpunit": "~4.2" 1233 | }, 1234 | "suggest": { 1235 | "ext-uopz": "*" 1236 | }, 1237 | "type": "library", 1238 | "extra": { 1239 | "branch-alias": { 1240 | "dev-master": "1.0-dev" 1241 | } 1242 | }, 1243 | "autoload": { 1244 | "classmap": [ 1245 | "src/" 1246 | ] 1247 | }, 1248 | "notification-url": "https://packagist.org/downloads/", 1249 | "license": [ 1250 | "BSD-3-Clause" 1251 | ], 1252 | "authors": [ 1253 | { 1254 | "name": "Sebastian Bergmann", 1255 | "email": "sebastian@phpunit.de" 1256 | } 1257 | ], 1258 | "description": "Snapshotting of global state", 1259 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1260 | "keywords": [ 1261 | "global state" 1262 | ], 1263 | "time": "2015-10-12T03:26:01+00:00" 1264 | }, 1265 | { 1266 | "name": "sebastian/recursion-context", 1267 | "version": "1.0.5", 1268 | "source": { 1269 | "type": "git", 1270 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1271 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" 1272 | }, 1273 | "dist": { 1274 | "type": "zip", 1275 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 1276 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 1277 | "shasum": "" 1278 | }, 1279 | "require": { 1280 | "php": ">=5.3.3" 1281 | }, 1282 | "require-dev": { 1283 | "phpunit/phpunit": "~4.4" 1284 | }, 1285 | "type": "library", 1286 | "extra": { 1287 | "branch-alias": { 1288 | "dev-master": "1.0.x-dev" 1289 | } 1290 | }, 1291 | "autoload": { 1292 | "classmap": [ 1293 | "src/" 1294 | ] 1295 | }, 1296 | "notification-url": "https://packagist.org/downloads/", 1297 | "license": [ 1298 | "BSD-3-Clause" 1299 | ], 1300 | "authors": [ 1301 | { 1302 | "name": "Jeff Welch", 1303 | "email": "whatthejeff@gmail.com" 1304 | }, 1305 | { 1306 | "name": "Sebastian Bergmann", 1307 | "email": "sebastian@phpunit.de" 1308 | }, 1309 | { 1310 | "name": "Adam Harvey", 1311 | "email": "aharvey@php.net" 1312 | } 1313 | ], 1314 | "description": "Provides functionality to recursively process PHP variables", 1315 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1316 | "time": "2016-10-03T07:41:43+00:00" 1317 | }, 1318 | { 1319 | "name": "sebastian/version", 1320 | "version": "1.0.6", 1321 | "source": { 1322 | "type": "git", 1323 | "url": "https://github.com/sebastianbergmann/version.git", 1324 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 1325 | }, 1326 | "dist": { 1327 | "type": "zip", 1328 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1329 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1330 | "shasum": "" 1331 | }, 1332 | "type": "library", 1333 | "autoload": { 1334 | "classmap": [ 1335 | "src/" 1336 | ] 1337 | }, 1338 | "notification-url": "https://packagist.org/downloads/", 1339 | "license": [ 1340 | "BSD-3-Clause" 1341 | ], 1342 | "authors": [ 1343 | { 1344 | "name": "Sebastian Bergmann", 1345 | "email": "sebastian@phpunit.de", 1346 | "role": "lead" 1347 | } 1348 | ], 1349 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1350 | "homepage": "https://github.com/sebastianbergmann/version", 1351 | "time": "2015-06-21T13:59:46+00:00" 1352 | }, 1353 | { 1354 | "name": "squizlabs/php_codesniffer", 1355 | "version": "2.9.1", 1356 | "source": { 1357 | "type": "git", 1358 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 1359 | "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62" 1360 | }, 1361 | "dist": { 1362 | "type": "zip", 1363 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62", 1364 | "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62", 1365 | "shasum": "" 1366 | }, 1367 | "require": { 1368 | "ext-simplexml": "*", 1369 | "ext-tokenizer": "*", 1370 | "ext-xmlwriter": "*", 1371 | "php": ">=5.1.2" 1372 | }, 1373 | "require-dev": { 1374 | "phpunit/phpunit": "~4.0" 1375 | }, 1376 | "bin": [ 1377 | "scripts/phpcs", 1378 | "scripts/phpcbf" 1379 | ], 1380 | "type": "library", 1381 | "extra": { 1382 | "branch-alias": { 1383 | "dev-master": "2.x-dev" 1384 | } 1385 | }, 1386 | "autoload": { 1387 | "classmap": [ 1388 | "CodeSniffer.php", 1389 | "CodeSniffer/CLI.php", 1390 | "CodeSniffer/Exception.php", 1391 | "CodeSniffer/File.php", 1392 | "CodeSniffer/Fixer.php", 1393 | "CodeSniffer/Report.php", 1394 | "CodeSniffer/Reporting.php", 1395 | "CodeSniffer/Sniff.php", 1396 | "CodeSniffer/Tokens.php", 1397 | "CodeSniffer/Reports/", 1398 | "CodeSniffer/Tokenizers/", 1399 | "CodeSniffer/DocGenerators/", 1400 | "CodeSniffer/Standards/AbstractPatternSniff.php", 1401 | "CodeSniffer/Standards/AbstractScopeSniff.php", 1402 | "CodeSniffer/Standards/AbstractVariableSniff.php", 1403 | "CodeSniffer/Standards/IncorrectPatternException.php", 1404 | "CodeSniffer/Standards/Generic/Sniffs/", 1405 | "CodeSniffer/Standards/MySource/Sniffs/", 1406 | "CodeSniffer/Standards/PEAR/Sniffs/", 1407 | "CodeSniffer/Standards/PSR1/Sniffs/", 1408 | "CodeSniffer/Standards/PSR2/Sniffs/", 1409 | "CodeSniffer/Standards/Squiz/Sniffs/", 1410 | "CodeSniffer/Standards/Zend/Sniffs/" 1411 | ] 1412 | }, 1413 | "notification-url": "https://packagist.org/downloads/", 1414 | "license": [ 1415 | "BSD-3-Clause" 1416 | ], 1417 | "authors": [ 1418 | { 1419 | "name": "Greg Sherwood", 1420 | "role": "lead" 1421 | } 1422 | ], 1423 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 1424 | "homepage": "http://www.squizlabs.com/php-codesniffer", 1425 | "keywords": [ 1426 | "phpcs", 1427 | "standards" 1428 | ], 1429 | "time": "2017-05-22T02:43:20+00:00" 1430 | }, 1431 | { 1432 | "name": "symfony/yaml", 1433 | "version": "v2.8.37", 1434 | "source": { 1435 | "type": "git", 1436 | "url": "https://github.com/symfony/yaml.git", 1437 | "reference": "be720fcfae4614df204190d57795351059946a77" 1438 | }, 1439 | "dist": { 1440 | "type": "zip", 1441 | "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77", 1442 | "reference": "be720fcfae4614df204190d57795351059946a77", 1443 | "shasum": "" 1444 | }, 1445 | "require": { 1446 | "php": ">=5.3.9" 1447 | }, 1448 | "type": "library", 1449 | "extra": { 1450 | "branch-alias": { 1451 | "dev-master": "2.8-dev" 1452 | } 1453 | }, 1454 | "autoload": { 1455 | "psr-4": { 1456 | "Symfony\\Component\\Yaml\\": "" 1457 | }, 1458 | "exclude-from-classmap": [ 1459 | "/Tests/" 1460 | ] 1461 | }, 1462 | "notification-url": "https://packagist.org/downloads/", 1463 | "license": [ 1464 | "MIT" 1465 | ], 1466 | "authors": [ 1467 | { 1468 | "name": "Fabien Potencier", 1469 | "email": "fabien@symfony.com" 1470 | }, 1471 | { 1472 | "name": "Symfony Community", 1473 | "homepage": "https://symfony.com/contributors" 1474 | } 1475 | ], 1476 | "description": "Symfony Yaml Component", 1477 | "homepage": "https://symfony.com", 1478 | "time": "2018-01-03T07:36:31+00:00" 1479 | }, 1480 | { 1481 | "name": "yiisoft/yii2-coding-standards", 1482 | "version": "2.0.3", 1483 | "source": { 1484 | "type": "git", 1485 | "url": "https://github.com/yiisoft/yii2-coding-standards.git", 1486 | "reference": "1047aaefcce4cfb83e4987110a573d19706bc50d" 1487 | }, 1488 | "dist": { 1489 | "type": "zip", 1490 | "url": "https://api.github.com/repos/yiisoft/yii2-coding-standards/zipball/1047aaefcce4cfb83e4987110a573d19706bc50d", 1491 | "reference": "1047aaefcce4cfb83e4987110a573d19706bc50d", 1492 | "shasum": "" 1493 | }, 1494 | "require": { 1495 | "php": ">=5.4.0", 1496 | "squizlabs/php_codesniffer": ">=2.3.1 <3.0" 1497 | }, 1498 | "type": "phpcodesniffer-standard", 1499 | "notification-url": "https://packagist.org/downloads/", 1500 | "license": [ 1501 | "BSD-3-Clause" 1502 | ], 1503 | "authors": [ 1504 | { 1505 | "name": "Qiang Xue", 1506 | "email": "qiang.xue@gmail.com", 1507 | "homepage": "http://www.yiiframework.com/", 1508 | "role": "Founder and project lead" 1509 | }, 1510 | { 1511 | "name": "Alexander Makarov", 1512 | "email": "sam@rmcreative.ru", 1513 | "homepage": "http://rmcreative.ru/", 1514 | "role": "Core framework development" 1515 | }, 1516 | { 1517 | "name": "Maurizio Domba", 1518 | "homepage": "http://mdomba.info/", 1519 | "role": "Core framework development" 1520 | }, 1521 | { 1522 | "name": "Carsten Brandt", 1523 | "email": "mail@cebe.cc", 1524 | "homepage": "http://cebe.cc/", 1525 | "role": "Core framework development" 1526 | }, 1527 | { 1528 | "name": "Timur Ruziev", 1529 | "email": "resurtm@gmail.com", 1530 | "homepage": "http://resurtm.com/", 1531 | "role": "Core framework development" 1532 | }, 1533 | { 1534 | "name": "Paul Klimov", 1535 | "email": "klimov.paul@gmail.com", 1536 | "role": "Core framework development" 1537 | } 1538 | ], 1539 | "description": "Yii PHP Framework Version 2 - Coding standard tools", 1540 | "homepage": "http://www.yiiframework.com/", 1541 | "keywords": [ 1542 | "codesniffer", 1543 | "framework", 1544 | "yii" 1545 | ], 1546 | "time": "2017-05-12T10:30:45+00:00" 1547 | } 1548 | ], 1549 | "aliases": [], 1550 | "minimum-stability": "stable", 1551 | "stability-flags": [], 1552 | "prefer-stable": false, 1553 | "prefer-lowest": false, 1554 | "platform": [], 1555 | "platform-dev": [], 1556 | "platform-overrides": { 1557 | "php": "5.4" 1558 | } 1559 | } 1560 | --------------------------------------------------------------------------------