diff --git a/apps/advanced/common/models/LoginForm.php b/apps/advanced/common/models/LoginForm.php index 30fb39b..38888d9 100644 --- a/apps/advanced/common/models/LoginForm.php +++ b/apps/advanced/common/models/LoginForm.php @@ -14,6 +14,8 @@ class LoginForm extends Model public $password; public $rememberMe = true; + private $_user = false; + /** * @return array the validation rules. */ @@ -35,7 +37,7 @@ class LoginForm extends Model */ public function validatePassword() { - $user = User::findByUsername($this->username); + $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError('password', 'Incorrect username or password.'); } @@ -48,11 +50,22 @@ class LoginForm extends Model public function login() { if ($this->validate()) { - $user = User::findByUsername($this->username); - Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); - return true; + return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); } else { return false; } } + + /** + * Finds user by [[username]] + * + * @return User|null + */ + private function getUser() + { + if ($this->_user === false) { + $this->_user = User::findByUsername($this->username); + } + return $this->_user; + } } diff --git a/apps/basic/models/LoginForm.php b/apps/basic/models/LoginForm.php index ad854a2..a365db7 100644 --- a/apps/basic/models/LoginForm.php +++ b/apps/basic/models/LoginForm.php @@ -14,6 +14,8 @@ class LoginForm extends Model public $password; public $rememberMe = true; + private $_user = false; + /** * @return array the validation rules. */ @@ -35,7 +37,7 @@ class LoginForm extends Model */ public function validatePassword() { - $user = User::findByUsername($this->username); + $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError('password', 'Incorrect username or password.'); } @@ -48,11 +50,22 @@ class LoginForm extends Model public function login() { if ($this->validate()) { - $user = User::findByUsername($this->username); - Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); - return true; + return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); } else { return false; } } + + /** + * Finds user by [[username]] + * + * @return User|null + */ + private function getUser() + { + if ($this->_user === false) { + $this->_user = User::findByUsername($this->username); + } + return $this->_user; + } } diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 9fd84e8..a0ba886 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -46,10 +46,7 @@ class ClassmapController extends Controller 'except' => [ 'Yii.php', 'BaseYii.php', - '/debug/', '/console/', - '/test/', - '/gii/', ], ]; $files = FileHelper::findFiles($root, $options); diff --git a/extensions/bootstrap/Progress.php b/extensions/bootstrap/Progress.php index ed2d37d..184451c 100644 --- a/extensions/bootstrap/Progress.php +++ b/extensions/bootstrap/Progress.php @@ -48,7 +48,7 @@ use yii\helpers\Html; * 'bars' => [ * ['percent' => 30, 'options' => ['class' => 'bar-danger']], * ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'bar-success']], - * ['percent' => 35, 'options' => array['class' => 'bar-warning']], + * ['percent' => 35, 'options' => ['class' => 'bar-warning']], * ] * ]); * ``` diff --git a/extensions/gii/generators/model/Generator.php b/extensions/gii/generators/model/Generator.php index 32ba2e5..cd2fcbf 100644 --- a/extensions/gii/generators/model/Generator.php +++ b/extensions/gii/generators/model/Generator.php @@ -115,11 +115,16 @@ class Generator extends \yii\gii\Generator */ public function autoCompleteData() { - return [ - 'tableName' => function () { - return $this->getDbConnection()->getSchema()->getTableNames(); - }, - ]; + $db = $this->getDbConnection(); + if ($db !== null) { + return [ + 'tableName' => function () use ($db) { + return $db->getSchema()->getTableNames(); + }, + ]; + } else { + return []; + } } /** diff --git a/extensions/jui/README.md b/extensions/jui/README.md index f76d88e..f197c8c 100644 --- a/extensions/jui/README.md +++ b/extensions/jui/README.md @@ -6,7 +6,17 @@ and makes using JQuery UI widgets in Yii applications extremely easy. For exampl single line of code in a view file would render a JQuery UI DatePicker widget: ```php - 'start']) ?> + 'attributeName']) ?> +``` + +Configuring the Jquery UI options should be done using the clientOptions attribute: +```php + 'attributeName', 'clientOptions' => ['dateFormat' => 'yy-mm-dd']]) ?> +``` + +If you want to use the JUI widget in an ActiveRecord form, it can be done like this: +```php +field($model,'attributeName')->widget(DatePicker::className(),['clientOptions' => ['dateFormat' => 'yy-mm-dd']]) ?> ``` diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php index 3418d8d..fda0477 100644 --- a/extensions/swiftmailer/Mailer.php +++ b/extensions/swiftmailer/Mailer.php @@ -17,9 +17,9 @@ use yii\mail\BaseMailer; * To use Mailer, you should configure it in the application configuration like the following, * * ~~~ - * 'components' => array( + * 'components' => [ * ... - * 'email' => array( + * 'email' => [ * 'class' => 'yii\swiftmailer\Mailer', * 'transport' => [ * 'class' => 'Swift_SmtpTransport', @@ -29,9 +29,9 @@ use yii\mail\BaseMailer; * 'port' => '587', * 'encryption' => 'tls', * ], - * ), + * ], * ... - * ), + * ], * ~~~ * * You may also skip the configuration of the [[transport]] property. In that case, the default diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index b729158..0af586c 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -31,7 +31,8 @@ use yii\web\HttpException; * @property \yii\web\UrlManager $urlManager The URL manager for this application. This property is read-only. * @property string $vendorPath The directory that stores vendor files. Defaults to "vendor" directory under * [[basePath]]. - * @property View $view The view object that is used to render various view files. This property is read-only. + * @property View|\yii\web\View $view The view object that is used to render various view files. This property + * is read-only. * * @author Qiang Xue * @since 2.0 diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index 2ef4ead..48d4f05 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -452,7 +452,7 @@ class Component extends Object $event->data = $handler[1]; call_user_func($handler[0], $event); // stop further handling if the event is handled - if ($event instanceof Event && $event->handled) { + if ($event->handled) { return; } } diff --git a/framework/yii/caching/ApcCache.php b/framework/yii/caching/ApcCache.php index 6c2754a..688c3f1 100644 --- a/framework/yii/caching/ApcCache.php +++ b/framework/yii/caching/ApcCache.php @@ -72,6 +72,17 @@ class ApcCache extends Cache } /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $expire) + { + return array_keys(apc_store($data, null, $expire)); + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * @param string $key the key identifying the value to be cached @@ -85,6 +96,17 @@ class ApcCache extends Cache } /** + * Adds multiple key-value pairs to cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $expire) + { + return array_keys(apc_add($data, null, $expire)); + } + + /** * Deletes a value with the specified key from cache * This is the implementation of the method declared in the parent class. * @param string $key the key of the value to be deleted diff --git a/framework/yii/caching/Cache.php b/framework/yii/caching/Cache.php index b2d96dc..41a25b2 100644 --- a/framework/yii/caching/Cache.php +++ b/framework/yii/caching/Cache.php @@ -93,7 +93,7 @@ abstract class Cache extends Component implements \ArrayAccess * If the given key is a string containing alphanumeric characters only and no more than 32 characters, * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]]. - * + * * @param mixed $key the key to be normalized * @return string the generated cache key */ @@ -216,6 +216,69 @@ abstract class Cache extends Component implements \ArrayAccess } /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and + * expiration time will be replaced with the new ones, respectively. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + */ + public function mset($items, $expire = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + + $data = []; + foreach ($items as $key => $value) { + $itemKey = $this->buildKey($key); + if ($this->serializer === null) { + $itemValue = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $itemValue = call_user_func($this->serializer[0], [$value, $dependency]); + } + + $data[$itemKey] = $itemValue; + } + return $this->setValues($data, $expire); + } + + /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and expiration time will be preserved. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + */ + public function madd($items, $expire = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + + $data = []; + foreach ($items as $key => $value) { + $itemKey = $this->buildKey($key); + if ($this->serializer === null) { + $itemValue = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $itemValue = call_user_func($this->serializer[0], [$value, $dependency]); + } + + $data[$itemKey] = $itemValue; + } + return $this->addValues($data, $expire); + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * Nothing will be done if the cache already contains the key. * @param mixed $key a key identifying the value to be cached. This can be a simple string or @@ -327,6 +390,46 @@ abstract class Cache extends Component implements \ArrayAccess } /** + * Stores multiple key-value pairs in cache. + * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache + * storage supports multiset, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $expire) + { + $failedKeys = []; + foreach ($data as $key => $value) + { + if ($this->setValue($key, $value, $expire) === false) { + $failedKeys[] = $key; + } + } + return $failedKeys; + } + + /** + * Adds multiple key-value pairs to cache. + * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache + * storage supports multiadd, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $expire) + { + $failedKeys = []; + foreach ($data as $key => $value) + { + if ($this->addValue($key, $value, $expire) === false) { + $failedKeys[] = $key; + } + } + return $failedKeys; + } + + /** * Returns whether there is a cache entry with a specified key. * This method is required by the interface ArrayAccess. * @param string $key a key identifying the cached value diff --git a/framework/yii/caching/MemCache.php b/framework/yii/caching/MemCache.php index 6f7a760..3391796 100644 --- a/framework/yii/caching/MemCache.php +++ b/framework/yii/caching/MemCache.php @@ -202,6 +202,27 @@ class MemCache extends Cache } /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys. Always empty in case of using memcached. + */ + protected function setValues($data, $expire) + { + if ($this->useMemcached) { + if ($expire > 0) { + $expire += time(); + } else { + $expire = 0; + } + $this->_cache->setMulti($data, $expire); + return []; + } else { + return parent::setValues($data, $expire); + } + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * diff --git a/framework/yii/caching/WinCache.php b/framework/yii/caching/WinCache.php index 3679884..7f1eca8 100644 --- a/framework/yii/caching/WinCache.php +++ b/framework/yii/caching/WinCache.php @@ -72,6 +72,17 @@ class WinCache extends Cache } /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $expire) + { + return wincache_ucache_set($data, null, $expire); + } + + /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * @@ -86,6 +97,19 @@ class WinCache extends Cache } /** + * Adds multiple key-value pairs to cache. + * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache + * storage supports multiadd, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $expire) + { + return wincache_ucache_add($data, null, $expire); + } + + /** * Deletes a value with the specified key from cache * This is the implementation of the method declared in the parent class. * @param string $key the key of the value to be deleted diff --git a/framework/yii/classes.php b/framework/yii/classes.php index c89e4ff..9f39ee9 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -76,8 +76,12 @@ return [ 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php', + 'yii\db\ActiveQueryInterface' => YII_PATH . '/db/ActiveQueryInterface.php', + 'yii\db\ActiveQueryTrait' => YII_PATH . '/db/ActiveQueryTrait.php', 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php', 'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php', + 'yii\db\ActiveRelationInterface' => YII_PATH . '/db/ActiveRelationInterface.php', + 'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php', 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php', 'yii\db\Command' => YII_PATH . '/db/Command.php', 'yii\db\Connection' => YII_PATH . '/db/Connection.php', @@ -87,6 +91,8 @@ return [ 'yii\db\Migration' => YII_PATH . '/db/Migration.php', 'yii\db\Query' => YII_PATH . '/db/Query.php', 'yii\db\QueryBuilder' => YII_PATH . '/db/QueryBuilder.php', + 'yii\db\QueryInterface' => YII_PATH . '/db/QueryInterface.php', + 'yii\db\QueryTrait' => YII_PATH . '/db/QueryTrait.php', 'yii\db\Schema' => YII_PATH . '/db/Schema.php', 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php', @@ -149,6 +155,10 @@ return [ 'yii\log\FileTarget' => YII_PATH . '/log/FileTarget.php', 'yii\log\Logger' => YII_PATH . '/log/Logger.php', 'yii\log\Target' => YII_PATH . '/log/Target.php', + 'yii\mail\BaseMailer' => YII_PATH . '/mail/BaseMailer.php', + 'yii\mail\BaseMessage' => YII_PATH . '/mail/BaseMessage.php', + 'yii\mail\MailerInterface' => YII_PATH . '/mail/MailerInterface.php', + 'yii\mail\MessageInterface' => YII_PATH . '/mail/MessageInterface.php', 'yii\mutex\DbMutex' => YII_PATH . '/mutex/DbMutex.php', 'yii\mutex\FileMutex' => YII_PATH . '/mutex/FileMutex.php', 'yii\mutex\Mutex' => YII_PATH . '/mutex/Mutex.php', @@ -161,6 +171,8 @@ return [ 'yii\redis\Connection' => YII_PATH . '/redis/Connection.php', 'yii\redis\Transaction' => YII_PATH . '/redis/Transaction.php', 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php', + 'yii\test\DbFixtureManager' => YII_PATH . '/test/DbFixtureManager.php', + 'yii\test\DbTestTrait' => YII_PATH . '/test/DbTestTrait.php', 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php', 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php', 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php', @@ -169,6 +181,7 @@ return [ 'yii\validators\ExistValidator' => YII_PATH . '/validators/ExistValidator.php', 'yii\validators\FileValidator' => YII_PATH . '/validators/FileValidator.php', 'yii\validators\FilterValidator' => YII_PATH . '/validators/FilterValidator.php', + 'yii\validators\ImageValidator' => YII_PATH . '/validators/ImageValidator.php', 'yii\validators\InlineValidator' => YII_PATH . '/validators/InlineValidator.php', 'yii\validators\NumberValidator' => YII_PATH . '/validators/NumberValidator.php', 'yii\validators\PunycodeAsset' => YII_PATH . '/validators/PunycodeAsset.php', diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php index ac941a4..0e36911 100644 --- a/framework/yii/console/controllers/MigrateController.php +++ b/framework/yii/console/controllers/MigrateController.php @@ -594,7 +594,7 @@ class MigrateController extends Controller { echo 'Creating migration history table "' . $this->migrationTable . '"...'; $this->db->createCommand()->createTable($this->migrationTable, [ - 'version' => 'varchar(255) NOT NULL PRIMARY KEY', + 'version' => 'varchar(180) NOT NULL PRIMARY KEY', 'apply_time' => 'integer', ])->execute(); $this->db->createCommand()->insert($this->migrationTable, [ diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php index 5fee61c..dc75294 100644 --- a/framework/yii/data/Pagination.php +++ b/framework/yii/data/Pagination.php @@ -9,6 +9,7 @@ namespace yii\data; use Yii; use yii\base\Object; +use yii\web\Request; /** * Pagination represents information relevant to pagination of data items. @@ -83,7 +84,7 @@ class Pagination extends Object public $route; /** * @var array parameters (name => value) that should be used to obtain the current page number - * and to create new pagination URLs. If not set, $_GET will be used instead. + * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead. * * The array element indexed by [[pageVar]] is considered to be the current page number. * If the element does not exist, the current page number is considered 0. @@ -131,7 +132,10 @@ class Pagination extends Object public function getPage($recalculate = false) { if ($this->_page === null || $recalculate) { - $params = $this->params === null ? $_GET : $this->params; + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->get() : []; + } if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) { $this->_page = (int)$params[$this->pageVar] - 1; if ($this->validatePage) { @@ -169,7 +173,10 @@ class Pagination extends Object */ public function createUrl($page) { - $params = $this->params === null ? $_GET : $this->params; + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->get() : []; + } if ($page > 0 || $page >= 0 && $this->forcePageVar) { $params[$this->pageVar] = $page + 1; } else { diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php index 4d21fbe..517bf22 100644 --- a/framework/yii/db/ActiveQuery.php +++ b/framework/yii/db/ActiveQuery.php @@ -68,7 +68,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface if (!empty($rows)) { $models = $this->createModels($rows); if (!empty($this->with)) { - $this->populateRelations($models, $this->with); + $this->findWith($this->with, $models); } return $models; } else { @@ -98,7 +98,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface } if (!empty($this->with)) { $models = [$model]; - $this->populateRelations($models, $this->with); + $this->findWith($this->with, $models); $model = $models[0]; } return $model; diff --git a/framework/yii/db/ActiveQueryTrait.php b/framework/yii/db/ActiveQueryTrait.php index 51135d8..7aae6e6 100644 --- a/framework/yii/db/ActiveQueryTrait.php +++ b/framework/yii/db/ActiveQueryTrait.php @@ -21,7 +21,7 @@ trait ActiveQueryTrait */ public $modelClass; /** - * @var array list of relations that this query should be performed with + * @var array a list of relations that this query should be performed with */ public $with; /** @@ -143,10 +143,12 @@ trait ActiveQueryTrait } /** - * @param ActiveRecord[] $models - * @param array $with + * Finds records corresponding to one or multiple relations and populates them into the primary models. + * @param array $with a list of relations that this query should be performed with. Please + * refer to [[with()]] for details about specifying this parameter. + * @param ActiveRecord[] $models the primary models */ - private function populateRelations(&$models, $with) + public function findWith($with, &$models) { $primaryModel = new $this->modelClass; $relations = $this->normalizeRelations($primaryModel, $with); @@ -155,7 +157,7 @@ trait ActiveQueryTrait // inherit asArray from primary query $relation->asArray = $this->asArray; } - $relation->findWith($name, $models); + $relation->populateRelation($name, $models); } } diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 9edc824..e13bf90 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -1485,18 +1485,19 @@ class ActiveRecord extends Model } /** - * @param array $keys - * @return boolean + * Returns a value indicating whether the given set of attributes represents the primary key for this model + * @param array $keys the set of attributes to check + * @return boolean whether the given set of attributes represents the primary key for this model */ - private function isPrimaryKey($keys) + public static function isPrimaryKey($keys) { - $pks = $this->primaryKey(); + $pks = static::primaryKey(); foreach ($keys as $key) { if (!in_array($key, $pks, true)) { return false; } } - return true; + return count($keys) === count($pks); } /** diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php index ea9b87a..b016c5c 100644 --- a/framework/yii/db/ActiveRelation.php +++ b/framework/yii/db/ActiveRelation.php @@ -20,9 +20,6 @@ namespace yii\db; * * If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method. * - * @property array|ActiveRelation $via the query associated with the pivot table. Please call [[via()]] - * or [[viaTable()]] to set this property instead of directly setting it. - * * @author Qiang Xue * @author Carsten Brandt * @since 2.0 diff --git a/framework/yii/db/ActiveRelationTrait.php b/framework/yii/db/ActiveRelationTrait.php index 27963d0..be42eb6 100644 --- a/framework/yii/db/ActiveRelationTrait.php +++ b/framework/yii/db/ActiveRelationTrait.php @@ -73,13 +73,12 @@ trait ActiveRelationTrait /** * Finds the related records and populates them into the primary models. - * This method is internally used by [[ActiveQuery]]. Do not call it directly. * @param string $name the relation name * @param array $primaryModels primary models * @return array the related models - * @throws InvalidConfigException + * @throws InvalidConfigException if [[link]] is invalid */ - public function findWith($name, &$primaryModels) + public function populateRelation($name, &$primaryModels) { if (!is_array($this->link)) { throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.'); @@ -96,7 +95,7 @@ trait ActiveRelationTrait /** @var ActiveRelationTrait $viaQuery */ list($viaName, $viaQuery) = $this->via; $viaQuery->primaryModel = null; - $viaModels = $viaQuery->findWith($viaName, $primaryModels); + $viaModels = $viaQuery->populateRelation($viaName, $primaryModels); $this->filterByModels($viaModels); } else { $this->filterByModels($primaryModels); diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index 0b9d0f5..3131fa8 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -138,11 +138,15 @@ class Schema extends \yii\db\Schema foreach ($columns as $info) { $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column; - if ($column->isPrimaryKey) { - $table->primaryKey[] = $column->name; - if ($column->autoIncrement) { - $table->sequenceName = ''; - } + } + + $primaryKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $table->name); + foreach ($primaryKeys as $key) { + $column = $table->columns[$key['ATTR_NAME']]; + $column->isPrimaryKey = true; + $table->primaryKey[] = $column->name; + if ($column->autoIncrement) { + $table->sequenceName = ''; } } diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php index 71ad9ea..64b070b 100644 --- a/framework/yii/helpers/BaseHtml.php +++ b/framework/yii/helpers/BaseHtml.php @@ -653,6 +653,9 @@ class BaseHtml */ public static function dropDownList($name, $selection = null, $items = [], $options = []) { + if (!empty($options['multiple'])) { + return static::listBox($name, $selection, $items, $options); + } $options['name'] = $name; $selectOptions = static::renderSelectOptions($selection, $items, $options); return static::tag('select', "\n" . $selectOptions . "\n", $options); diff --git a/framework/yii/test/DbFixtureManager.php b/framework/yii/test/DbFixtureManager.php new file mode 100644 index 0000000..ed90284 --- /dev/null +++ b/framework/yii/test/DbFixtureManager.php @@ -0,0 +1,219 @@ + + * @since 2.0 + */ +class DbFixtureManager extends Component +{ + /** + * @var string the init script file that should be executed before running each test. + * This should be a path relative to [[basePath]]. + */ + public $initScript = 'init.php'; + /** + * @var string the base path containing all fixtures. This can be either a directory path or path alias. + */ + public $basePath = '@app/tests/fixtures'; + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbFixtureManager object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var array list of database schemas that the test tables may reside in. Defaults to + * array(''), meaning using the default schema (an empty string refers to the + * default schema). This property is mainly used when turning on and off integrity checks + * so that fixture data can be populated into the database without causing problem. + */ + public $schemas = ['']; + + private $_rows; // fixture name, row alias => row + private $_models; // fixture name, row alias => record (or class name) + private $_modelClasses; + + + /** + * Loads the specified fixtures. + * + * This method does the following things to load the fixtures: + * + * - Run [[initScript]] if any. + * - Clean up data and models loaded in memory previously. + * - Load each specified fixture by calling [[loadFixture()]]. + * + * @param array $fixtures a list of fixtures (fixture name => table name or AR class name) to be loaded. + * Each array element can be either a table name (with schema prefix if needed), or a fully-qualified + * ActiveRecord class name (e.g. `app\models\Post`). An element can be associated with a key + * which will be treated as the fixture name. + * @return array the loaded fixture data (fixture name => table rows) + * @throws InvalidConfigException if a model class specifying a fixture is not an ActiveRecord class. + */ + public function load(array $fixtures = []) + { + $this->basePath = Yii::getAlias($this->basePath); + + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException("The 'db' property must be either a DB connection instance or the application component ID of a DB connection."); + } + + foreach ($fixtures as $name => $fixture) { + if (strpos($fixture, '\\') !== false) { + $model = new $fixture; + if ($model instanceof ActiveRecord) { + $this->_modelClasses[$name] = $fixture; + $fixtures[$name] = $model->getTableSchema()->name; + } else { + throw new InvalidConfigException("Fixture '$fixture' must be an ActiveRecord class."); + } + } + } + + $this->_modelClasses = $this->_rows = $this->_models = []; + + $this->checkIntegrity(false); + + if (!empty($this->initScript)) { + $initFile = $this->basePath . '/' . $this->initScript; + if (is_file($initFile)) { + require($initFile); + } + } + + foreach ($fixtures as $name => $tableName) { + $rows = $this->loadFixture($tableName); + if (is_array($rows)) { + $this->_rows[$name] = $rows; + } + } + $this->checkIntegrity(true); + return $this->_rows; + } + + /** + * Loads the fixture for the specified table. + * + * This method does the following tasks to load the fixture for a table: + * + * - Remove existing rows in the table. + * - If there is any auto-incremental column, the corresponding sequence will be reset to 0. + * - If a fixture file is found, it will be executed, and its return value will be treated + * as rows which will then be inserted into the table. + * + * @param string $tableName table name + * @return array|boolean the loaded fixture rows indexed by row aliases (if any). + * False is returned if the table does not have a fixture. + * @throws InvalidConfigException if the specified table does not exist + */ + public function loadFixture($tableName) + { + $table = $this->db->getSchema()->getTableSchema($tableName); + if ($table === null) { + throw new InvalidConfigException("Table does not exist: $tableName"); + } + + $this->db->createCommand()->truncateTable($tableName); + + $fileName = $this->basePath . '/' . $tableName . '.php'; + if (!is_file($fileName)) { + return false; + } + + $rows = []; + foreach (require($fileName) as $alias => $row) { + $this->db->createCommand()->insert($tableName, $row)->execute(); + if ($table->sequenceName !== null) { + foreach ($table->primaryKey as $pk) { + if (!isset($row[$pk])) { + $row[$pk] = $this->db->getLastInsertID($table->sequenceName); + break; + } + } + } + $rows[$alias] = $row; + } + + return $rows; + } + + /** + * Returns the fixture data rows. + * The rows will have updated primary key values if the primary key is auto-incremental. + * @param string $fixtureName the fixture name + * @return array the fixture data rows. False is returned if there is no such fixture data. + */ + public function getRows($fixtureName) + { + return isset($this->_rows[$fixtureName]) ? $this->_rows[$fixtureName] : false; + } + + /** + * Returns the specified ActiveRecord instance in the fixture data. + * @param string $fixtureName the fixture name + * @param string $modelName the alias for the fixture data row + * @return \yii\db\ActiveRecord the ActiveRecord instance. Null is returned if there is no such fixture row. + */ + public function getModel($fixtureName, $modelName) + { + if (!isset($this->_modelClasses[$fixtureName]) || !isset($this->_rows[$fixtureName][$modelName])) { + return null; + } + if (isset($this->_models[$fixtureName][$modelName])) { + return $this->_models[$fixtureName][$modelName]; + } + $row = $this->_rows[$fixtureName][$modelName]; + /** @var \yii\db\ActiveRecord $modelClass */ + $modelClass = $this->_models[$fixtureName]; + /** @var \yii\db\ActiveRecord $model */ + $model = new $modelClass; + $keys = []; + foreach ($model->primaryKey() as $key) { + $keys[$key] = isset($row[$key]) ? $row[$key] : null; + } + return $this->_models[$fixtureName][$modelName] = $modelClass::find($keys); + } + + /** + * Enables or disables database integrity check. + * This method may be used to temporarily turn off foreign constraints check. + * @param boolean $check whether to enable database integrity check + */ + public function checkIntegrity($check) + { + foreach ($this->schemas as $schema) { + $this->db->createCommand()->checkIntegrity($check, $schema); + } + } +} diff --git a/framework/yii/test/DbTestTrait.php b/framework/yii/test/DbTestTrait.php new file mode 100644 index 0000000..8a6dc3c --- /dev/null +++ b/framework/yii/test/DbTestTrait.php @@ -0,0 +1,110 @@ +loadFixtures([ + * 'posts' => Post::className(), + * 'users' => User::className(), + * ]); + * } + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +trait DbTestTrait +{ + /** + * Loads the specified fixtures. + * + * This method should typically be called in the setup method of test cases so that + * the fixtures are loaded before running each test method. + * + * This method does the following things: + * + * - Run [[DbFixtureManager::initScript]] if it is found under [[DbFixtureManager::basePath]]. + * - Clean up data and models loaded in memory previously. + * - Load each specified fixture: + * * Truncate the corresponding table. + * * If a fixture file named `TableName.php` is found under [[DbFixtureManager::basePath]], + * the file will be executed, and the return value will be treated as rows which will + * then be inserted into the table. + * + * @param array $fixtures a list of fixtures (fixture name => table name or AR class name) to be loaded. + * Each array element can be either a table name (with schema prefix if needed), or a fully-qualified + * ActiveRecord class name (e.g. `app\models\Post`). An element can be optionally associated with a key + * which will be treated as the fixture name. For example, + * + * ~~~ + * [ + * 'tbl_comment', + * 'users' => 'tbl_user', // 'users' is the fixture name, 'tbl_user' is a table name + * 'posts' => 'app\models\Post, // 'app\models\Post' is a model class name + * ] + * ~~~ + * + * @return array the loaded fixture data (fixture name => table rows) + */ + public function loadFixtures(array $fixtures = []) + { + return $this->getFixtureManager()->load($fixtures); + } + + /** + * Returns the DB fixture manager. + * @return DbFixtureManager the DB fixture manager + */ + public function getFixtureManager() + { + return Yii::$app->getComponent('fixture'); + } + + /** + * Returns the table rows of the named fixture. + * @param string $fixtureName the fixture name. + * @return array the named fixture table rows. False is returned if there is no such fixture data. + */ + public function getFixtureRows($fixtureName) + { + return $this->getFixtureManager()->getRows($fixtureName); + } + + /** + * Returns the named AR instance corresponding to the named fixture. + * @param string $fixtureName the fixture name. + * @param string $modelName the name of the fixture data row + * @return \yii\db\ActiveRecord the named AR instance corresponding to the named fixture. + * Null is returned if there is no such fixture or the record cannot be found. + */ + public function getFixtureModel($fixtureName, $modelName) + { + return $this->getFixtureManager()->getModel($fixtureName, $modelName); + } +} diff --git a/framework/yii/test/TestCase.php b/framework/yii/test/TestCase.php deleted file mode 100644 index a6806b3..0000000 --- a/framework/yii/test/TestCase.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -abstract class TestCase extends \PHPUnit_Framework_TestCase -{ -} diff --git a/framework/yii/test/WebTestCase.php b/framework/yii/test/WebTestCase.php deleted file mode 100644 index 4308454..0000000 --- a/framework/yii/test/WebTestCase.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -abstract class WebTestCase extends \PHPUnit_Extensions_SeleniumTestCase -{ -} diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php index a93b915..1b7d1c8 100644 --- a/framework/yii/web/AssetConverter.php +++ b/framework/yii/web/AssetConverter.php @@ -74,11 +74,11 @@ class AssetConverter extends Component implements AssetConverterInterface '{from}' => escapeshellarg("$basePath/$asset"), '{to}' => escapeshellarg("$basePath/$result"), ]); - $descriptor = array( - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w'), - ); - $pipes = array(); + $descriptor = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $pipes = []; $proc = proc_open($command, $descriptor, $pipes, $basePath); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 2071afa..0b49730 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -35,7 +35,6 @@ use yii\helpers\Security; * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned * if no such header is sent. This property is read-only. * @property array $delete The DELETE request parameter values. This property is read-only. - * @property array $get The GET request parameter values. This property is read-only. * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. * `http://www.yiiframework.com`). * @property boolean $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only. @@ -312,15 +311,6 @@ class Request extends \yii\base\Request } /** - * Returns the GET request parameter values. - * @return array the GET request parameter values - */ - public function getGet() - { - return $_GET; - } - - /** * Returns the named POST parameter value. * If the POST parameter does not exist, the second parameter to this method will be returned. * @param string $name the POST parameter name. If not specified, whole $_POST is returned. diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index 9fba49a..894d75d 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -46,7 +46,7 @@ use yii\base\InvalidParamException; * call methods such as [[setFlash()]], [[getFlash()]]. * * @property array $allFlashes Flash messages (key => message). This property is read-only. - * @property array $cookieParams The session cookie parameters. + * @property array $cookieParams The session cookie parameters. This property is read-only. * @property integer $count The number of session variables. This property is read-only. * @property string $flash The key identifying the flash message. Note that flash messages and normal session * variables share the same name space. If you have a normal session variable using the same name, its value will diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index d3ac557..e83605e 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -2,10 +2,15 @@ namespace yiiunit; +require_once('PHPUnit/Runner/Version.php'); +spl_autoload_unregister(['Yii', 'autoload']); +require_once('PHPUnit/Autoload.php'); +spl_autoload_register(['Yii', 'autoload']); // put yii's autoloader at the end + /** * This is the base class for all yii framework unit tests. */ -abstract class TestCase extends \yii\test\TestCase +abstract class TestCase extends \PHPUnit_Framework_TestCase { public static $params; diff --git a/tests/unit/framework/base/ExceptionTest.php b/tests/unit/framework/base/ExceptionTest.php index af4293a..5a623b1 100644 --- a/tests/unit/framework/base/ExceptionTest.php +++ b/tests/unit/framework/base/ExceptionTest.php @@ -1,7 +1,7 @@ assertEquals('array_test', $array['array_test']); } + public function testMset() + { + $cache = $this->getCacheInstance(); + $cache->flush(); + + $cache->mset([ + 'string_test' => 'string_test', + 'number_test' => 42, + 'array_test' => ['array_test' => 'array_test'], + ]); + + $this->assertEquals('string_test', $cache->get('string_test')); + + $this->assertEquals(42, $cache->get('number_test')); + + $array = $cache->get('array_test'); + $this->assertArrayHasKey('array_test', $array); + $this->assertEquals('array_test', $array['array_test']); + } + public function testExists() { $cache = $this->prepare(); @@ -164,6 +184,21 @@ abstract class CacheTestCase extends TestCase $this->assertEquals(13, $cache->get('add_test')); } + public function testMadd() + { + $cache = $this->prepare(); + + $this->assertFalse($cache->get('add_test')); + + $cache->madd([ + 'number_test' => 13, + 'add_test' => 13, + ]); + + $this->assertEquals(42, $cache->get('number_test')); + $this->assertEquals(13, $cache->get('add_test')); + } + public function testDelete() { $cache = $this->prepare(); diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index f96f2d3..b2981f2 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -489,4 +489,21 @@ class ActiveRecordTest extends DatabaseTestCase $customers = Customer::find()->where(['status' => false])->all(); $this->assertEquals(1, count($customers)); } + + public function testIsPrimaryKey() + { + $this->assertFalse(Customer::isPrimaryKey([])); + $this->assertTrue(Customer::isPrimaryKey(['id'])); + $this->assertFalse(Customer::isPrimaryKey(['id', 'name'])); + $this->assertFalse(Customer::isPrimaryKey(['name'])); + $this->assertFalse(Customer::isPrimaryKey(['name', 'email'])); + + $this->assertFalse(OrderItem::isPrimaryKey([])); + $this->assertFalse(OrderItem::isPrimaryKey(['order_id'])); + $this->assertFalse(OrderItem::isPrimaryKey(['item_id'])); + $this->assertFalse(OrderItem::isPrimaryKey(['quantity'])); + $this->assertFalse(OrderItem::isPrimaryKey(['quantity', 'subtotal'])); + $this->assertTrue(OrderItem::isPrimaryKey(['order_id', 'item_id'])); + $this->assertFalse(OrderItem::isPrimaryKey(['order_id', 'item_id', 'quantity'])); + } } diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php index 159e41c..e566cec 100644 --- a/tests/unit/framework/helpers/ArrayHelperTest.php +++ b/tests/unit/framework/helpers/ArrayHelperTest.php @@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers; use yii\base\Object; use yii\helpers\ArrayHelper; -use yii\test\TestCase; +use yiiunit\TestCase; use yii\data\Sort; class Post1 diff --git a/tests/unit/framework/helpers/FileHelperTest.php b/tests/unit/framework/helpers/FileHelperTest.php index 3fde06e..3b5f49e 100644 --- a/tests/unit/framework/helpers/FileHelperTest.php +++ b/tests/unit/framework/helpers/FileHelperTest.php @@ -1,7 +1,7 @@ assertTrue(file_exists($dstDirName), 'Destination directory does not exist!'); + $this->assertFileExists($dstDirName, 'Destination directory does not exist!'); foreach ($files as $name => $content) { $fileName = $dstDirName . DIRECTORY_SEPARATOR . $name; - $this->assertTrue(file_exists($fileName), 'Directory file is missing!'); + $this->assertFileExists($fileName); $this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!'); } } @@ -189,7 +189,7 @@ class FileHelperTest extends TestCase FileHelper::removeDirectory($dirName); - $this->assertFalse(file_exists($dirName), 'Unable to remove directory!'); + $this->assertFileNotExists($dirName, 'Unable to remove directory!'); // should be silent about non-existing directories FileHelper::removeDirectory($basePath . DIRECTORY_SEPARATOR . 'nonExisting'); @@ -277,7 +277,7 @@ class FileHelperTest extends TestCase $basePath = $this->testFilePath; $dirName = $basePath . DIRECTORY_SEPARATOR . 'test_dir_level_1' . DIRECTORY_SEPARATOR . 'test_dir_level_2'; $this->assertTrue(FileHelper::createDirectory($dirName), 'FileHelper::createDirectory should return true if directory was created!'); - $this->assertTrue(file_exists($dirName), 'Unable to create directory recursively!'); + $this->assertFileExists($dirName, 'Unable to create directory recursively!'); $this->assertTrue(FileHelper::createDirectory($dirName), 'FileHelper::createDirectory should return true for already existing directories!'); } diff --git a/tests/unit/framework/helpers/JsonTest.php b/tests/unit/framework/helpers/JsonTest.php index d08ebb8..20da347 100644 --- a/tests/unit/framework/helpers/JsonTest.php +++ b/tests/unit/framework/helpers/JsonTest.php @@ -4,7 +4,7 @@ namespace yiiunit\framework\helpers; use yii\helpers\Json; -use yii\test\TestCase; +use yiiunit\TestCase; use yii\web\JsExpression; /** diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/unit/framework/helpers/StringHelperTest.php index 8af731d..2f1fb06 100644 --- a/tests/unit/framework/helpers/StringHelperTest.php +++ b/tests/unit/framework/helpers/StringHelperTest.php @@ -1,8 +1,8 @@