diff --git a/.gitignore b/.gitignore index 13fcf4a..f2915a9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Thumbs.db # composer itself is not needed composer.phar + +# Mac DS_Store Files +.DS_Store diff --git a/.travis.yml b/.travis.yml index c24c0b3..2add223 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,24 @@ php: - 5.4 - 5.5 +env: + - CUBRID_VERSION=9.1.0 + +services: + - redis-server + - memcached + before_script: - composer self-update && composer --version - composer require satooshi/php-coveralls 0.6.* - mysql -e 'CREATE DATABASE yiitest;'; - psql -U postgres -c 'CREATE DATABASE yiitest;'; + - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh + - tests/unit/data/travis/cubrid-setup.sh script: - - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml + - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata after_script: - - php vendor/bin/coveralls \ No newline at end of file + - php vendor/bin/coveralls diff --git a/apps/advanced/README.md b/apps/advanced/README.md index 7055360..3903532 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -74,6 +74,8 @@ You can then install the application using the following command: php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced ~~~ +Note that in order to install some dependencies you must have `php_openssl` extension enabled. + ### Install from an Archive File @@ -101,9 +103,11 @@ GETTING STARTED After you install the application, you have to conduct the following steps to initialize the installed application. You only need to do these once for all. -1. Execute the `init` command and select `dev` as environment. +1. Execute the `init` command and select `dev` as environment. Alternatively you can execute it as `init --env=Development` +or `init --env=Production`. 2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. 3. In `common/config/params.php` set your database details in `components.db` values. +4. Apply migrations with `yii migrate`. Now you should be able to access: diff --git a/apps/advanced/backend/config/main.php b/apps/advanced/backend/config/main.php index 377d34c..30c1825 100644 --- a/apps/advanced/backend/config/main.php +++ b/apps/advanced/backend/config/main.php @@ -17,13 +17,16 @@ return array( 'modules' => array( ), 'components' => array( + 'request' => array( + 'enableCsrfValidation' => true, + ), 'db' => $params['components.db'], 'cache' => $params['components.cache'], 'user' => array( - 'class' => 'yii\web\User', 'identityClass' => 'common\models\User', ), 'log' => array( + 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => array( array( 'class' => 'yii\log\FileTarget', @@ -31,6 +34,9 @@ return array( ), ), ), + 'errorHandler' => array( + 'errorAction' => 'site/error', + ), ), 'params' => $params, ); diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php index 480406a..28f2310 100644 --- a/apps/advanced/backend/controllers/SiteController.php +++ b/apps/advanced/backend/controllers/SiteController.php @@ -8,6 +8,36 @@ use common\models\LoginForm; class SiteController extends Controller { + public function behaviors() + { + return array( + 'access' => array( + 'class' => \yii\web\AccessControl::className(), + 'rules' => array( + array( + 'actions' => array('login'), + 'allow' => true, + 'roles' => array('?'), + ), + array( + 'actions' => array('logout', 'index'), + 'allow' => true, + 'roles' => array('@'), + ), + ), + ), + ); + } + + public function actions() + { + return array( + 'error' => array( + 'class' => 'yii\web\ErrorAction', + ), + ); + } + public function actionIndex() { return $this->render('index'); diff --git a/apps/advanced/backend/views/site/error.php b/apps/advanced/backend/views/site/error.php new file mode 100644 index 0000000..024e27d --- /dev/null +++ b/apps/advanced/backend/views/site/error.php @@ -0,0 +1,29 @@ +title = $name; +?> +
+ +

title); ?>

+ +
+ +
+ +

+ The above error occurred while the Web server was processing your request. +

+

+ Please contact us if you think this is a server error. Thank you. +

+ +
diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index e4101e8..62baf48 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -3,7 +3,7 @@ namespace common\models; use yii\db\ActiveRecord; use yii\helpers\Security; -use yii\web\Identity; +use yii\web\IdentityInterface; /** * Class User @@ -20,7 +20,7 @@ use yii\web\Identity; * @property integer $create_time * @property integer $update_time */ -class User extends ActiveRecord implements Identity +class User extends ActiveRecord implements IdentityInterface { /** * @var string the raw password. Used to collect password input and isn't saved in database @@ -49,7 +49,7 @@ class User extends ActiveRecord implements Identity * Finds an identity by the given ID. * * @param string|integer $id the ID to be looked for - * @return Identity|null the identity object that matches the given ID. + * @return IdentityInterface|null the identity object that matches the given ID. */ public static function findIdentity($id) { @@ -123,7 +123,6 @@ class User extends ActiveRecord implements Identity { return array( 'signup' => array('username', 'email', 'password'), - 'login' => array('username', 'password'), 'resetPassword' => array('password'), 'requestPasswordResetToken' => array('email'), ); diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 2d5b987..4c0fced 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -20,7 +20,8 @@ }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\InstallHandler::setPermissions", + "./init" ] }, "extra": { diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php index fdc131d..2689ed1 100644 --- a/apps/advanced/environments/dev/backend/config/main-local.php +++ b/apps/advanced/environments/dev/backend/config/main-local.php @@ -1,17 +1,11 @@ array( + //'debug', + ), 'modules' => array( // 'debug' => array( // 'class' => 'yii\debug\Module', // ), ), - 'components' => array( - 'log' => array( - 'targets' => array( -// array( -// 'class' => 'yii\log\DebugTarget', -// ) - ), - ), - ), ); diff --git a/apps/advanced/environments/dev/backend/web/index.php b/apps/advanced/environments/dev/backend/web/index.php index 7d47419..2113419 100644 --- a/apps/advanced/environments/dev/backend/web/index.php +++ b/apps/advanced/environments/dev/backend/web/index.php @@ -1,6 +1,6 @@ array( + //'debug', + ), 'modules' => array( // 'debug' => array( // 'class' => 'yii\debug\Module', // ), ), - 'components' => array( - 'log' => array( - 'targets' => array( -// array( -// 'class' => 'yii\log\DebugTarget', -// ) - ), - ), - ), ); diff --git a/apps/advanced/environments/dev/frontend/web/index.php b/apps/advanced/environments/dev/frontend/web/index.php index 82ca04c..9a7cbae 100644 --- a/apps/advanced/environments/dev/frontend/web/index.php +++ b/apps/advanced/environments/dev/frontend/web/index.php @@ -1,7 +1,6 @@ run(); +$exitCode = $application->run(); +exit($exitCode); diff --git a/apps/advanced/environments/prod/backend/web/index.php b/apps/advanced/environments/prod/backend/web/index.php index d351635..fc62a78 100644 --- a/apps/advanced/environments/prod/backend/web/index.php +++ b/apps/advanced/environments/prod/backend/web/index.php @@ -1,6 +1,6 @@ run(); +$exitCode = $application->run(); +exit($exitCode); diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index b9bfdae..975a3b4 100644 --- a/apps/advanced/frontend/config/main.php +++ b/apps/advanced/frontend/config/main.php @@ -14,15 +14,19 @@ return array( 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'controllerNamespace' => 'frontend\controllers', 'modules' => array( + 'gii' => 'yii\gii\Module' ), 'components' => array( + 'request' => array( + 'enableCsrfValidation' => true, + ), 'db' => $params['components.db'], 'cache' => $params['components.cache'], 'user' => array( - 'class' => 'yii\web\User', 'identityClass' => 'common\models\User', ), 'log' => array( + 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => array( array( 'class' => 'yii\log\FileTarget', @@ -30,6 +34,9 @@ return array( ), ), ), + 'errorHandler' => array( + 'errorAction' => 'site/error', + ), ), 'params' => $params, ); diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index 0c1b2f5..a9413de 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -12,11 +12,37 @@ use yii\helpers\Security; class SiteController extends Controller { + public function behaviors() + { + return array( + 'access' => array( + 'class' => \yii\web\AccessControl::className(), + 'only' => array('login', 'logout', 'signup'), + 'rules' => array( + array( + 'actions' => array('login', 'signup'), + 'allow' => true, + 'roles' => array('?'), + ), + array( + 'actions' => array('logout'), + 'allow' => true, + 'roles' => array('@'), + ), + ), + ), + ); + } + public function actions() { return array( + 'error' => array( + 'class' => 'yii\web\ErrorAction', + ), 'captcha' => array( 'class' => 'yii\captcha\CaptchaAction', + 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ), ); } @@ -138,7 +164,7 @@ class SiteController extends Controller $headers = "From: $name <{$fromEmail}>\r\n" . "MIME-Version: 1.0\r\n" . "Content-type: text/plain; charset=UTF-8"; - return mail($fromEmail, $subject, $body, $headers); + return mail($email, $subject, $body, $headers); } return false; diff --git a/apps/advanced/frontend/views/emails/passwordResetToken.php b/apps/advanced/frontend/views/emails/passwordResetToken.php index 11aa8e9..1e7a855 100644 --- a/apps/advanced/frontend/views/emails/passwordResetToken.php +++ b/apps/advanced/frontend/views/emails/passwordResetToken.php @@ -13,4 +13,4 @@ Hello username)?>, Follow the link below to reset your password: - \ No newline at end of file + diff --git a/apps/advanced/frontend/views/site/error.php b/apps/advanced/frontend/views/site/error.php new file mode 100644 index 0000000..024e27d --- /dev/null +++ b/apps/advanced/frontend/views/site/error.php @@ -0,0 +1,29 @@ +title = $name; +?> +
+ +

title); ?>

+ +
+ +
+ +

+ The above error occurred while the Web server was processing your request. +

+

+ Please contact us if you think this is a server error. Thank you. +

+ +
diff --git a/apps/advanced/frontend/widgets/Alert.php b/apps/advanced/frontend/widgets/Alert.php index 20f3372..b68bfb0 100644 --- a/apps/advanced/frontend/widgets/Alert.php +++ b/apps/advanced/frontend/widgets/Alert.php @@ -23,13 +23,13 @@ class Alert extends \yii\bootstrap\Alert private $_doNotRender = false; public function init() { - if ($this->body = \Yii::$app->getSession()->getFlash('error')) { + if ($this->body = \Yii::$app->getSession()->getFlash('error', null, true)) { Html::addCssClass($this->options, 'alert-danger'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('success')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('success', null, true)) { Html::addCssClass($this->options, 'alert-success'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('info')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('info', null, true)) { Html::addCssClass($this->options, 'alert-info'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning', null, true)) { Html::addCssClass($this->options, 'alert-warning'); } else { $this->_doNotRender = true; diff --git a/apps/advanced/init b/apps/advanced/init index 17ed854..3a8f6a6 100755 --- a/apps/advanced/init +++ b/apps/advanced/init @@ -1,27 +1,49 @@ #!/usr/bin/env php $name) { - echo " [$i] $name\n"; +echo "Yii Application Initialization Tool v1.0\n\n"; + +$envName = null; +if (empty($params['env'])) { + echo "Which environment do you want the application to be initialized in?\n\n"; + foreach ($envNames as $i => $name) { + echo " [$i] $name\n"; + } + echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; + $answer = trim(fgets(STDIN)); + + if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { + echo "\n Quit initialization.\n"; + exit(1); + } + + if(isset($envNames[$answer])) { + $envName = $envNames[$answer]; + } +} +else { + $envName = $params['env']; } -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit initialization.\n"; - return; + +if (!in_array($envName, $envNames)) { + $envsList = implode(', ', $envNames); + echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; + exit(2); } -$env = $envs[$envNames[$answer]]; -echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit initialization.\n"; - return; +$env = $envs[$envName]; + +if (empty($params['env'])) { + echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; + $answer = trim(fgets(STDIN)); + if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + exit(1); + } } echo "\n Start initialization ...\n\n"; @@ -110,3 +132,23 @@ function copyFile($root, $source, $target, &$all) file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); return true; } + +function getParams() +{ + $rawParams = array(); + if (isset($_SERVER['argv'])) { + $rawParams = $_SERVER['argv']; + array_shift($rawParams); + } + + $params = array(); + foreach ($rawParams as $param) { + if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } else { + $params[] = $param; + } + } + return $params; +} diff --git a/apps/basic/README.md b/apps/basic/README.md index b5e1ec2..aaa7fba 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -56,6 +56,8 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-basic yii-basi Now you should be able to access the application using the URL `http://localhost/yii-basic/web/`, assuming `yii-basic` is directly under the document root of your Web server. +Note that in order to install some dependencies you must have `php_openssl` extension enabled. + ### Install from an Archive File diff --git a/apps/basic/config/web.php b/apps/basic/config/web.php index 1433a64..e7d9420 100644 --- a/apps/basic/config/web.php +++ b/apps/basic/config/web.php @@ -1,9 +1,12 @@ 'bootstrap', 'basePath' => dirname(__DIR__), 'components' => array( + 'request' => array( + 'enableCsrfValidation' => true, + ), 'cache' => array( 'class' => 'yii\caching\FileCache', ), @@ -23,7 +26,7 @@ $config = array( ), ), ), - 'params' => require(__DIR__ . '/params.php'), + 'params' => $params, ); if (YII_ENV_DEV) { diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index cd0b3fb..1196280 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -3,12 +3,42 @@ namespace app\controllers; use Yii; +use yii\web\AccessControl; use yii\web\Controller; +use yii\web\VerbFilter; use app\models\LoginForm; use app\models\ContactForm; class SiteController extends Controller { + public function behaviors() + { + return array( + 'access' => array( + 'class' => AccessControl::className(), + 'only' => array('login', 'logout'), + 'rules' => array( + array( + 'actions' => array('login'), + 'allow' => true, + 'roles' => array('?'), + ), + array( + 'actions' => array('logout'), + 'allow' => true, + 'roles' => array('@'), + ), + ), + ), + 'verbs' => array( + 'class' => VerbFilter::className(), + 'actions' => array( + 'logout' => array('post'), + ), + ), + ); + } + public function actions() { return array( diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php index afbf9f8..e1088a0 100644 --- a/apps/basic/models/User.php +++ b/apps/basic/models/User.php @@ -2,7 +2,7 @@ namespace app\models; -class User extends \yii\base\Object implements \yii\web\Identity +class User extends \yii\base\Object implements \yii\web\IdentityInterface { public $id; public $username; diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 04a2f33..1b7083d 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -36,7 +36,9 @@ app\config\AppAsset::register($this); array('label' => 'Contact', 'url' => array('/site/contact')), Yii::$app->user->isGuest ? array('label' => 'Login', 'url' => array('/site/login')) : - array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')), + array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , + 'url' => array('/site/logout'), + 'linkOptions' => array('data-method' => 'post')), ), )); NavBar::end(); diff --git a/apps/basic/views/site/login.php b/apps/basic/views/site/login.php index 524b0cc..f61d9d7 100644 --- a/apps/basic/views/site/login.php +++ b/apps/basic/views/site/login.php @@ -15,20 +15,33 @@ $this->params['breadcrumbs'][] = $this->title;

Please fill out the following fields to login:

-
-
- 'login-form')); ?> - field($model, 'username'); ?> - field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe')->checkbox(); ?> -
- 'btn btn-primary')); ?> -
- -
-
- You may login with admin/admin or demo/demo.
- To modify the username/password, please check out the code app\models\User::$users. + 'login-form', + 'options' => array('class' => 'form-horizontal'), + 'fieldConfig' => array( + 'template' => "{label}\n
{input}
\n
{error}
", + 'labelOptions' => array('class' => 'col-lg-1 control-label'), + ), + )); ?> + + field($model, 'username'); ?> + + field($model, 'password')->passwordInput(); ?> + + field($model, 'rememberMe', array( + 'template' => "
{input}
\n
{error}
", + ))->checkbox(); ?> + +
+
+ 'btn btn-primary')); ?>
+ + + +
+ You may login with admin/admin or demo/demo.
+ To modify the username/password, please check out the code app\models\User::$users. +
diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md index 2aeb0ae..2d5871a 100644 --- a/apps/benchmark/README.md +++ b/apps/benchmark/README.md @@ -54,3 +54,5 @@ http://localhost/yii-benchmark/index.php/site/hello In the above, we assume `yii-benchmark` is directly under the document root of your Web server. +Note that in order to install some dependencies you must have `php_openssl` extension enabled. + diff --git a/build/build.xml b/build/build.xml index 9c18af0..b0975dc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and YiiBase.php + - sync : synchronize yiilite.php and BaseYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 2a0483c..6a5ac8c 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -45,7 +45,7 @@ class ClassmapController extends Controller 'only' => array('.php'), 'except' => array( 'Yii.php', - 'YiiBase.php', + 'BaseYii.php', '/debug/', '/console/', '/test/', diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 5aac7e5..cb574ff 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -56,7 +56,7 @@ class PhpDocController extends Controller }, 'only' => array('.php'), 'except' => array( - 'YiiBase.php', + 'BaseYii.php', 'Yii.php', '/debug/views/', '/requirements/', diff --git a/docs/guide/active-record.md b/docs/guide/active-record.md index bb39115..fc98f98 100644 --- a/docs/guide/active-record.md +++ b/docs/guide/active-record.md @@ -1,17 +1,15 @@ Active Record ============= -ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). -The idea is that an [[ActiveRecord]] object is associated with a row in a database table and its attributes are mapped -to the columns of the corresponding table columns. Reading an ActiveRecord attribute is equivalent to accessing -the corresponding table column. For example, a `Customer` object is associated with a row in the -`tbl_customer` table, and its `name` attribute is mapped to the `name` column in the `tbl_customer` table. -To get the value of the `name` column in the table row, you can simply use the expression `$customer->name`, -just like reading an object property. - -Instead of writing raw SQL statements to perform database queries, you can call intuitive methods provided -by ActiveRecord to achieve the same goals. For example, calling [[ActiveRecord::save()|save()]] would -insert or update a row in the associated table of the ActiveRecord class: +Active Record implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). +The premise behind Active Record is that an individual [[ActiveRecord]] object is associated with a specific row in a database table. The object's attributes are mapped to the columns of the corresponding table. Referencing an Active Record attribute is equivalent to accessing +the corresponding table column for that record. + +As an example, say that the `Customer` ActiveRecord class is associated with the +`tbl_customer` table. This would mean that the class's `name` attribute is automatically mapped to the `name` column in `tbl_customer`. +Thanks to Active Record, assuming the variable `$customer` is an object of type `Customer`, to get the value of the `name` column for the table row, you can use the expression `$customer->name`. In this example, Active Record is providing an object-oriented interface for accessing data stored in the database. But Active Record provides much more functionality than this. + +With Active Record, instead of writing raw SQL statements to perform database queries, you can call intuitive methods to achieve the same goals. For example, calling [[ActiveRecord::save()|save()]] would perform an INSERT or UPDATE query, creating or updating a row in the associated table of the ActiveRecord class: ```php $customer = new Customer(); @@ -24,7 +22,7 @@ Declaring ActiveRecord Classes ------------------------------ To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and -implement the `tableName` method like the following: +implement the `tableName` method: ```php use yii\db\ActiveRecord; @@ -41,13 +39,19 @@ class Customer extends ActiveRecord } ``` -Connecting to Database +The `tableName` method only has to return the name of the database table associated with the class. + +Class instances are obtained in one of two ways: + +* Using the `new` operator to create a new, empty object +* Using a method to fetch an existing record (or records) from the database + +Connecting to the Database ---------------------- ActiveRecord relies on a [[Connection|DB connection]] to perform the underlying DB operations. -By default, it assumes that there is an application component named `db` which gives the needed -[[Connection]] instance. Usually this component is configured via application configuration -like the following: +By default, ActiveRecord assumes that there is an application component named `db` which provides the needed +[[Connection]] instance. Usually this component is configured in application configuration file: ```php return array( @@ -62,16 +66,9 @@ return array( ); ``` -Please read the [Database basics](database-basics.md) section to learn more on how to configure -and use database connections. - -> Tip: To use a different database connection, you may override the [[ActiveRecord::getDb()]] method. -You may create a base ActiveRecord class and override its [[ActiveRecord::getDb()]] method. You -then extend from this base class for all those ActiveRecord classes that need to use the same -DB connection. +Please read the [Database basics](database-basics.md) section to learn more on how to configure and use database connections. - -Querying Data from Database +Querying Data from the Database --------------------------- There are two ActiveRecord methods for querying data from database: @@ -79,8 +76,8 @@ There are two ActiveRecord methods for querying data from database: - [[ActiveRecord::find()]] - [[ActiveRecord::findBySql()]] -They both return an [[ActiveQuery]] instance which extends from [[Query]] and thus supports -the same set of flexible and powerful DB query methods. The followings are some examples, +Both methods return an [[ActiveQuery]] instance, which extends [[Query]], and thus supports +the same set of flexible and powerful DB query methods. The following examples demonstrate some of the possibilities. ```php // to retrieve all *active* customers and order them by their ID: @@ -121,27 +118,26 @@ $customers = Customer::find()->indexBy('id')->all(); Accessing Column Data --------------------- -ActiveRecord maps each column of the corresponding database table row to an *attribute* in the ActiveRecord -object. An attribute is like a regular object property whose name is the same as the corresponding column -name and is case-sensitive. +ActiveRecord maps each column of the corresponding database table row to an attribute in the ActiveRecord +object. The attribute behaves like any regular object public property. The attribute's name will be the same as the corresponding column +name, and is case-sensitive. -To read the value of a column, you can use the following expression: +To read the value of a column, you can use the following syntax: ```php -// "id" is the name of a column in the table associated with $customer ActiveRecord object +// "id" and "email" are the names of columns in the table associated with $customer ActiveRecord object $id = $customer->id; -// or alternatively, -$id = $customer->getAttribute('id'); +$email = $customer->email; ``` -You can get all column values through the [[ActiveRecord::attributes]] property: +To change the value of a column, assign a new value to the associated property and save the object: -```php -$values = $customer->attributes; +``` +$customer->email = 'jane@example.com'; +$customer->save(); ``` - -Manipulating Data in Database +Manipulating Data in the Database ----------------------------- ActiveRecord provides the following methods to insert, update and delete data in the database: @@ -156,10 +152,8 @@ ActiveRecord provides the following methods to insert, update and delete data in - [[ActiveRecord::deleteAll()|deleteAll()]] Note that [[ActiveRecord::updateAll()|updateAll()]], [[ActiveRecord::updateAllCounters()|updateAllCounters()]] -and [[ActiveRecord::deleteAll()|deleteAll()]] are static methods and apply to the whole database -table, while the rest of the methods only apply to the row associated with the ActiveRecord object. - -The followings are some examples: +and [[ActiveRecord::deleteAll()|deleteAll()]] are static methods that apply to the whole database +table. The other methods only apply to the row associated with the ActiveRecord object through which the method is being called. ```php // to insert a new customer record @@ -181,12 +175,21 @@ $customer->delete(); Customer::updateAllCounters(array('age' => 1)); ``` +Notice that you can always use the `save` method, and ActiveRecord will automatically perform an INSERT for new records and an UPDATE for existing ones. + +Data Input and Validation +------------------------- + +ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called +automatically when `save()` is performed. If data validation fails, the saving operation will be cancelled. + +For more details refer to the [Model](model.md) section of this guide. Querying Relational Data ------------------------ -You can use ActiveRecord to query the relational data of a table. The relational data returned can -be accessed like a property of the ActiveRecord object associated with the primary table. +You can use ActiveRecord to also query a table's relational data (i.e., selection of data from Table A can also pull in related data from Table B). Thanks to ActiveRecord, the relational data returned can be accessed like a property of the ActiveRecord object associated with the primary table. + For example, with an appropriate relation declaration, by accessing `$customer->orders` you may obtain an array of `Order` objects which represent the orders placed by the specified customer. @@ -405,15 +408,6 @@ The [[link()]] call above will set the `customer_id` of the order to be the prim value of `$customer` and then call [[save()]] to save the order into database. -Data Input and Validation -------------------------- - -ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called -automatically when `save()` is performed. If data validation fails, the saving operation will be cancelled. - -For more details refer to the [Model](model.md) section of this guide. - - Life Cycles of an ActiveRecord Object ------------------------------------- diff --git a/docs/guide/bootstrap-widgets.md b/docs/guide/bootstrap-widgets.md index 0739847..432dcd8 100644 --- a/docs/guide/bootstrap-widgets.md +++ b/docs/guide/bootstrap-widgets.md @@ -1,18 +1,17 @@ Bootstrap widgets ================= -Yii includes support of [Bootstrap 3](http://getbootstrap.com/) markup and components framework out of the box. It is an -excellent framework that allows you to speed up development a lot. +Out of the box, Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework (also known as "Twitter Bootstrap"). Bootstrap is an excellent, responsive framework that can greatly speed up the client-side of your development process. -Bootstrap is generally about two parts: +The core of Bootstrap is represented by two parts: -- Basics such as grid system, typography, helper classes and responsive utilities. -- Ready to use components such as menus, pagination, modal boxes, tabs etc. +- CSS basics, such as a grid layout system, typography, helper classes, and responsive utilities. +- Ready to use components, such as menus, pagination, modal boxes, tabs etc. Basics ------ -Yii doesn't wrap bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details +Yii doesn't wrap the bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details about using the basics at [bootstrap documentation website](http://getbootstrap.com/css/). Still Yii provides a convenient way to include bootstrap assets in your pages with a single line added to `AppAsset.php` located in your `config` directory: diff --git a/docs/guide/database-basics.md b/docs/guide/database-basics.md index 71510f4..85bc042 100644 --- a/docs/guide/database-basics.md +++ b/docs/guide/database-basics.md @@ -2,8 +2,14 @@ Database basics =============== Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides -uniform API and solves some inconsistencies between different DBMS. By default Yii supports MySQL, SQLite, PostgreSQL, -Oracle and MSSQL. +uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS: + +- [MySQL](http://www.mysql.com/) +- [SQLite](http://sqlite.org/) +- [PostgreSQL](http://www.postgresql.org/) +- [CUBRID](http://www.cubrid.org/) (version 9.1.0 and higher). +- Oracle +- MSSQL Configuration @@ -22,6 +28,7 @@ return array( 'dsn' => 'mysql:host=localhost;dbname=mydatabase', // MySQL, MariaDB //'dsn' => 'sqlite:/path/to/database/file', // SQLite //'dsn' => 'pgsql:host=localhost;port=5432;dbname=mydatabase', // PostgreSQL + //'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', // CUBRID //'dsn' => 'sqlsrv:Server=localhost;Database=mydatabase', // MS SQL Server, sqlsrv driver //'dsn' => 'dblib:host=localhost;dbname=mydatabase', // MS SQL Server, dblib driver //'dsn' => 'mssql:host=localhost;dbname=mydatabase', // MS SQL Server, mssql driver @@ -34,8 +41,10 @@ return array( // ... ); ``` +Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) for more details +on the format of the DSN string. -After the component is configured you can access it using the following syntax: +After the connection component is configured you can access it using the following syntax: ```php $connection = \Yii::$app->db; @@ -79,7 +88,7 @@ When only a single row is returned: ```php $command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=1'); -$post = $command->query(); +$post = $command->queryOne(); ``` When there are multiple values from the same column: diff --git a/docs/guide/model.md b/docs/guide/model.md index fec7ac3..b93fb7a 100644 --- a/docs/guide/model.md +++ b/docs/guide/model.md @@ -1,15 +1,15 @@ Model ===== -A model in Yii is intended for application data storage and has the following basic features: +In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data. Yii models have the following basic features: -- attribute declaration: a model defines what is considered an attribute. -- attribute labels: each attribute may be associated with a label for display purpose. -- massive attribute assignment. -- scenario-based data validation. +- Attribute declaration: a model defines what is considered an attribute. +- Attribute labels: each attribute may be associated with a label for display purpose. +- Massive attribute assignment: the ability to populate multiple model attributes in one step. +- Scenario-based data validation. -Models extending from [[\yii\base\Model]] class are typically used to hold data and corresponding validation rules of complex web forms. -The class is also a base for more advanced models with additional functionality such as [Active Record](active-record.md). +Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data. The validation rules greatly simply the generation of models from complex web forms. +The Model class is also the base for more advanced models with additional functionality such as [Active Record](active-record.md). Attributes ---------- diff --git a/docs/guide/overview.md b/docs/guide/overview.md index d2ccb19..835c511 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -1,8 +1,7 @@ What is Yii =========== -Yii is a high-performance, component-based PHP framework for developing -large-scale Web applications rapidly. It enables maximum reusability in Web +Yii is a high-performance, component-based PHP framework for rapidly developing large-scale Web applications. Yii enables maximum reusability in Web programming and can significantly accelerate your Web application development process. The name Yii (pronounced `Yee` or `[ji:]`) is an acronym for **Yes It Is!**. @@ -12,7 +11,7 @@ Requirements ------------ To run a Yii-powered Web application, you need a Web server that supports -PHP 5.3.?. +PHP 5.3.? or greater. For developers who want to use Yii, understanding object-oriented programming (OOP) is very helpful, because Yii is a pure OOP framework. @@ -25,16 +24,15 @@ Yii is a generic Web programming framework that can be used for developing virtually any type of Web application. Because it is light-weight and equipped with sophisticated caching mechanisms, it is especially suited to high-traffic applications, such as portals, forums, content -management systems (CMS), e-commerce systems, etc. +management systems (CMS), e-commerce projects, etc. How does Yii Compare with Other Frameworks? ------------------------------------------- -- Like most PHP frameworks, Yii is an MVC (Model-View-Controller) framework. -- It is a fullstack framework providing many solutions and components such as logging, session management, caching etc. -- It has a good balance of simplicity and features. -- Syntax and overall development usability are taken seriously. -- Performance is one of the key goals. -- We are constantly watching other web frameworks out there and getting the best ideas in. Initial Yii release was heavily - influenced by Ruby on Rails. Still, we aren't blindly copying anything. +- Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach. +- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching etc. +- Yii strikes a good balance between simplicity and features. +- Syntax and overall development usability are taken seriously by the Yii development team. +- Performance is one of the key goals for the Yii framework. +- The Yii development team is constantly watching what other Web frameworks are doing to see what best practices and features should be incorporated into Yii. The initial Yii release was heavily influenced by Ruby on Rails. Still, no framework or feature is being blindly copied into Yii; all decisions are based upon what's best for Web developers and in keeping with Yii's philosophy. diff --git a/docs/guide/security.md b/docs/guide/security.md index f9adf7c..af30e5b 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -4,12 +4,9 @@ Security Hashing and verifying passwords ------------------------------ -It is important not to store passwords in plain text but, contrary to popular belief, just using `md5` or `sha1` to -compute and verify hashes isn't a good way either. Modern hardware allows to brute force these very fast. +Most developers know that you cannot store passwords in plain text, but many believe it's safe to hash passwords using `md5` or `sha1`. There was a time when those hashing algorithms were sufficient, but modern hardware makes it possible to break those hashes very quickly using a brute force attack. -In order to truly secure user passwords even in case your database is leaked you need to use a function that is resistant -to brute-force such as bcrypt. In PHP it can be achieved by using [crypt function](http://php.net/manual/en/function.crypt.php) -but since usage isn't trivial and one can easily misuse it, Yii provides two helper functions for generating hash from +In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is bcrypt. In PHP, you can create a bcrypt hash by using [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions for generating hash from password and verifying existing hash. When user sets his password we're taking password string from POST and then getting a hash: diff --git a/docs/guide/template.md b/docs/guide/template.md index f2a6fc4..f9405ff 100644 --- a/docs/guide/template.md +++ b/docs/guide/template.md @@ -1,11 +1,10 @@ Using template engines ====================== -By default Yii uses PHP as template language but you can configure it to be able -to render templates with special engines such as Twig or Smarty. +By default Yii uses PHP as template language, but you can configure it to support other rendering engines, such as [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/). -The component responsible for rendering a view is called `view`. You can add -a custom template engines as follows: +The `view` component is responsible for rendering views. You can add +a custom template engines by reconfiguring this component's behavior: ```php array( @@ -27,15 +26,13 @@ array( ) ``` -Note that Smarty and Twig are not bundled with Yii and you have to download and -unpack these yourself and then specify `twigPath` and `smartyPath` respectively. +Note that the Smarty and Twig packages themselves are not bundled with Yii. You must download them yourself. Then unpack the packages and place the resulting files in a logical location, such as the application's `protected/vendor` folder. Finally, specify the correct `smartyPath` or `twigPath`, as in the code above (for Twig). Twig ---- -In order to use Twig you need to put you templates in files with extension `.twig` -(or another one if configured differently). -Also you need to specify this extension explicitly when calling `$this->render()` +To use Twig, you need to create templates in files with the `.twig` extension (or use another file extension but configure the component accordingly). +Unlike standard view files, when using Twig, you must include the extension when calling `$this->render()` or `$this->renderPartial()` from your controller: ```php @@ -44,25 +41,25 @@ echo $this->render('renderer.twig', array('username' => 'Alex')); ### Additional functions -Additionally to regular Twig syntax the following is available in Yii: +Yii adds the following construct to the standard Twig syntax: ```php {{ post.title }} ``` -path function calls `Html::url()` internally. +Internally, the `path()` function calls Yii's `Html::url()` method. ### Additional variables -- `app` = `\Yii::$app` -- `this` = current `View` object +Within Twig templates, you can also make use of these variables: + +- `app`, which equates to `\Yii::$app` +- `this`, which equates to the current `View` object Smarty ------ -In order to use Smarty you need to put you templates in files with extension `.tpl` -(or another one if configured differently). -Also you need to specify this extension explicitly when calling `$this->render()` +To use Smarty, you need to create templates in files with the `.tpl` extension (or use another file extension but configure the component accordingly). Unlike standard view files, when using Smarty, you must include the extension when calling `$this->render()` or `$this->renderPartial()` from your controller: ```php @@ -71,16 +68,18 @@ echo $this->render('renderer.tpl', array('username' => 'Alex')); ### Additional functions -Additionally to regular Smarty syntax the following is available in Yii: +Yii adds the following construct to the standard Smarty syntax: ```php {$post.title} ``` -path function calls `Html::url()` internally. +Internally, the `path()` function calls Yii's `Html::url()` method. ### Additional variables -- `$app` = `\Yii::$app` -- `$this` = current `View` object +Within Smarty templates, you can also make use of these variables: + +- `$app`, which equates to `\Yii::$app` +- `$this`, which equates to the current `View` object diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index ee2a3d5..f174864 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -450,11 +450,11 @@ This feature is especially useful if you are developing an application that supp different DBMS. -User and Identity ------------------ +User and IdentityInterface +-------------------------- The `CWebUser` class in 1.1 is now replaced by `\yii\Web\User`, and there is no more -`CUserIdentity` class. Instead, you should implement the `Identity` interface which +`CUserIdentity` class. Instead, you should implement the `IdentityInterface` which is much more straightforward to implement. The bootstrap application provides such an example. diff --git a/docs/guide/validation.md b/docs/guide/validation.md index 7bfeb96..0322573 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -7,22 +7,176 @@ In order to learn model validation basics please refer to [Model, Validation sub Standard Yii validators ----------------------- -- `boolean`: [[BooleanValidator]] -- `captcha`: [[CaptchaValidator]] -- `compare`: [[CompareValidator]] -- `date`: [[DateValidator]] -- `default`: [[DefaultValueValidator]] -- `double`: [[NumberValidator]] -- `email`: [[EmailValidator]] -- `exist`: [[ExistValidator]] -- `file`: [[FileValidator]] -- `filter`: [[FilterValidator]] -- `in`: [[RangeValidator]] -- `integer`: [[NumberValidator]] -- `match`: [[RegularExpressionValidator]] -- `required`: [[RequiredValidator]] -- `string`: [[StringValidator]] -- `unique`: [[UniqueValidator]] -- `url`: [[UrlValidator]] +Standard Yii validators could be specified using aliases instead of referring to class names. Here's the list of all +validators budled with Yii with their most useful properties: + +### `boolean`: [[BooleanValidator]] + +Checks if the attribute value is a boolean value. + +- `trueValue`, the value representing true status. _(1)_ +- `falseValue`, the value representing false status. _(0)_ +- `strict`, whether to compare the type of the value and `trueValue`/`falseValue`. _(false)_ + +### `captcha`: [[CaptchaValidator]] + +Validates that the attribute value is the same as the verification code displayed in the CAPTCHA. Should be used together +with [[CaptchaAction]]. + +- `caseSensitive` whether the comparison is case sensitive. _(false)_ +- `captchaAction` the route of the controller action that renders the CAPTCHA image. _('site/captcha')_ + +### `compare`: [[CompareValidator]] + +Compares the specified attribute value with another value and validates if they are equal. + +- `compareAttribute` the name of the attribute to be compared with. _(currentAttribute_repeat)_ +- `compareValue` the constant value to be compared with. +- `operator` the operator for comparison. _('==')_ + +### `date`: [[DateValidator]] + +Verifies if the attribute represents a date, time or datetime in a proper format. + +- `format` the date format that the value being validated should follow accodring to [[http://www.php.net/manual/en/datetime.createfromformat.php]]. _('Y-m-d')_ +- `timestampAttribute` the name of the attribute to receive the parsing result. + +### `default`: [[DefaultValueValidator]] + +Sets the attribute to be the specified default value. + +- `value` the default value to be set to the specified attributes. + +### `double`: [[NumberValidator]] + +Validates that the attribute value is a number. + +- `max` limit of the number. _(null)_ +- `min` lower limit of the number. _(null)_ + +### `email`: [[EmailValidator]] + +Validates that the attribute value is a valid email address. + +- `allowName` whether to allow name in the email address (e.g. `John Smith `). _(false)_. +- `checkMX` whether to check the MX record for the email address. _(false)_ +- `checkPort` whether to check port 25 for the email address. _(false)_ +- `enableIDN` whether validation process should take into account IDN (internationalized domain names). _(false)_ + +### `exist`: [[ExistValidator]] + +Validates that the attribute value exists in a table. + +- `className` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being + validated. _(ActiveRecord class of the attribute being validated)_ +- `attributeName` the ActiveRecord attribute name that should be used to look for the attribute value being validated. + _(name of the attribute being validated)_ + +### `file`: [[FileValidator]] + +Verifies if an attribute is receiving a valid uploaded file. + +- `types` a list of file name extensions that are allowed to be uploaded. _(any)_ +- `minSize` the minimum number of bytes required for the uploaded file. +- `maxSize` the maximum number of bytes required for the uploaded file. +- `maxFiles` the maximum file count the given attribute can hold. _(1)_ + +### `filter`: [[FilterValidator]] + +Converts the attribute value according to a filter. + +- `filter` PHP callback that defines a filter. + +Typically a callback is either the name of PHP function: + +```php +array('password', 'filter', 'filter' => 'trim'), +``` + +Or an anonymous function: + +```php +array('text', 'filter', 'filter' => function ($value) { + // here we are removing all swear words from text + return $newValue; +}), +``` + +### `in`: [[RangeValidator]] + +Validates that the attribute value is among a list of values. + +- `range` list of valid values that the attribute value should be among. +- `strict` whether the comparison is strict (both type and value must be the same). _(false)_ +- `not` whether to invert the validation logic. _(false)_ + +### `integer`: [[NumberValidator]] + +Validates that the attribute value is an integer number. + +- `max` limit of the number. _(null)_ +- `min` lower limit of the number. _(null)_ + +### `match`: [[RegularExpressionValidator]] + +Validates that the attribute value matches the specified pattern defined by regular expression. + +- `pattern` the regular expression to be matched with. +- `not` whether to invert the validation logic. _(false)_ + +### `required`: [[RequiredValidator]] + +Validates that the specified attribute does not have null or empty value. + +- `requiredValue` the desired value that the attribute must have. _(any)_ +- `strict` whether the comparison between the attribute value and [[requiredValue]] is strict. _(false)_ + +### `safe`: [[SafeValidator]] + +Serves as a dummy validator whose main purpose is to mark the attributes to be safe for massive assignment. + +### `string`: [[StringValidator]] + +Validates that the attribute value is of certain length. + +- `length` specifies the length limit of the value to be validated. Can be `exactly X`, `array(min X)`, `array(min X, max Y)`. +- `max` maximum length. If not set, it means no maximum length limit. +- `min` minimum length. If not set, it means no minimum length limit. +- `encoding` the encoding of the string value to be validated. _([[\yii\base\Application::charset]])_ + +### `unique`: [[UniqueValidator]] + +Validates that the attribute value is unique in the corresponding database table. + +- `className` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being + validated. _(ActiveRecord class of the attribute being validated)_ +- `attributeName` the ActiveRecord attribute name that should be used to look for the attribute value being validated. + _(name of the attribute being validated)_ + +### `url`: [[UrlValidator]] + +Validates that the attribute value is a valid http or https URL. + +- `validSchemes` list of URI schemes which should be considered valid. _array('http', 'https')_ +- `defaultScheme` the default URI scheme. If the input doesn't contain the scheme part, the default scheme will be + prepended to it. _(null)_ +- `enableIDN` whether validation process should take into account IDN (internationalized domain names). _(false)_ + +Validating values out of model context +-------------------------------------- + +Sometimes you need to validate a value that is not bound to any model such as email. In Yii `Validator` class has +`validateValue` method that can help you with it. Not all validator classes have it implemented but the ones that can +operate without model do. In our case to validate an email we can do the following: + +```php +$email = 'test@example.com'; +$validator = new yii\validators\EmailValidator(); +if ($validator->validateValue($email)) { + echo 'Email is valid.'; +} else { + echo 'Email is not valid.' +} +``` TBD: refer to http://www.yiiframework.com/wiki/56/ for the format \ No newline at end of file diff --git a/docs/internals/autoloader.md b/docs/internals/autoloader.md index b7696d7..76a545b 100644 --- a/docs/internals/autoloader.md +++ b/docs/internals/autoloader.md @@ -16,4 +16,4 @@ PEAR-style libraries References ---------- -- YiiBase::autoload \ No newline at end of file +- BaseYii::autoload \ No newline at end of file diff --git a/extensions/composer/README.md b/extensions/composer/README.md index 986fc6c..853d3c3 100644 --- a/extensions/composer/README.md +++ b/extensions/composer/README.md @@ -17,11 +17,11 @@ This is the yii2 composer installer. Installation ------------ -This extension offers you enhanced composer handling for your yii2-project. It will therefor require you to use composer. +This extension offers you enhanced Composer handling for your yii2-project. It will therefore require you to use Composer. -` +``` php composer.phar require yiisoft/yii2-composer "*" -` +``` *Note: You might have to run `php composer.phar selfupdate` before using this extension.* @@ -29,9 +29,9 @@ php composer.phar require yiisoft/yii2-composer "*" Usage & Documentation --------------------- -This extensions allows you to hook to certain composer events and prepare your yii2-app for usage. +This extension allows you to hook to certain composer events and automate preparing your Yii2 application for further usage. -After the package is installed, the composer.json file has to be modified to enable this extension. +After the package is installed, the `composer.json` file has to be modified to enable this extension. To see it in action take a look at the example apps in the repository: diff --git a/framework/yii/YiiBase.php b/framework/yii/BaseYii.php similarity index 98% rename from framework/yii/YiiBase.php rename to framework/yii/BaseYii.php index c96a969..b586160 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/BaseYii.php @@ -49,15 +49,15 @@ defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true); /** - * YiiBase is the core helper class for the Yii framework. + * BaseYii is the core helper class for the Yii framework. * - * Do not use YiiBase directly. Instead, use its child class [[Yii]] where - * you can customize methods of YiiBase. + * Do not use BaseYii directly. Instead, use its child class [[Yii]] where + * you can customize methods of BaseYii. * * @author Qiang Xue * @since 2.0 */ -class YiiBase +class BaseYii { /** * @var array class map used by the Yii autoloading mechanism. @@ -335,7 +335,7 @@ class YiiBase include($classFile); - if (!class_exists($className, false) && !interface_exists($className, false) && + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && (!function_exists('trait_exists') || !trait_exists($className, false))) { throw new UnknownClassException("Unable to find '$className' in file: $classFile"); } diff --git a/framework/yii/Yii.php b/framework/yii/Yii.php index bde15cc..232117f 100644 --- a/framework/yii/Yii.php +++ b/framework/yii/Yii.php @@ -7,18 +7,18 @@ * @license http://www.yiiframework.com/license/ */ -require(__DIR__ . '/YiiBase.php'); +require(__DIR__ . '/BaseYii.php'); /** * Yii is a helper class serving common framework functionalities. * - * It extends from [[YiiBase]] which provides the actual implementation. - * By writing your own Yii class, you can customize some functionalities of [[YiiBase]]. + * It extends from [[BaseYii]] which provides the actual implementation. + * By writing your own Yii class, you can customize some functionalities of [[BaseYii]]. * * @author Qiang Xue * @since 2.0 */ -class Yii extends \yii\YiiBase +class Yii extends \yii\BaseYii { } diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 31a57d5..add3a02 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -37,13 +37,116 @@ * * Using this structure, you can define public and private functions/properties for a module. * Private functions/properties are only visible within the module, while public functions/properties - * may be accessed outside of the module. For example, you can access "yii.sample.init()". + * may be accessed outside of the module. For example, you can access "yii.sample.isActive". * * You must call "yii.initModule()" once for the root module of all your modules. */ yii = (function ($) { var pub = { - version: '2.0', + /** + * The selector for clickable elements that need to support confirmation and form submission. + */ + clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]', + /** + * The selector for changeable elements that need to support confirmation and form submission. + */ + changeableSelector: 'select, input, textarea', + + /** + * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. + */ + getCsrfVar: function () { + return $('meta[name=csrf-var]').prop('content'); + }, + + /** + * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled. + */ + getCsrfToken: function () { + return $('meta[name=csrf-token]').prop('content'); + }, + + /** + * Displays a confirmation dialog. + * The default implementation simply displays a js confirmation dialog. + * You may override this by setting `yii.confirm`. + * @param message the confirmation message. + * @return boolean whether the user confirms with the message in the dialog + */ + confirm: function (message) { + return confirm(message); + }, + + /** + * Returns a value indicating whether to allow executing the action defined for the specified element. + * This method recognizes the `data-confirm` attribute of the element and uses it + * as the message in a confirmation dialog. The method will return true if this special attribute + * is not defined or if the user confirms the message. + * @param $e the jQuery representation of the element + * @return boolean whether to allow executing the action defined for the specified element. + */ + allowAction: function ($e) { + var message = $e.data('confirm'); + return message === undefined || pub.confirm(message); + }, + + /** + * Handles the action triggered by user. + * This method recognizes the `data-method` attribute of the element. If the attribute exists, + * the method will submit the form containing this element. If there is no containing form, a form + * will be created and submitted using the method given by this attribute value (e.g. "post", "put"). + * For hyperlinks, the form action will take the value of the "href" attribute of the link. + * For other elements, either the containing form action or the current page URL will be used + * as the form action URL. + * + * If the `data-method` attribute is not defined, the default element action will be performed. + * + * @param $e the jQuery representation of the element + * @return boolean whether to execute the default action for the element. + */ + handleAction: function ($e) { + var method = $e.data('method'); + if (method === undefined) { + return true; + } + + var $form = $e.closest('form'); + var newForm = !$form.length; + if (newForm) { + var action = $e.prop('href'); + if (!action || !action.match(/(^\/|:\/\/)/)) { + action = window.location.href; + } + $form = $('
'); + var target = $e.prop('target'); + if (target) { + $form.attr('target', target); + } + if (!method.match(/(get|post)/i)) { + $form.append(''); + } + var csrfVar = pub.getCsrfVar(); + if (csrfVar) { + $form.append(''); + } + $form.hide().appendTo('body'); + } + + var activeFormData = $form.data('yiiActiveForm'); + if (activeFormData) { + // remember who triggers the form submission. This is used by yii.activeForm.js + activeFormData.submitObject = $e; + } + + $form.trigger('submit'); + + if (newForm) { + $form.remove(); + } + + return false; + }, + initModule: function (module) { if (module.isActive === undefined || module.isActive) { if ($.isFunction(module.init)) { @@ -55,6 +158,47 @@ yii = (function ($) { } }); } + }, + + init: function () { + var $document = $(document); + + // automatically send CSRF token for all AJAX requests + $.ajaxPrefilter(function (options, originalOptions, xhr) { + if (!options.crossDomain && pub.getCsrfVar()) { + xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); + } + }); + + // handle AJAX redirection + $document.ajaxComplete(function (event, xhr, settings) { + var url = xhr.getResponseHeader('X-Redirect'); + if (url) { + window.location = url; + } + }); + + // handle data-confirm and data-method for clickable elements + $document.on('click.yii', pub.clickableSelector, function (event) { + var $this = $(this); + if (pub.allowAction($this)) { + return pub.handleAction($this); + } else { + event.stopImmediatePropagation(); + return false; + } + }); + + // handle data-confirm and data-method for changeable elements + $document.on('change.yii', pub.changeableSelector, function (event) { + var $this = $(this); + if (pub.allowAction($this)) { + return pub.handleAction($this); + } else { + event.stopImmediatePropagation(); + return false; + } + }); } }; return pub; diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index cc3525e..2ad2c94 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -40,6 +40,7 @@ class Component extends Object * @param string $name the property name * @return mixed the property value or the value of a behavior's property * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is write-only. * @see __set */ public function __get($name) @@ -178,9 +179,8 @@ class Component extends Object /** * Calls the named method which is not a class method. - * If the name refers to a component property whose value is - * an anonymous function, the method will execute the function. - * Otherwise, it will check if any attached behavior has + * + * This method will check if any attached behavior has * the named method and will execute it if available. * * Do not call this method directly as it is a PHP magic method that @@ -192,14 +192,6 @@ class Component extends Object */ public function __call($name, $params) { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - $func = $this->$getter(); - if ($func instanceof \Closure) { - return call_user_func_array($func, $params); - } - } - $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 20f6e2b..3eebaa0 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -210,6 +210,7 @@ class Controller extends Component /** * This method is invoked right before an action is to be executed (after all possible filters.) * You may override this method to do last-minute preparation for the action. + * If you override this method, please make sure you call the parent implementation first. * @param Action $action the action to be executed. * @return boolean whether the action should continue to be executed. */ @@ -223,6 +224,7 @@ class Controller extends Component /** * This method is invoked right after an action is executed. * You may override this method to do some postprocessing for the action. + * If you override this method, please make sure you call the parent implementation first. * @param Action $action the action just executed. * @param mixed $result the action return result. */ diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 41fa7f9..40f5c37 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -93,6 +93,8 @@ class ErrorHandler extends Component $response->getHeaders()->removeAll(); if ($useErrorView && $this->errorAction !== null) { + // disable CSRF validation so that errorAction can run in case the error is caused by CSRF validation failure + Yii::$app->getRequest()->enableCsrfValidation = false; $result = Yii::$app->runAction($this->errorAction); if ($result instanceof Response) { $response = $result; diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index 4f66e53..64f1d1b 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -39,21 +39,12 @@ class Exception extends \Exception implements Arrayable */ protected function toArrayRecursive($exception) { - if ($exception instanceof self) { - $array = array( - 'type' => get_class($this), - 'name' => $this->getName(), - 'message' => $this->getMessage(), - 'code' => $this->getCode(), - ); - } else { - $array = array( - 'type' => get_class($exception), - 'name' => 'Exception', - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } + $array = array( + 'type' => get_class($exception), + 'name' => $exception instanceof self ? $exception->getName() : 'Exception', + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + ); if (($prev = $exception->getPrevious()) !== null) { $array['previous'] = $this->toArrayRecursive($prev); } diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index a195acd..93b5a6b 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -45,7 +45,7 @@ use yii\validators\Validator; * property is read-only. * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is * read-only. - * @property string $scenario The scenario that this model is in. Defaults to 'default'. + * @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. * @property ArrayObject $validators All the validators declared in the model. This property is read-only. * * @author Qiang Xue @@ -54,6 +54,11 @@ use yii\validators\Validator; class Model extends Component implements IteratorAggregate, ArrayAccess { /** + * The name of the default scenario. + */ + const DEFAULT_SCENARIO = 'default'; + + /** * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set * [[ModelEvent::isValid]] to be false to stop the validation. */ @@ -74,7 +79,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess /** * @var string current scenario */ - private $_scenario = 'default'; + private $_scenario = self::DEFAULT_SCENARIO; /** * Returns the validation rules for attributes. @@ -111,6 +116,10 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * function validatorName($attribute, $params) * ~~~ * + * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of + * validator configuration options such as `max` in case of `length` validator. Currently validate attribute value + * can be accessed as `$this->[$attribute]`. + * * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. * They each has an alias name which can be used when specifying a validation rule. * @@ -159,23 +168,39 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * If an attribute should NOT be massively assigned (thus considered unsafe), * please prefix the attribute with an exclamation character (e.g. '!rank'). * - * The default implementation of this method will return a 'default' scenario - * which corresponds to all attributes listed in the validation rules applicable - * to the 'default' scenario. + * The default implementation of this method will return all scenarios found in the [[rules()]] + * declaration. A special scenario named [[DEFAULT_SCENARIO]] will contain all attributes + * found in the [[rules()]]. Each scenario will be associated with the attributes that + * are being validated by the validation rules that apply to the scenario. * * @return array a list of scenarios and the corresponding active attributes. */ public function scenarios() { - $attributes = array(); - foreach ($this->getActiveValidators() as $validator) { - foreach ($validator->attributes as $name) { - $attributes[$name] = true; + $scenarios = array(); + $defaults = array(); + /** @var $validator Validator */ + foreach ($this->getValidators() as $validator) { + if (empty($validator->on)) { + foreach ($validator->attributes as $attribute) { + $defaults[$attribute] = true; + } + } else { + foreach ($validator->on as $scenario) { + foreach ($validator->attributes as $attribute) { + $scenarios[$scenario][$attribute] = true; + } + } + } + } + foreach ($scenarios as $scenario => $attributes) { + foreach (array_keys($defaults) as $attribute) { + $attributes[$attribute] = true; } + $scenarios[$scenario] = array_keys($attributes); } - return array( - 'default' => array_keys($attributes), - ); + $scenarios[self::DEFAULT_SCENARIO] = array_keys($defaults); + return $scenarios; } /** @@ -593,7 +618,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Scenario affects how validation is performed and which attributes can * be massively assigned. * - * @return string the scenario that this model is in. Defaults to 'default'. + * @return string the scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. */ public function getScenario() { diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 1b7bf5b..7df87a5 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -338,10 +338,9 @@ abstract class Module extends Component /** * Retrieves the named module. - * @param string $id module ID (case-sensitive) + * @param string $id module ID (case-sensitive). * @param boolean $load whether to load the module if it is not yet loaded. - * @return Module|null the module instance, null if the module - * does not exist. + * @return Module|null the module instance, null if the module does not exist. * @see hasModule() */ public function getModule($id, $load = true) @@ -618,7 +617,7 @@ abstract class Module extends Component if (isset($this->controllerMap[$id])) { $controller = Yii::createObject($this->controllerMap[$id], $id, $this); } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { - $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller'; + $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $id))) . 'Controller'; $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; if (!is_file($classFile)) { return false; diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php index adbab9c..55754de 100644 --- a/framework/yii/base/Object.php +++ b/framework/yii/base/Object.php @@ -143,8 +143,6 @@ class Object implements Arrayable /** * Calls the named method which is not a class method. - * If the name refers to a component property whose value is - * an anonymous function, the method will execute the function. * * Do not call this method directly as it is a PHP magic method that * will be implicitly called when an unknown method is being invoked. @@ -155,13 +153,6 @@ class Object implements Arrayable */ public function __call($name, $params) { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - $func = $this->$getter(); - if ($func instanceof \Closure) { - return call_user_func_array($func, $params); - } - } throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()"); } diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 4d3d996..df0b2b2 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -708,6 +708,13 @@ class View extends Component if (!empty($this->metaTags)) { $lines[] = implode("\n", $this->metaTags); } + + $request = Yii::$app->getRequest(); + if ($request instanceof \yii\web\Request && $request->enableCsrfValidation) { + $lines[] = Html::tag('meta', '', array('name' => 'csrf-var', 'content' => $request->csrfVar)); + $lines[] = Html::tag('meta', '', array('name' => 'csrf-token', 'content' => $request->getCsrfToken())); + } + if (!empty($this->linkTags)) { $lines[] = implode("\n", $this->linkTags); } diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index e19eae1..f24f729 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -56,7 +56,7 @@ class Nav extends Widget { /** * @var array list of items in the nav widget. Each array element represents a single - * menu item with the following structure: + * menu item which can be either a string or an array with the following structure: * * - label: string, required, the nav item label. * - url: optional, the item's URL. Defaults to "#". @@ -66,6 +66,8 @@ class Nav extends Widget * - active: boolean, optional, whether the item should be on active state or not. * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget, * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. + * + * If a menu item is a string, it will be rendered directly without HTML encoding. */ public $items = array(); /** diff --git a/framework/yii/caching/MemCache.php b/framework/yii/caching/MemCache.php index 53202f0..69a90b4 100644 --- a/framework/yii/caching/MemCache.php +++ b/framework/yii/caching/MemCache.php @@ -87,7 +87,14 @@ class MemCache extends Cache parent::init(); $servers = $this->getServers(); $cache = $this->getMemCache(); - if (count($servers)) { + if (empty($servers)) { + $cache->addServer('127.0.0.1', 11211); + } else { + if (!$this->useMemcached) { + // different version of memcache may have different number of parameters for the addServer method. + $class = new \ReflectionClass($cache); + $paramCount = $class->getMethod('addServer')->getNumberOfParameters(); + } foreach ($servers as $server) { if ($server->host === null) { throw new InvalidConfigException("The 'host' property must be specified for every memcache server."); @@ -97,15 +104,21 @@ class MemCache extends Cache } else { // $timeout is used for memcache versions that do not have timeoutms parameter $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0); - $cache->addServer( - $server->host, $server->port, $server->persistent, - $server->weight, $timeout, $server->retryInterval, - $server->status, $server->failureCallback, $server->timeout - ); + if ($paramCount === 9) { + $cache->addServer( + $server->host, $server->port, $server->persistent, + $server->weight, $timeout, $server->retryInterval, + $server->status, $server->failureCallback, $server->timeout + ); + } else { + $cache->addServer( + $server->host, $server->port, $server->persistent, + $server->weight, $timeout, $server->retryInterval, + $server->status, $server->failureCallback + ); + } } } - } else { - $cache->addServer('127.0.0.1', 11211); } } diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index 09ce599..5c778fc 100644 --- a/framework/yii/caching/RedisCache.php +++ b/framework/yii/caching/RedisCache.php @@ -7,7 +7,7 @@ namespace yii\caching; -use yii\db\redis\Connection; +use yii\redis\Connection; /** * RedisCache implements a cache application component based on [redis](http://redis.io/). @@ -39,7 +39,7 @@ use yii\db\redis\Connection; * ) * ~~~ * - * @property \yii\db\redis\Connection $connection This property is read-only. + * @property Connection $connection The redis connection object. This property is read-only. * * @author Carsten Brandt * @since 2.0 @@ -71,7 +71,7 @@ class RedisCache extends Cache */ public $dataTimeout = null; /** - * @var \yii\db\redis\Connection the redis connection + * @var Connection the redis connection */ private $_connection; @@ -88,9 +88,7 @@ class RedisCache extends Cache /** * Returns the redis connection object. * Establishes a connection to the redis server if it does not already exists. - * - * TODO throw exception on error - * @return \yii\db\redis\Connection + * @return Connection the redis connection object. */ public function getConnection() { diff --git a/framework/yii/classes.php b/framework/yii/classes.php index aee93c0..1469910 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -49,6 +49,7 @@ return array( 'yii\bootstrap\Alert' => YII_PATH . '/bootstrap/Alert.php', 'yii\bootstrap\BootstrapAsset' => YII_PATH . '/bootstrap/BootstrapAsset.php', 'yii\bootstrap\BootstrapPluginAsset' => YII_PATH . '/bootstrap/BootstrapPluginAsset.php', + 'yii\bootstrap\BootstrapThemeAsset' => YII_PATH . '/bootstrap/BootstrapThemeAsset.php', 'yii\bootstrap\Button' => YII_PATH . '/bootstrap/Button.php', 'yii\bootstrap\ButtonDropdown' => YII_PATH . '/bootstrap/ButtonDropdown.php', 'yii\bootstrap\ButtonGroup' => YII_PATH . '/bootstrap/ButtonGroup.php', @@ -60,7 +61,6 @@ return array( 'yii\bootstrap\NavBar' => YII_PATH . '/bootstrap/NavBar.php', 'yii\bootstrap\Progress' => YII_PATH . '/bootstrap/Progress.php', 'yii\bootstrap\Tabs' => YII_PATH . '/bootstrap/Tabs.php', - 'yii\bootstrap\Typeahead' => YII_PATH . '/bootstrap/Typeahead.php', 'yii\bootstrap\Widget' => YII_PATH . '/bootstrap/Widget.php', 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php', 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php', @@ -75,6 +75,7 @@ return array( 'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php', 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php', 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php', + 'yii\caching\RedisCache' => YII_PATH . '/caching/RedisCache.php', 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php', 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php', 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php', @@ -85,7 +86,7 @@ return array( 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', - 'yii\data\IDataProvider' => YII_PATH . '/data/IDataProvider.php', + 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php', 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php', @@ -104,10 +105,13 @@ return array( 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php', 'yii\db\Transaction' => YII_PATH . '/db/Transaction.php', + 'yii\db\cubrid\QueryBuilder' => YII_PATH . '/db/cubrid/QueryBuilder.php', + 'yii\db\cubrid\Schema' => YII_PATH . '/db/cubrid/Schema.php', 'yii\db\mssql\PDO' => YII_PATH . '/db/mssql/PDO.php', 'yii\db\mssql\QueryBuilder' => YII_PATH . '/db/mssql/QueryBuilder.php', 'yii\db\mssql\Schema' => YII_PATH . '/db/mssql/Schema.php', 'yii\db\mssql\SqlsrvPDO' => YII_PATH . '/db/mssql/SqlsrvPDO.php', + 'yii\db\mssql\TableSchema' => YII_PATH . '/db/mssql/TableSchema.php', 'yii\db\mysql\QueryBuilder' => YII_PATH . '/db/mysql/QueryBuilder.php', 'yii\db\mysql\Schema' => YII_PATH . '/db/mysql/Schema.php', 'yii\db\pgsql\QueryBuilder' => YII_PATH . '/db/pgsql/QueryBuilder.php', @@ -121,27 +125,27 @@ return array( 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php', 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', - 'yii\helpers\ArrayHelperBase' => YII_PATH . '/helpers/ArrayHelperBase.php', + 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php', 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', - 'yii\helpers\ConsoleBase' => YII_PATH . '/helpers/ConsoleBase.php', + 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php', 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', - 'yii\helpers\FileHelperBase' => YII_PATH . '/helpers/FileHelperBase.php', + 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php', 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', - 'yii\helpers\HtmlBase' => YII_PATH . '/helpers/HtmlBase.php', + 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php', 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', - 'yii\helpers\HtmlPurifierBase' => YII_PATH . '/helpers/HtmlPurifierBase.php', + 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php', 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', - 'yii\helpers\InflectorBase' => YII_PATH . '/helpers/InflectorBase.php', + 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', - 'yii\helpers\JsonBase' => YII_PATH . '/helpers/JsonBase.php', + 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', - 'yii\helpers\MarkdownBase' => YII_PATH . '/helpers/MarkdownBase.php', + 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', - 'yii\helpers\SecurityBase' => YII_PATH . '/helpers/SecurityBase.php', + 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', - 'yii\helpers\StringHelperBase' => YII_PATH . '/helpers/StringHelperBase.php', + 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', - 'yii\helpers\VarDumperBase' => YII_PATH . '/helpers/VarDumperBase.php', + 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', @@ -162,6 +166,8 @@ return array( 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php', 'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php', 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php', + '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\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php', 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php', @@ -177,6 +183,7 @@ return array( 'yii\validators\RangeValidator' => YII_PATH . '/validators/RangeValidator.php', 'yii\validators\RegularExpressionValidator' => YII_PATH . '/validators/RegularExpressionValidator.php', 'yii\validators\RequiredValidator' => YII_PATH . '/validators/RequiredValidator.php', + 'yii\validators\SafeValidator' => YII_PATH . '/validators/SafeValidator.php', 'yii\validators\StringValidator' => YII_PATH . '/validators/StringValidator.php', 'yii\validators\UniqueValidator' => YII_PATH . '/validators/UniqueValidator.php', 'yii\validators\UrlValidator' => YII_PATH . '/validators/UrlValidator.php', @@ -197,15 +204,15 @@ return array( 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php', 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php', - 'yii\web\IAssetConverter' => YII_PATH . '/web/IAssetConverter.php', - 'yii\web\Identity' => YII_PATH . '/web/Identity.php', + 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', + 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php', 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php', 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php', 'yii\web\PageCache' => YII_PATH . '/web/PageCache.php', 'yii\web\Request' => YII_PATH . '/web/Request.php', 'yii\web\Response' => YII_PATH . '/web/Response.php', 'yii\web\ResponseEvent' => YII_PATH . '/web/ResponseEvent.php', - 'yii\web\ResponseFormatter' => YII_PATH . '/web/ResponseFormatter.php', + 'yii\web\ResponseFormatterInterface' => YII_PATH . '/web/ResponseFormatterInterface.php', 'yii\web\Session' => YII_PATH . '/web/Session.php', 'yii\web\SessionIterator' => YII_PATH . '/web/SessionIterator.php', 'yii\web\UploadedFile' => YII_PATH . '/web/UploadedFile.php', @@ -228,7 +235,7 @@ return array( 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', - 'yii\widgets\ListViewBase' => YII_PATH . '/widgets/ListViewBase.php', + 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index aaf71b2..2fe0efb 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -156,7 +156,6 @@ class ActiveDataProvider extends DataProvider throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); } if (($sort = $this->getSort()) !== false) { diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index d6eaca7..9534803 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -114,7 +114,6 @@ class ArrayDataProvider extends DataProvider } if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php index 84491d6..d75be6f 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/DataProvider.php @@ -14,7 +14,7 @@ use yii\base\InvalidParamException; /** * DataProvider is the base class of data provider classes. * - * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[IDataProvider]] interface. + * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[DataProviderInterface]]. * * @property integer $count The number of data models in the current page. This property is read-only. * @property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination @@ -26,7 +26,7 @@ use yii\base\InvalidParamException; * @author Qiang Xue * @since 2.0 */ -abstract class DataProvider extends Component implements IDataProvider +abstract class DataProvider extends Component implements DataProviderInterface { /** * @var string an ID that uniquely identifies the data provider among all data providers. @@ -48,6 +48,7 @@ abstract class DataProvider extends Component implements IDataProvider if ($this->id !== null) { $this->_pagination->pageVar = $this->id . '-page'; } + $this->_pagination->totalCount = $this->getTotalCount(); } return $this->_pagination; } diff --git a/framework/yii/data/IDataProvider.php b/framework/yii/data/DataProviderInterface.php similarity index 92% rename from framework/yii/data/IDataProvider.php rename to framework/yii/data/DataProviderInterface.php index 9ae5546..f0bc39d 100644 --- a/framework/yii/data/IDataProvider.php +++ b/framework/yii/data/DataProviderInterface.php @@ -8,7 +8,7 @@ namespace yii\data; /** - * IDataProvider is the interface that must be implemented by data provider classes. + * DataProviderInterface is the interface that must be implemented by data provider classes. * * Data providers are components that sort and paginate data, and provide them to widgets * such as [[GridView]], [[ListView]]. @@ -16,7 +16,7 @@ namespace yii\data; * @author Qiang Xue * @since 2.0 */ -interface IDataProvider +interface DataProviderInterface { /** * Returns the number of data models in the current page. diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php index 1625fde..04af828 100644 --- a/framework/yii/data/Pagination.php +++ b/framework/yii/data/Pagination.php @@ -98,10 +98,10 @@ class Pagination extends Object */ public $validatePage = true; /** - * @var integer number of items on each page. Defaults to 10. + * @var integer number of items on each page. Defaults to 20. * If it is less than 1, it means the page size is infinite, and thus a single page contains all items. */ - public $pageSize = 10; + public $pageSize = 20; /** * @var integer total number of items. */ diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 35d7305..e1c4b4f 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -749,21 +749,21 @@ class ActiveRecord extends Model return false; } $db = static::getDb(); - $transaction = $this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null ? $db->beginTransaction() : null; - try { - $result = $this->insertInternal($attributes); - if ($transaction !== null) { + if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) { + $transaction = $db->beginTransaction(); + try { + $result = $this->insertInternal($attributes); if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } - } - } catch (\Exception $e) { - if ($transaction !== null) { + } catch (\Exception $e) { $transaction->rollback(); + throw $e; } - throw $e; + } else { + $result = $this->insertInternal($attributes); } return $result; } @@ -859,21 +859,21 @@ class ActiveRecord extends Model return false; } $db = static::getDb(); - $transaction = $this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null ? $db->beginTransaction() : null; - try { - $result = $this->updateInternal($attributes); - if ($transaction !== null) { + if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) { + $transaction = $db->beginTransaction(); + try { + $result = $this->updateInternal($attributes); if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } - } - } catch (\Exception $e) { - if ($transaction !== null) { + } catch (\Exception $e) { $transaction->rollback(); + throw $e; } - throw $e; + } else { + $result = $this->updateInternal($attributes); } return $result; } @@ -889,6 +889,7 @@ class ActiveRecord extends Model } $values = $this->getDirtyAttributes($attributes); if (empty($values)) { + $this->afterSave(false); return 0; } $condition = $this->getOldPrimaryKey(true); @@ -1010,6 +1011,16 @@ class ActiveRecord extends Model } /** + * Sets the value indicating whether the record is new. + * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. + * @see getIsNewRecord + */ + public function setIsNewRecord($value) + { + $this->_oldAttributes = $value ? null : $this->_attributes; + } + + /** * Initializes the object. * This method is called at the end of the constructor. * The default implementation will trigger an [[EVENT_INIT]] event. @@ -1034,16 +1045,6 @@ class ActiveRecord extends Model } /** - * Sets the value indicating whether the record is new. - * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. - * @see getIsNewRecord - */ - public function setIsNewRecord($value) - { - $this->_oldAttributes = $value ? null : $this->_attributes; - } - - /** * This method is called at the beginning of inserting or updating a record. * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true, * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false. diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php index 0be4feb..f05c56a 100644 --- a/framework/yii/db/ActiveRelation.php +++ b/framework/yii/db/ActiveRelation.php @@ -279,7 +279,9 @@ class ActiveRelation extends ActiveQuery // single key $attribute = reset($this->link); foreach ($models as $model) { - $values[] = $model[$attribute]; + if (($value = $model[$attribute]) !== null) { + $values[] = $value; + } } } else { // composite keys diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index 460cd46..bfb8a26 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -246,14 +246,14 @@ class Command extends \yii\base\Component } /** - * Determines the PDO type for the give PHP data value. + * Determines the PDO type for the given PHP data value. * @param mixed $data the data whose PDO type is to be determined * @return integer the PDO type * @see http://www.php.net/manual/en/pdo.constants.php */ private function getPdoType($data) { - static $typeMap = array( + static $typeMap = array( // php type => PDO type 'boolean' => \PDO::PARAM_BOOL, 'integer' => \PDO::PARAM_INT, 'string' => \PDO::PARAM_STR, @@ -472,7 +472,7 @@ class Command extends \yii\base\Component * ))->execute(); * ~~~ * - * Not that the values in each row must match the corresponding column names. + * Note that the values in each row must match the corresponding column names. * * @param string $table the table that new rows will be inserted into. * @param array $columns the column names diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 342fa15..69bf6a5 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -201,7 +201,7 @@ class Connection extends Component public $queryCache = 'cache'; /** * @var string the charset used for database connection. The property is only used - * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset + * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset * as specified by the database. * * Note that if you're using GBK or BIG5 then it's highly recommended to @@ -244,6 +244,7 @@ class Connection extends Component 'oci' => 'yii\db\oci\Schema', // Oracle driver 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts + 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID ); /** * @var Transaction the currently active transaction @@ -361,7 +362,7 @@ class Connection extends Component if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) { $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); } - if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli'))) { + if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli', 'cubrid'))) { $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); } $this->trigger(self::EVENT_AFTER_OPEN); diff --git a/framework/yii/db/DataReader.php b/framework/yii/db/DataReader.php index d18de4c..f2990c1 100644 --- a/framework/yii/db/DataReader.php +++ b/framework/yii/db/DataReader.php @@ -40,7 +40,7 @@ use yii\base\InvalidCallException; * for more details about possible fetch mode. * * @property integer $columnCount The number of columns in the result set. This property is read-only. - * @property mixed $fetchMode Fetch mode. This property is write-only. + * @property integer $fetchMode Fetch mode. This property is write-only. * @property boolean $isClosed Whether the reader is closed or not. This property is read-only. * @property integer $rowCount Number of rows contained in the result. This property is read-only. * diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index f67038c..f210f65 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -134,7 +134,7 @@ class QueryBuilder extends \yii\base\Object * ))->execute(); * ~~~ * - * Not that the values in each row must match the corresponding column names. + * Note that the values in each row must match the corresponding column names. * * @param string $table the table that new rows will be inserted into. * @param array $columns the column names @@ -491,6 +491,7 @@ class QueryBuilder extends \yii\base\Object * physical types): * * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY" * - `string`: string type, will be converted into "varchar(255)" * - `text`: a long string type, will be converted into "text" * - `smallint`: a small integer type, will be converted into "smallint(6)" @@ -584,7 +585,7 @@ class QueryBuilder extends \yii\base\Object foreach ($tables as $i => $table) { if (strpos($table, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/i', $table, $matches)) { // with alias + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); } else { $tables[$i] = $this->db->quoteTableName($table); @@ -618,7 +619,7 @@ class QueryBuilder extends \yii\base\Object // 0:join type, 1:table name, 2:on-condition $table = $join[1]; if (strpos($table, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); } else { $table = $this->db->quoteTableName($table); @@ -912,11 +913,6 @@ class QueryBuilder extends \yii\base\Object protected function buildCompositeInCondition($operator, $columns, $values, &$params) { - foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } - } $vss = array(); foreach ($values as $value) { $vs = array(); @@ -931,6 +927,11 @@ class QueryBuilder extends \yii\base\Object } $vss[] = '(' . implode(', ', $vs) . ')'; } + foreach ($columns as $i => $column) { + if (strpos($column, '(') === false) { + $columns[$i] = $this->db->quoteColumnName($column); + } + } return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; } diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index 4fd1cd1..1d86616 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -35,6 +35,7 @@ abstract class Schema extends Object * The followings are the supported abstract column data types. */ const TYPE_PK = 'pk'; + const TYPE_BIGPK = 'bigpk'; const TYPE_STRING = 'string'; const TYPE_TEXT = 'text'; const TYPE_SMALLINT = 'smallint'; @@ -216,7 +217,7 @@ abstract class Schema extends Object * This method should be overridden by child classes in order to support this feature * because the default implementation simply throws an exception. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array all table names in the database. The names have NO the schema name prefix. + * @return array all table names in the database. The names have NO schema name prefix. * @throws NotSupportedException if this method is called */ protected function findTableNames($schema = '') diff --git a/framework/yii/db/TableSchema.php b/framework/yii/db/TableSchema.php index d599388..910061d 100644 --- a/framework/yii/db/TableSchema.php +++ b/framework/yii/db/TableSchema.php @@ -21,12 +21,6 @@ use yii\base\InvalidParamException; class TableSchema extends Object { /** - * @var string name of the catalog (database) that this table belongs to. - * Defaults to null, meaning no catalog (or the current database). - * This property is only meaningful for MSSQL. - */ - public $catalogName; - /** * @var string name of the schema that this table belongs to. */ public $schemaName; diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php new file mode 100644 index 0000000..4b7ef43 --- /dev/null +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -0,0 +1,117 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = array( + Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'varchar', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'int', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'float(7)', + Schema::TYPE_DECIMAL => 'decimal(10,0)', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'blob', + Schema::TYPE_BOOLEAN => 'smallint', + Schema::TYPE_MONEY => 'decimal(19,4)', + ); + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value = (int)$this->db->createCommand("SELECT MAX(`$key`) FROM " . $this->db->schema->quoteTableName($tableName))->queryScalar() + 1; + } else { + $value = (int)$value; + } + return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'."); + } + } + + /** + * Generates a batch INSERT SQL statement. + * For example, + * + * ~~~ + * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( + * array('Tom', 30), + * array('Jane', 20), + * array('Linda', 25), + * ))->execute(); + * ~~~ + * + * Note that the values in each row must match the corresponding column names. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names + * @param array $rows the rows to be batch inserted into the table + * @return string the batch INSERT SQL statement + */ + public function batchInsert($table, $columns, $rows) + { + if (($tableSchema = $this->db->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = array(); + } + + foreach ($columns as $i => $name) { + $columns[$i] = $this->db->quoteColumnName($name); + } + + $values = array(); + foreach ($rows as $row) { + $vs = array(); + foreach ($row as $i => $value) { + if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->typecast($value); + } + $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value; + } + $values[] = '(' . implode(', ', $vs) . ')'; + } + + return 'INSERT INTO ' . $this->db->quoteTableName($table) + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); + } +} diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php new file mode 100644 index 0000000..ba7fcae --- /dev/null +++ b/framework/yii/db/cubrid/Schema.php @@ -0,0 +1,240 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for + * details on data types. + */ + public $typeMap = array( + // Numeric data types + 'short' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'numeric' => self::TYPE_DECIMAL, + 'decimal' => self::TYPE_DECIMAL, + 'float' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_FLOAT, + 'monetary' => self::TYPE_MONEY, + // Date/Time data types + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'datetime' => self::TYPE_DATETIME, + // String data types + 'char' => self::TYPE_STRING, + 'varchar' => self::TYPE_STRING, + 'char varying' => self::TYPE_STRING, + 'nchar' => self::TYPE_STRING, + 'nchar varying' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + // BLOB/CLOB data types + 'blob' => self::TYPE_BINARY, + 'clob' => self::TYPE_BINARY, + // Bit string data types + 'bit' => self::TYPE_STRING, + 'bit varying' => self::TYPE_STRING, + // Collection data types (considered strings for now) + 'set' => self::TYPE_STRING, + 'multiset' => self::TYPE_STRING, + 'list' => self::TYPE_STRING, + 'sequence' => self::TYPE_STRING, + 'enum' => self::TYPE_STRING, + ); + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; + } + + /** + * Quotes a string value for use in a query. + * Note that if the parameter is not a string, it will be returned without change. + * @param string $str string to be quoted + * @return string the properly quoted string + * @see http://www.php.net/manual/en/function.PDO-quote.php + */ + public function quoteValue($str) + { + if (!is_string($str)) { + return $str; + } + + $this->db->open(); + // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658 + if (version_compare($this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION), '9.1.0', '<=')) { + return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; + } else { + return $this->db->pdo->quote($str); + } + } + + /** + * Creates a query builder for the CUBRID database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $this->db->open(); + $tableInfo = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name); + + if (isset($tableInfo[0]['NAME'])) { + $table = new TableSchema(); + $table->name = $tableInfo[0]['NAME']; + + $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); + $columns = $this->db->createCommand($sql)->queryAll(); + + 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 = ''; + } + } + } + + $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name); + foreach($foreignKeys as $key) { + if (isset($table->foreignKeys[$key['FK_NAME']])) { + $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME']; + } else { + $table->foreignKeys[$key['FK_NAME']] = array( + $key['PKTABLE_NAME'], + $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] + ); + } + } + $table->foreignKeys = array_values($table->foreignKeys); + + return $table; + } else { + return null; + } + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = new ColumnSchema(); + + $column->name = $info['Field']; + $column->allowNull = $info['Null'] === 'YES'; + $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false; + $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false; + + $column->dbType = strtolower($info['Type']); + $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + + $column->type = self::TYPE_STRING; + if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = $matches[1]; + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + if (!empty($matches[2])) { + if ($type === 'enum') { + $values = explode(',', $matches[2]); + foreach ($values as $i => $value) { + $values[$i] = trim($value, "'"); + } + $column->enumValues = $values; + } else { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int)$values[0]; + if (isset($values[1])) { + $column->scale = (int)$values[1]; + } + } + } + } + + $column->phpType = $this->getColumnPhpType($column); + + if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' || + $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' || + $column->type === 'date' && $info['Default'] === 'SYS_DATE' || + $column->type === 'time' && $info['Default'] === 'SYS_TIME' + ) { + $column->defaultValue = new Expression($info['Default']); + } else { + $column->defaultValue = $column->typecast($info['Default']); + } + + return $column; + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + $this->db->open(); + $tables = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE); + $tableNames = array(); + foreach($tables as $table) { + // do not list system tables + if ($table['TYPE'] != 0) { + $tableNames[] = $table['NAME']; + } + } + return $tableNames; + } +} diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index e7f8f80..aeb5be8 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -22,6 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php index ad0f7d4..9def3b4 100644 --- a/framework/yii/db/mssql/Schema.php +++ b/framework/yii/db/mssql/Schema.php @@ -7,7 +7,6 @@ namespace yii\db\mssql; -use yii\db\TableSchema; use yii\db\ColumnSchema; /** @@ -332,10 +331,8 @@ SQL; /** * Returns all table names in the database. - * This method should be overridden by child classes in order to support this feature - * because the default implementation simply throws an exception. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array all table names in the database. The names have NO the schema name prefix. + * @return array all table names in the database. The names have NO schema name prefix. */ protected function findTableNames($schema = '') { diff --git a/framework/yii/db/mssql/TableSchema.php b/framework/yii/db/mssql/TableSchema.php new file mode 100644 index 0000000..67ad85c --- /dev/null +++ b/framework/yii/db/mssql/TableSchema.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +class TableSchema extends \yii\db\TableSchema +{ + /** + * @var string name of the catalog (database) that this table belongs to. + * Defaults to null, meaning no catalog (or the current database). + */ + public $catalogName; +} diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 0307abd..386de2f 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -23,6 +23,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', @@ -152,7 +153,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * ))->execute(); * ~~~ * - * Not that the values in each row must match the corresponding column names. + * Note that the values in each row must match the corresponding column names. * * @param string $table the table that new rows will be inserted into. * @param array $columns the column names diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index 225ef38..998f49a 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -236,10 +236,8 @@ class Schema extends \yii\db\Schema /** * Returns all table names in the database. - * This method should be overridden by child classes in order to support this feature - * because the default implementation simply throws an exception. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array all table names in the database. The names have NO the schema name prefix. + * @return array all table names in the database. The names have NO schema name prefix. */ protected function findTableNames($schema = '') { diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 9701fd6..33c7bf6 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -21,7 +21,8 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'serial not null primary key', + Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 8acb7bd..d131342 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -129,6 +129,35 @@ class Schema extends \yii\db\Schema } /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * If not empty, the returned table names will be prefixed with the schema name. + * @return array all table names in the database. + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + $schema = $this->defaultSchema; + } + $sql = <<db->createCommand($sql); + $command->bindParam(':schema', $schema); + $rows = $command->queryAll(); + $names = array(); + foreach ($rows as $row) { + if ($schema === $this->defaultSchema) { + $names[] = $row['table_name']; + } else { + $names[] = $row['table_schema'] . '.' . $row['table_name']; + } + } + return $names; + } + + /** * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata */ @@ -171,7 +200,7 @@ SQL; } $citem = array($foreignTable); foreach ($columns as $idx => $column) { - $citem[] = array($fcolumns[$idx] => $column); + $citem[$fcolumns[$idx]] = $column; } $table->foreignKeys[] = $citem; } @@ -226,7 +255,7 @@ SELECT information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t)) AS numeric ) AS size, - a.attnum = any (ct.conkey) as is_pkey + a.attnum = any (ct.conkey) as is_pkey FROM pg_class c LEFT JOIN pg_attribute a ON a.attrelid = c.oid diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index be0275a..4e210f8 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -24,6 +24,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php index d4fb245..bca26c1 100644 --- a/framework/yii/db/sqlite/Schema.php +++ b/framework/yii/db/sqlite/Schema.php @@ -126,7 +126,13 @@ class Schema extends \yii\db\Schema $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')'; $keys = $this->db->createCommand($sql)->queryAll(); foreach ($keys as $key) { - $table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']); + $id = (int)$key['id']; + if (!isset($table->foreignKeys[$id])) { + $table->foreignKeys[$id] = array($key['table'], $key['from'] => $key['to']); + } else { + // composite FK + $table->foreignKeys[$id][$key['from']] = $key['to']; + } } } diff --git a/framework/yii/debug/assets/main.css b/framework/yii/debug/assets/main.css index 6cb65dd..7953873 100644 --- a/framework/yii/debug/assets/main.css +++ b/framework/yii/debug/assets/main.css @@ -131,6 +131,7 @@ ul.trace { margin: 2px 0 0 0; padding: 0; list-style: none; + white-space: normal; } .callout-danger { diff --git a/framework/yii/gii/Generator.php b/framework/yii/gii/Generator.php index f79b428..eb5b8b4 100644 --- a/framework/yii/gii/Generator.php +++ b/framework/yii/gii/Generator.php @@ -325,6 +325,28 @@ abstract class Generator extends Model } /** + * An inline validator that checks if the attribute value refers to a valid namespaced class name. + * The validator will check if the directory containing the new class file exist or not. + * @param string $attribute the attribute being validated + * @param array $params the validation options + */ + public function validateNewClass($attribute, $params) + { + $class = ltrim($this->$attribute, '\\'); + if (($pos = strrpos($class, '\\')) === false) { + $this->addError($attribute, "The class name must contain fully qualified namespace name."); + } else { + $ns = substr($class, 0, $pos); + $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); + if ($path === false) { + $this->addError($attribute, "The class namespace is invalid: $ns"); + } elseif (!is_dir($path)) { + $this->addError($attribute, "Please make sure the directory containing this class exists: $path"); + } + } + } + + /** * @param string $value the attribute to be validated * @return boolean whether the value is a reserved PHP keyword. */ diff --git a/framework/yii/gii/controllers/DefaultController.php b/framework/yii/gii/controllers/DefaultController.php index 969096b..305ef35 100644 --- a/framework/yii/gii/controllers/DefaultController.php +++ b/framework/yii/gii/controllers/DefaultController.php @@ -7,6 +7,7 @@ namespace yii\gii\controllers; +use Yii; use yii\web\Controller; use yii\web\HttpException; @@ -86,6 +87,26 @@ class DefaultController extends Controller throw new HttpException(404, "Code file not found: $file"); } + /** + * Runs an action defined in the generator. + * Given an action named "xyz", the method "actionXyz()" in the generator will be called. + * If the method does not exist, a 400 HTTP exception will be thrown. + * @param string $id the ID of the generator + * @param string $name the action name + * @return mixed the result of the action. + * @throws HttpException if the action method does not exist. + */ + public function actionAction($id, $name) + { + $generator = $this->loadGenerator($id); + $method = 'action' . $name; + if (method_exists($generator, $method)) { + return $generator->$method(); + } else { + throw new HttpException(400, "Unknown generator action: $name"); + } + } + public function createUrl($route, $params = array()) { if (!isset($params['id']) && $this->generator !== null) { @@ -99,6 +120,18 @@ class DefaultController extends Controller return parent::createUrl($route, $params); } + public function createActionUrl($name, $params = array()) + { + foreach ($this->module->generators as $id => $generator) { + if ($generator === $this->generator) { + $params['id'] = $id; + break; + } + } + $params['name'] = $name; + return parent::createUrl('action', $params); + } + /** * Loads the generator with the specified ID. * @param string $id the ID of the generator to be loaded. diff --git a/framework/yii/gii/generators/controller/Generator.php b/framework/yii/gii/generators/controller/Generator.php index c57b2b2..9de9c17 100644 --- a/framework/yii/gii/generators/controller/Generator.php +++ b/framework/yii/gii/generators/controller/Generator.php @@ -78,7 +78,7 @@ class Generator extends \yii\gii\Generator 'baseClass' => 'Base Class', 'controller' => 'Controller ID', 'actions' => 'Action IDs', - 'ns' => 'Namespace', + 'ns' => 'Controller Namespace', ); } diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 2b6697d..9bce7ff 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -7,8 +7,12 @@ namespace yii\gii\generators\crud; +use Yii; +use yii\base\Model; use yii\db\ActiveRecord; +use yii\db\Schema; use yii\gii\CodeFile; +use yii\helpers\Inflector; use yii\web\Controller; /** @@ -19,8 +23,11 @@ use yii\web\Controller; class Generator extends \yii\gii\Generator { public $modelClass; - public $controllerID; + public $moduleID; + public $controllerClass; public $baseControllerClass = 'yii\web\Controller'; + public $indexWidgetType = 'grid'; + public $searchModelClass; public function getName() { @@ -36,13 +43,16 @@ class Generator extends \yii\gii\Generator public function rules() { return array_merge(parent::rules(), array( - array('modelClass, controllerID, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, controllerID, baseControllerClass', 'required'), - array('modelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), + array('moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'), + array('modelClass, searchModelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'), + array('modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), - array('controllerID', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'), - array('baseControllerClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('baseControllerClass', 'validateClass', 'params' => array('extends' => Controller::className())), + array('controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'), + array('controllerClass, searchModelClass', 'validateNewClass'), + array('indexWidgetType', 'in', 'range' => array('grid', 'list')), + array('modelClass', 'validateModelClass'), + array('moduleID', 'validateModuleID'), )); } @@ -50,8 +60,11 @@ class Generator extends \yii\gii\Generator { return array_merge(parent::attributeLabels(), array( 'modelClass' => 'Model Class', - 'controllerID' => 'Controller ID', + 'moduleID' => 'Module ID', + 'controllerClass' => 'Controller Class', 'baseControllerClass' => 'Base Controller Class', + 'indexWidgetType' => 'Widget Used in Index Page', + 'searchModelClass' => 'Search Model Class', )); } @@ -63,15 +76,16 @@ class Generator extends \yii\gii\Generator return array( 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. You should provide a fully qualified class name, e.g., app\models\Post.', - 'controllerID' => 'CRUD controllers are often named after the model class name that they are dealing with. - Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example: -
    -
  • order generates OrderController.php
  • -
  • order-item generates OrderItemController.php
  • -
  • admin/user generates UserController.php within the admin module.
  • -
', + 'controllerClass' => 'This is the name of the controller class to be generated. You should + provide a fully qualified namespaced class, .e.g, app\controllers\PostController.', 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. You should provide a fully qualified class name, e.g., yii\web\Controller.', + 'moduleID' => 'This is the ID of the module that the generated controller will belong to. + If not set, it means the controller will belong to the application.', + 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. + You may choose either GridView or ListView', + 'searchModelClass' => 'This is the class representing the data being collecting in the search form. + A fully qualified namespaced class name is required, e.g., app\models\search\PostSearch.', ); } @@ -87,7 +101,27 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('baseControllerClass'); + return array('baseControllerClass', 'moduleID', 'indexWidgetType'); + } + + public function validateModelClass() + { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + if (empty($pk)) { + $this->addError('modelClass', "The table associated with $class must have primary key(s)."); + } + } + + public function validateModuleID() + { + if (!empty($this->moduleID)) { + $module = Yii::$app->getModule($this->moduleID); + if ($module === null) { + $this->addError('moduleID', "Module '{$this->moduleID}' does not exist."); + } + } } /** @@ -95,22 +129,261 @@ class Generator extends \yii\gii\Generator */ public function generate() { - $files = array(); - $files[] = new CodeFile( - $this->controllerFile, - $this->render('controller.php') + $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); + $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); + $files = array( + new CodeFile($controllerFile, $this->render('controller.php')), + new CodeFile($searchModel, $this->render('search.php')), ); - $files = scandir($this->getTemplatePath()); - foreach ($files as $file) { - if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php' && $file !== 'controller.php') { - $files[] = new CodeFile( - $this->viewPath . DIRECTORY_SEPARATOR . $file, - $this->render($templatePath . '/' . $file) - ); + $viewPath = $this->getViewPath(); + $templatePath = $this->getTemplatePath() . '/views'; + foreach (scandir($templatePath) as $file) { + if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { + $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); } } + return $files; } + + /** + * @return string the controller ID (without the module ID prefix) + */ + public function getControllerID() + { + $pos = strrpos($this->controllerClass, '\\'); + $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); + return Inflector::camel2id($class); + } + + /** + * @return string the action view file path + */ + public function getViewPath() + { + $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID); + return $module->getViewPath() . '/' . $this->getControllerID() ; + } + + public function getNameAttribute() + { + /** @var \yii\db\ActiveRecord $class */ + $class = $this->modelClass; + foreach ($class::getTableSchema()->columnNames as $name) { + if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { + return $name; + } + } + $pk = $class::primaryKey(); + return $pk[0]; + } + + /** + * @param string $attribute + * @return string + */ + public function generateActiveField($attribute) + { + $tableSchema = $this->getTableSchema(); + if (!isset($tableSchema->columns[$attribute])) { + return "\$form->field(\$model, '$attribute');"; + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox();"; + } elseif ($column->type === 'text') { + return "\$form->field(\$model, '$attribute')->textarea(array('rows' => 6));"; + } else { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { + $input = 'passwordInput'; + } else { + $input = 'textInput'; + } + if ($column->phpType !== 'string' || $column->size === null) { + return "\$form->field(\$model, '$attribute')->$input();"; + } else { + return "\$form->field(\$model, '$attribute')->$input(array('maxlength' => $column->size));"; + } + } + } + + /** + * @param string $attribute + * @return string + */ + public function generateActiveSearchField($attribute) + { + $tableSchema = $this->getTableSchema(); + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox();"; + } else { + return "\$form->field(\$model, '$attribute');"; + } + } + + /** + * @param \yii\db\ColumnSchema $column + * @return string + */ + public function generateColumnFormat($column) + { + if ($column->phpType === 'boolean') { + return 'boolean'; + } elseif ($column->type === 'text') { + return 'ntext'; + } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') { + return 'datetime'; + } elseif (stripos($column->name, 'email') !== false) { + return 'email'; + } elseif (stripos($column->name, 'url') !== false) { + return 'url'; + } else { + return 'text'; + } + } + + /** + * Generates validation rules for the search model. + * @return array the generated validation rules + */ + public function generateSearchRules() + { + $table = $this->getTableSchema(); + $types = array(); + foreach ($table->columns as $column) { + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + $types['integer'][] = $column->name; + break; + case Schema::TYPE_BOOLEAN: + $types['boolean'][] = $column->name; + break; + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + $types['number'][] = $column->name; + break; + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + default: + $types['safe'][] = $column->name; + break; + } + } + + $rules = array(); + foreach ($types as $type => $columns) { + $rules[] = "array('" . implode(', ', $columns) . "', '$type')"; + } + + return $rules; + } + + public function getSearchAttributes() + { + return $this->getTableSchema()->getColumnNames(); + } + + /** + * Generates the attribute labels for the search model. + * @return array the generated attribute labels (name => label) + */ + public function generateSearchLabels() + { + $table = $this->getTableSchema(); + $labels = array(); + foreach ($table->columns as $column) { + if (!strcasecmp($column->name, 'id')) { + $labels[$column->name] = 'ID'; + } else { + $label = Inflector::camel2words($column->name); + if (strcasecmp(substr($label, -3), ' id') === 0) { + $label = substr($label, 0, -3) . ' ID'; + } + $labels[$column->name] = $label; + } + } + return $labels; + } + + public function generateSearchConditions() + { + $table = $this->getTableSchema(); + $conditions = array(); + foreach ($table->columns as $column) { + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + case Schema::TYPE_BOOLEAN: + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + $conditions[] = "\$this->addCondition(\$query, '{$column->name}');"; + break; + default: + $conditions[] = "\$this->addCondition(\$query, '{$column->name}', true);"; + break; + } + } + + return $conditions; + } + + public function generateUrlParams() + { + $pks = $this->getTableSchema()->primaryKey; + if (count($pks) === 1) { + return "'id' => \$model->{$pks[0]}"; + } else { + $params = array(); + foreach ($pks as $pk) { + $params[] = "'$pk' => \$model->$pk"; + } + return implode(', ', $params); + } + } + + public function generateActionParams() + { + $pks = $this->getTableSchema()->primaryKey; + if (count($pks) === 1) { + return '$id'; + } else { + return '$' . implode(', $', $pks); + } + } + + public function generateActionParamComments() + { + $table = $this->getTableSchema(); + $pks = $table->primaryKey; + if (count($pks) === 1) { + return array('@param ' . $table->columns[$pks[0]]->phpType . ' $id'); + } else { + $params = array(); + foreach ($pks as $pk) { + $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; + } + return $params; + } + } + + public function getTableSchema() + { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + return $class::getTableSchema(); + } } diff --git a/framework/yii/gii/generators/crud/form.php b/framework/yii/gii/generators/crud/form.php index 0951695..829b8a3 100644 --- a/framework/yii/gii/generators/crud/form.php +++ b/framework/yii/gii/generators/crud/form.php @@ -6,5 +6,11 @@ */ echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'controllerID'); +echo $form->field($generator, 'searchModelClass'); +echo $form->field($generator, 'controllerClass'); echo $form->field($generator, 'baseControllerClass'); +echo $form->field($generator, 'moduleID'); +echo $form->field($generator, 'indexWidgetType')->dropDownList(array( + 'grid' => 'GridView', + 'list' => 'ListView', +)); diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index f372629..f53f819 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -1,8 +1,152 @@ controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); + +$pks = $generator->getTableSchema()->primaryKey; +$urlParams = $generator->generateUrlParams(); +$actionParams = $generator->generateActionParams(); +$actionParamComments = $generator->generateActionParamComments(); + +echo " + +namespace controllerClass, '\\')); ?>; + +use modelClass, '\\'); ?>; +use searchModelClass, '\\'); ?>; +use yii\data\ActiveDataProvider; +use baseControllerClass, '\\'); ?>; +use yii\web\HttpException; +use yii\web\VerbFilter; + +/** + * implements the CRUD actions for model. + */ +class extends baseControllerClass) . "\n"; ?> +{ + public function behaviors() + { + return array( + 'verbs' => array( + 'class' => VerbFilter::className(), + 'actions' => array( + 'delete' => array('post'), + ), + ), + ); + } + + /** + * Lists all models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new ; + $dataProvider = $searchModel->search($_GET); + + return $this->render('index', array( + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, + )); + } + + /** + * Displays a single model. + * + * @return mixed + */ + public function actionView() + { + return $this->render('view', array( + 'model' => $this->findModel(), + )); + } + + /** + * Creates a new model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new ; + + if ($model->load($_POST) && $model->save()) { + return $this->redirect(array('view', )); + } else { + return $this->render('create', array( + 'model' => $model, + )); + } + } + + /** + * Updates an existing model. + * If update is successful, the browser will be redirected to the 'view' page. + * + * @return mixed + */ + public function actionUpdate() + { + $model = $this->findModel(); + + if ($model->load($_POST) && $model->save()) { + return $this->redirect(array('view', )); + } else { + return $this->render('update', array( + 'model' => $model, + )); + } + } + + /** + * Deletes an existing model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed + */ + public function actionDelete() + { + $this->findModel()->delete(); + return $this->redirect(array('index')); + } + + /** + * Finds the model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * + * @return the loaded model + * @throws HttpException if the model cannot be found + */ + protected function findModel() + { + \$$pk"; + } + $condition = 'array(' . implode(', ', $condition) . ')'; +} +?> + if (($model = ::find()) !== null) { + return $model; + } else { + throw new HttpException(404, 'The requested page does not exist.'); + } + } +} diff --git a/framework/yii/gii/generators/crud/templates/model.php b/framework/yii/gii/generators/crud/templates/model.php deleted file mode 100644 index f372629..0000000 --- a/framework/yii/gii/generators/crud/templates/model.php +++ /dev/null @@ -1,8 +0,0 @@ -modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +$rules = $generator->generateSearchRules(); +$labels = $generator->generateSearchLabels(); +$searchAttributes = $generator->getSearchAttributes(); +$searchConditions = $generator->generateSearchConditions(); + +echo " + +namespace searchModelClass, '\\')); ?>; + +use yii\base\Model; +use yii\data\ActiveDataProvider; +use modelClass, '\\'); ?>; + +/** + * represents the model behind the search form about . + */ +class extends Model +{ + public $; + + public function rules() + { + return array( + , + ); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array( + $label): ?> + '" . addslashes($label) . "',\n"; ?> + + ); + } + + public function search($params) + { + $query = ::find(); + $dataProvider = new ActiveDataProvider(array( + 'query' => $query, + )); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + + + return $dataProvider; + } + + protected function addCondition($query, $attribute, $partialMatch = false) + { + $value = $this->$attribute; + if (trim($value) === '') { + return; + } + if ($partialMatch) { + $value = '%' . strtr($value, array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')) . '%'; + $query->andWhere(array('like', $attribute, $value)); + } else { + $query->andWhere(array($attribute => $value)); + } + } +} diff --git a/framework/yii/gii/generators/crud/templates/views/_form.php b/framework/yii/gii/generators/crud/templates/views/_form.php new file mode 100644 index 0000000..2d9d5dc --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/_form.php @@ -0,0 +1,44 @@ +modelClass; +$safeAttributes = $model->safeAttributes(); +if (empty($safeAttributes)) { + $safeAttributes = $model->getTableSchema()->columnNames; +} + +echo " + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + * @var yii\widgets\ActiveForm $form + */ +?> + +
+ + $form = ActiveForm::begin(); ?> + +generateActiveField($attribute) . " ?>\n\n"; +} ?> +
+ echo Html::submitButton($model->isNewRecord ? 'Create' : 'Update', array('class' => 'btn btn-primary')); ?> +
+ + ActiveForm::end(); ?> + +
diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php new file mode 100644 index 0000000..a649589 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/_search.php @@ -0,0 +1,45 @@ + + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/** + * @var yii\base\View $this + * @var searchModelClass, '\\'); ?> $model + * @var yii\widgets\ActiveForm $form + */ +?> + + diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/framework/yii/gii/generators/crud/templates/views/create.php new file mode 100644 index 0000000..669b99a --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/create.php @@ -0,0 +1,33 @@ + + +use yii\helpers\Html; + +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ + +$this->title = 'Create modelClass)); ?>'; +$this->params['breadcrumbs'][] = array('label' => 'modelClass))); ?>', 'url' => array('index')); +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

echo Html::encode($this->title); ?>

+ + echo $this->render('_form', array( + 'model' => $model, + )); ?> + +
diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php new file mode 100644 index 0000000..1de548a --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -0,0 +1,73 @@ +generateUrlParams(); +$nameAttribute = $generator->getNameAttribute(); + +echo " + +use yii\helpers\Html; +use indexWidgetType === 'grid' ? 'yii\grid\GridView' : 'yii\widgets\ListView'; ?>; + +/** + * @var yii\base\View $this + * @var yii\data\ActiveDataProvider $dataProvider + * @var searchModelClass, '\\'); ?> $searchModel + */ + +$this->title = 'modelClass))); ?>'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

echo Html::encode($this->title); ?>

+ + indexWidgetType === 'grid' ? ' //' : ''); ?> echo $this->render('_search', array('model' => $searchModel)); ?> + +

+ echo Html::a('Create modelClass); ?>', array('create'), array('class' => 'btn btn-danger')); ?> +

+ +indexWidgetType === 'grid'): ?> + echo GridView::widget(array( + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'columns' => array( + array('class' => 'yii\grid\SerialColumn'), + +getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + if (++$count < 6) { + echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + } else { + echo "\t\t\t// '" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + } +} +?> + + array('class' => 'yii\grid\ActionColumn'), + ), + )); ?> + + echo ListView::widget(array( + 'dataProvider' => $dataProvider, + 'itemOptions' => array( + 'class' => 'item', + ), + 'itemView' => function ($model, $key, $index, $widget) { + return Html::a(Html::encode($model->), array('view', )); + }, + )); ?> + + +
diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php new file mode 100644 index 0000000..cb892c2 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/update.php @@ -0,0 +1,36 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; + +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ + +$this->title = 'Update modelClass)); ?>: ' . $model->getNameAttribute(); ?>; +$this->params['breadcrumbs'][] = array('label' => 'modelClass))); ?>', 'url' => array('index')); +$this->params['breadcrumbs'][] = array('label' => $model->getNameAttribute(); ?>, 'url' => array('view', )); +$this->params['breadcrumbs'][] = 'Update'; +?> +
+ +

echo Html::encode($this->title); ?>

+ + echo $this->render('_form', array( + 'model' => $model, + )); ?> + +
diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php new file mode 100644 index 0000000..d25a851 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -0,0 +1,53 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; +use yii\widgets\DetailView; + +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ + +$this->title = $model->getNameAttribute(); ?>; +$this->params['breadcrumbs'][] = array('label' => 'modelClass))); ?>', 'url' => array('index')); +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

echo Html::encode($this->title); ?>

+ +

+ echo Html::a('Update', array('update', ), array('class' => 'btn btn-danger')); ?> + echo Html::a('Delete', array('delete', ), array( + 'class' => 'btn btn-danger', + 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), + 'data-method' => 'post', + )); ?> +

+ + echo DetailView::widget(array( + 'model' => $model, + 'attributes' => array( +getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; +} +?> + ), + )); ?> + +
diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php index 7aa601a..b9c8f23 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/framework/yii/gii/generators/model/Generator.php @@ -93,8 +93,11 @@ class Generator extends \yii\gii\Generator 'db' => 'This is the ID of the DB application component.', 'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. tbl_post. The table name may consist of the DB schema part if needed, e.g. public.tbl_post. - The table name may contain an asterisk at the end to match multiple table names, e.g. tbl_*. - In this case, multiple ActiveRecord classes will be generated, one for each matching table name.', + The table name may contain an asterisk to match multiple table names, e.g. tbl_* + will match tables who name starts with tbl_. In this case, multiple ActiveRecord classes + will be generated, one for each matching table name; and the class names will be generated from + the matching characters. For example, table tbl_post will generate Post + class.', 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain the namespace part as it is specified in "Namespace". You do not need to specify the class name if "Table Name" contains an asterisk at the end, in which case multiple ActiveRecord classes will be generated.', diff --git a/framework/yii/gii/views/default/view.php b/framework/yii/gii/views/default/view.php index c61d5d0..9754918 100644 --- a/framework/yii/gii/views/default/view.php +++ b/framework/yii/gii/views/default/view.php @@ -30,6 +30,7 @@ foreach ($generator->templates as $name => $path) { "$id-generator", + 'successCssClass' => '', 'fieldConfig' => array('class' => ActiveField::className()), )); ?>
@@ -39,7 +40,7 @@ foreach ($generator->templates as $name => $path) { 'form' => $form, )); ?> field($generator, 'template')->sticky() - ->label(array('label' => 'Code Template')) + ->label('Code Template') ->dropDownList($templates)->hint(' Please select which set of the templates should be used to generated the code. '); ?> diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php new file mode 100644 index 0000000..72fafb6 --- /dev/null +++ b/framework/yii/grid/ActionColumn.php @@ -0,0 +1,102 @@ + + * @since 2.0 + */ +class ActionColumn extends Column +{ + public $template = '{view} {update} {delete}'; + public $buttons = array(); + public $urlCreator; + + public function init() + { + parent::init(); + $this->initDefaultButtons(); + } + + protected function initDefaultButtons() + { + if (!isset($this->buttons['view'])) { + $this->buttons['view'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'view'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'View'), + )); + }; + } + if (!isset($this->buttons['update'])) { + $this->buttons['update'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'update'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'Update'), + )); + }; + } + if (!isset($this->buttons['delete'])) { + $this->buttons['delete'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'delete'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'Delete'), + 'data-confirm' => Yii::t('yii', 'Are you sure to delete this item?'), + 'data-method' => 'post', + )); + }; + } + } + + /** + * @param \yii\db\ActiveRecord $model + * @param string $action + * @return string + */ + public function createUrl($model, $action) + { + if ($this->urlCreator instanceof Closure) { + return call_user_func($this->urlCreator, $model, $action); + } else { + $route = Inflector::camel2id(StringHelper::basename(get_class($model))) . '/' . $action; + $params = $model->getPrimaryKey(true); + if (count($params) === 1) { + $params = array('id' => reset($params)); + } + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } + + /** + * Renders the data cell content. + * @param mixed $model the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]]. + * @return string the rendering result + */ + protected function renderDataCellContent($model, $index) + { + $column = $this; + return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $column) { + $name = $matches[1]; + if (isset($column->buttons[$name])) { + return call_user_func($column->buttons[$name], $model, $column); + } else { + return ''; + } + }, $this->template); + } +} diff --git a/framework/yii/grid/DataColumn.php b/framework/yii/grid/DataColumn.php index 295dece..4ebbb8f 100644 --- a/framework/yii/grid/DataColumn.php +++ b/framework/yii/grid/DataColumn.php @@ -68,6 +68,12 @@ class DataColumn extends Column * - If you don't want a filter for this data column, set this value to be false. */ public $filter; + /** + * @var array the HTML attributes for the filter input fields. This property is used in combination with + * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to + * render the HTML attributes for the generated filter input fields. + */ + public $filterInputOptions = array('class' => 'form-control'); protected function renderHeaderCellContent() @@ -111,9 +117,10 @@ class DataColumn extends Column return $this->filter; } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model && $this->attribute !== null) { if (is_array($this->filter)) { - return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, array('prompt' => '')); + $options = array_merge(array('prompt' => ''), $this->filterInputOptions); + return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options); } else { - return Html::activeTextInput($this->grid->filterModel, $this->attribute); + return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions); } } else { return parent::renderFilterCellContent(); diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php index 60d325d..f4433bc 100644 --- a/framework/yii/grid/GridView.php +++ b/framework/yii/grid/GridView.php @@ -14,13 +14,13 @@ use yii\base\InvalidConfigException; use yii\base\Widget; use yii\db\ActiveRecord; use yii\helpers\Html; -use yii\widgets\ListViewBase; +use yii\widgets\BaseListView; /** * @author Qiang Xue * @since 2.0 */ -class GridView extends ListViewBase +class GridView extends BaseListView { const FILTER_POS_HEADER = 'header'; const FILTER_POS_FOOTER = 'footer'; @@ -124,16 +124,6 @@ class GridView extends ListViewBase * Both "format" and "label" are optional. They will take default values if absent. */ public $columns = array(); - /** - * @var string the layout that determines how different sections of the list view should be organized. - * The following tokens will be replaced with the corresponding section contents: - * - * - `{summary}`: the summary section. See [[renderSummary()]]. - * - `{items}`: the list items. See [[renderItems()]]. - * - `{sorter}`: the sorter. See [[renderSorter()]]. - * - `{pager}`: the pager. See [[renderPager()]]. - */ - public $layout = "{items}\n{summary}\n{pager}"; public $emptyCell = ' '; /** * @var \yii\base\Model the model that keeps the user-entered filter data. When this property is set, diff --git a/framework/yii/grid/SerialColumn.php b/framework/yii/grid/SerialColumn.php index 2fdb770..6a875ae 100644 --- a/framework/yii/grid/SerialColumn.php +++ b/framework/yii/grid/SerialColumn.php @@ -15,6 +15,8 @@ namespace yii\grid; */ class SerialColumn extends Column { + public $header = '#'; + /** * Renders the data cell content. * @param mixed $model the data model diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/yii/helpers/ArrayHelper.php index 085104b..9d428f5 100644 --- a/framework/yii/helpers/ArrayHelper.php +++ b/framework/yii/helpers/ArrayHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class ArrayHelper extends ArrayHelperBase +class ArrayHelper extends BaseArrayHelper { } diff --git a/framework/yii/helpers/ArrayHelperBase.php b/framework/yii/helpers/BaseArrayHelper.php similarity index 99% rename from framework/yii/helpers/ArrayHelperBase.php rename to framework/yii/helpers/BaseArrayHelper.php index 59129de..0ed584f 100644 --- a/framework/yii/helpers/ArrayHelperBase.php +++ b/framework/yii/helpers/BaseArrayHelper.php @@ -12,14 +12,14 @@ use yii\base\Arrayable; use yii\base\InvalidParamException; /** - * ArrayHelperBase provides concrete implementation for [[ArrayHelper]]. + * BaseArrayHelper provides concrete implementation for [[ArrayHelper]]. * - * Do not use ArrayHelperBase. Use [[ArrayHelper]] instead. + * Do not use BaseArrayHelper. Use [[ArrayHelper]] instead. * * @author Qiang Xue * @since 2.0 */ -class ArrayHelperBase +class BaseArrayHelper { /** * Converts an object or an array of objects into an array. diff --git a/framework/yii/helpers/ConsoleBase.php b/framework/yii/helpers/BaseConsole.php similarity index 99% rename from framework/yii/helpers/ConsoleBase.php rename to framework/yii/helpers/BaseConsole.php index a985291..6796283 100644 --- a/framework/yii/helpers/ConsoleBase.php +++ b/framework/yii/helpers/BaseConsole.php @@ -8,14 +8,14 @@ namespace yii\helpers; /** - * ConsoleBase provides concrete implementation for [[Console]]. + * BaseConsole provides concrete implementation for [[Console]]. * - * Do not use ConsoleBase. Use [[Console]] instead. + * Do not use BaseConsole. Use [[Console]] instead. * * @author Carsten Brandt * @since 2.0 */ -class ConsoleBase +class BaseConsole { const FG_BLACK = 30; const FG_RED = 31; diff --git a/framework/yii/helpers/FileHelperBase.php b/framework/yii/helpers/BaseFileHelper.php similarity index 95% rename from framework/yii/helpers/FileHelperBase.php rename to framework/yii/helpers/BaseFileHelper.php index 0e480da..7540168 100644 --- a/framework/yii/helpers/FileHelperBase.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -12,15 +12,15 @@ namespace yii\helpers; use Yii; /** - * FileHelperBase provides concrete implementation for [[FileHelper]]. + * BaseFileHelper provides concrete implementation for [[FileHelper]]. * - * Do not use FileHelperBase. Use [[FileHelper]] instead. + * Do not use BaseFileHelper. Use [[FileHelper]] instead. * * @author Qiang Xue * @author Alex Makarov * @since 2.0 */ -class FileHelperBase +class BaseFileHelper { /** * Normalizes a file/directory path. @@ -155,6 +155,10 @@ class FileHelperBase * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches * both '/' and '\' in the paths. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * If the callback returns false, the copy operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or * file copied from, while `$to` is the copy target. @@ -173,6 +177,9 @@ class FileHelperBase $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { + if (isset($options['beforeCopy']) && !call_user_func($options['beforeCopy'], $from, $to)) { + continue; + } if (is_file($from)) { copy($from, $to); if (isset($options['fileMode'])) { diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/BaseHtml.php similarity index 99% rename from framework/yii/helpers/HtmlBase.php rename to framework/yii/helpers/BaseHtml.php index cfff8f5..2baa679 100644 --- a/framework/yii/helpers/HtmlBase.php +++ b/framework/yii/helpers/BaseHtml.php @@ -13,14 +13,14 @@ use yii\web\Request; use yii\base\Model; /** - * HtmlBase provides concrete implementation for [[Html]]. + * BaseHtml provides concrete implementation for [[Html]]. * - * Do not use HtmlBase. Use [[Html]] instead. + * Do not use BaseHtml. Use [[Html]] instead. * * @author Qiang Xue * @since 2.0 */ -class HtmlBase +class BaseHtml { /** * @var array list of void elements (element name => 1) @@ -238,7 +238,7 @@ class HtmlBase $method = 'post'; } if ($request->enableCsrfValidation) { - $hiddenInputs[] = static::hiddenInput($request->csrfTokenName, $request->getCsrfToken()); + $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); } } @@ -1049,7 +1049,10 @@ class HtmlBase */ public static function activeFileInput($model, $attribute, $options = array()) { - return static::activeInput('file', $model, $attribute, $options); + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + . static::activeInput('file', $model, $attribute, $options); } /** diff --git a/framework/yii/helpers/HtmlPurifierBase.php b/framework/yii/helpers/BaseHtmlPurifier.php similarity index 83% rename from framework/yii/helpers/HtmlPurifierBase.php rename to framework/yii/helpers/BaseHtmlPurifier.php index e89a589..17d2122 100644 --- a/framework/yii/helpers/HtmlPurifierBase.php +++ b/framework/yii/helpers/BaseHtmlPurifier.php @@ -7,14 +7,14 @@ namespace yii\helpers; /** - * HtmlPurifierBase provides concrete implementation for [[HtmlPurifier]]. + * BaseHtmlPurifier provides concrete implementation for [[HtmlPurifier]]. * - * Do not use HtmlPurifierBase. Use [[HtmlPurifier]] instead. + * Do not use BaseHtmlPurifier. Use [[HtmlPurifier]] instead. * * @author Alexander Makarov * @since 2.0 */ -class HtmlPurifierBase +class BaseHtmlPurifier { /** * Passes markup through HTMLPurifier making it safe to output to end user diff --git a/framework/yii/helpers/InflectorBase.php b/framework/yii/helpers/BaseInflector.php similarity index 99% rename from framework/yii/helpers/InflectorBase.php rename to framework/yii/helpers/BaseInflector.php index 87c1ff4..affd3dd 100644 --- a/framework/yii/helpers/InflectorBase.php +++ b/framework/yii/helpers/BaseInflector.php @@ -10,14 +10,14 @@ namespace yii\helpers; use Yii; /** - * InflectorBase provides concrete implementation for [[Inflector]]. + * BaseInflector provides concrete implementation for [[Inflector]]. * - * Do not use InflectorBase. Use [[Inflector]] instead. + * Do not use BaseInflector. Use [[Inflector]] instead. * * @author Antonio Ramirez * @since 2.0 */ -class InflectorBase +class BaseInflector { /** * @var array the rules for converting a word into its plural form. diff --git a/framework/yii/helpers/JsonBase.php b/framework/yii/helpers/BaseJson.php similarity index 96% rename from framework/yii/helpers/JsonBase.php rename to framework/yii/helpers/BaseJson.php index fa3fb01..bd6ede5 100644 --- a/framework/yii/helpers/JsonBase.php +++ b/framework/yii/helpers/BaseJson.php @@ -12,14 +12,14 @@ use yii\base\Arrayable; use yii\web\JsExpression; /** - * JsonBase provides concrete implementation for [[Json]]. + * BaseJson provides concrete implementation for [[Json]]. * - * Do not use JsonBase. Use [[Json]] instead. + * Do not use BaseJson. Use [[Json]] instead. * * @author Qiang Xue * @since 2.0 */ -class JsonBase +class BaseJson { /** * Encodes the given value into a JSON string. diff --git a/framework/yii/helpers/MarkdownBase.php b/framework/yii/helpers/BaseMarkdown.php similarity index 84% rename from framework/yii/helpers/MarkdownBase.php rename to framework/yii/helpers/BaseMarkdown.php index 9db5b1e..40a1dc4 100644 --- a/framework/yii/helpers/MarkdownBase.php +++ b/framework/yii/helpers/BaseMarkdown.php @@ -10,14 +10,14 @@ namespace yii\helpers; use Michelf\MarkdownExtra; /** - * MarkdownBase provides concrete implementation for [[Markdown]]. + * BaseMarkdown provides concrete implementation for [[Markdown]]. * - * Do not use MarkdownBase. Use [[Markdown]] instead. + * Do not use BaseMarkdown. Use [[Markdown]] instead. * * @author Alexander Makarov * @since 2.0 */ -class MarkdownBase +class BaseMarkdown { /** * @var MarkdownExtra diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/BaseSecurity.php similarity index 79% rename from framework/yii/helpers/SecurityBase.php rename to framework/yii/helpers/BaseSecurity.php index 541b311..a1b0ec4 100644 --- a/framework/yii/helpers/SecurityBase.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -13,31 +13,51 @@ use yii\base\InvalidConfigException; use yii\base\InvalidParamException; /** - * SecurityBase provides concrete implementation for [[Security]]. + * BaseSecurity provides concrete implementation for [[Security]]. * - * Do not use SecurityBase. Use [[Security]] instead. + * Do not use BaseSecurity. Use [[Security]] instead. * * @author Qiang Xue * @author Tom Worster * @since 2.0 */ -class SecurityBase +class BaseSecurity { /** + * Uses AES, block size is 128-bit (16 bytes). + */ + const CRYPT_BLOCK_SIZE = 16; + + /** + * Uses AES-192, key size is 192-bit (24 bytes). + */ + const CRYPT_KEY_SIZE = 24; + + /** + * Uses SHA-256. + */ + const DERIVATION_HASH = 'sha256'; + + /** + * Uses 1000 iterations. + */ + const DERIVATION_ITERATIONS = 1000; + + /** * Encrypts data. * @param string $data data to be encrypted. - * @param string $key the encryption secret key + * @param string $password the encryption password * @return string the encrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see decrypt() */ - public static function encrypt($data, $key) + public static function encrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); + $data = static::addPadding($data); srand(); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $encrypted = $iv . mcrypt_generic($module, $data); mcrypt_generic_deinit($module); @@ -48,23 +68,69 @@ class SecurityBase /** * Decrypts data * @param string $data data to be decrypted. - * @param string $key the decryption secret key + * @param string $password the decryption password * @return string the decrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see encrypt() */ - public static function decrypt($data, $key) + public static function decrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); $ivSize = mcrypt_enc_get_iv_size($module); $iv = StringHelper::substr($data, 0, $ivSize); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); mcrypt_generic_deinit($module); mcrypt_module_close($module); - return rtrim($decrypted, "\0"); + return static::stripPadding($decrypted); + } + + /** + * Adds a padding to the given data (PKCS #7). + * @param string $data the data to pad + * @return string the padded data + */ + protected static function addPadding($data) + { + $pad = self::CRYPT_BLOCK_SIZE - (StringHelper::strlen($data) % self::CRYPT_BLOCK_SIZE); + return $data . str_repeat(chr($pad), $pad); + } + + /** + * Strips the padding from the given data. + * @param string $data the data to trim + * @return string the trimmed data + */ + protected static function stripPadding($data) + { + $end = StringHelper::substr($data, -1, NULL); + $last = ord($end); + $n = StringHelper::strlen($data) - $last; + if (StringHelper::substr($data, $n, NULL) == str_repeat($end, $last)) { + return StringHelper::substr($data, 0, $n); + } + return false; + } + + /** + * Derives a key from the given password (PBKDF2). + * @param string $password the source password + * @param string $salt the random salt + * @return string the derived key + */ + protected static function deriveKey($password, $salt) + { + if (function_exists('hash_pbkdf2')) { + return hash_pbkdf2(self::DERIVATION_HASH, $password, $salt, self::DERIVATION_ITERATIONS, self::CRYPT_KEY_SIZE, true); + } + $hmac = hash_hmac(self::DERIVATION_HASH, $salt . pack('N', 1), $password, true); + $xorsum = $hmac; + for ($i = 1; $i < self::DERIVATION_ITERATIONS; $i++) { + $hmac = hash_hmac(self::DERIVATION_HASH, $hmac, $password, true); + $xorsum ^= $hmac; + } + return substr($xorsum, 0, self::CRYPT_KEY_SIZE); } /** @@ -133,19 +199,19 @@ class SecurityBase } /** - * Generates a random key. + * Generates a random key. The key may contain uppercase and lowercase latin letters, digits, underscore, dash and dot. * @param integer $length the length of the key that should be generated * @return string the generated random key */ public static function generateRandomKey($length = 32) { if (function_exists('openssl_random_pseudo_bytes')) { - $key = base64_encode(openssl_random_pseudo_bytes($length, $strong)); + $key = strtr(base64_encode(openssl_random_pseudo_bytes($length, $strong)), '+/=', '_-.'); if ($strong) { return substr($key, 0, $length); } } - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; return substr(str_shuffle(str_repeat($chars, 5)), 0, $length); } diff --git a/framework/yii/helpers/StringHelperBase.php b/framework/yii/helpers/BaseStringHelper.php similarity index 83% rename from framework/yii/helpers/StringHelperBase.php rename to framework/yii/helpers/BaseStringHelper.php index 54dabda..e1622b9 100644 --- a/framework/yii/helpers/StringHelperBase.php +++ b/framework/yii/helpers/BaseStringHelper.php @@ -10,15 +10,15 @@ namespace yii\helpers; use yii\base\InvalidParamException; /** - * StringHelperBase provides concrete implementation for [[StringHelper]]. + * BaseStringHelper provides concrete implementation for [[StringHelper]]. * - * Do not use StringHelperBase. Use [[StringHelper]] instead. + * Do not use BaseStringHelper. Use [[StringHelper]] instead. * * @author Qiang Xue * @author Alex Makarov * @since 2.0 */ -class StringHelperBase +class BaseStringHelper { /** * Returns the number of bytes in the given string. @@ -47,8 +47,8 @@ class StringHelperBase /** * Returns the trailing name component of a path. - * This method does the same as the php function `basename()` except that it will - * always use \ and / as directory separators, independent of the operating system. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. * This method was mainly created to work on php namespaces. When working with real * file paths, php's `basename()` should work fine for you. * Note: this method is not aware of the actual filesystem, or path components such as "..". @@ -70,6 +70,24 @@ class StringHelperBase } /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return $path; + } + } + + /** * Compares two strings or string arrays, and return their differences. * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. * @param string|array $lines1 the first string or string array to be compared. If it is a string, diff --git a/framework/yii/helpers/VarDumperBase.php b/framework/yii/helpers/BaseVarDumper.php similarity index 96% rename from framework/yii/helpers/VarDumperBase.php rename to framework/yii/helpers/BaseVarDumper.php index c7da208..f109125 100644 --- a/framework/yii/helpers/VarDumperBase.php +++ b/framework/yii/helpers/BaseVarDumper.php @@ -9,14 +9,14 @@ namespace yii\helpers; /** - * VarDumperBase provides concrete implementation for [[VarDumper]]. + * BaseVarDumper provides concrete implementation for [[VarDumper]]. * - * Do not use VarDumperBase. Use [[VarDumper]] instead. + * Do not use BaseVarDumper. Use [[VarDumper]] instead. * * @author Qiang Xue * @since 2.0 */ -class VarDumperBase +class BaseVarDumper { private static $_objects; private static $_output; diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index eeadcbc..a34dc96 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -8,15 +8,12 @@ namespace yii\helpers; /** - * TODO adjust phpdoc - * Console View is the base class for console view components - * - * A console view provides functionality to create rich console application by allowing to format output - * by adding color and font style to it. + * Console helper provides useful methods for command line related tasks such as getting input or formatting and coloring + * output. * * @author Carsten Brandt * @since 2.0 */ -class Console extends ConsoleBase +class Console extends BaseConsole { } diff --git a/framework/yii/helpers/FileHelper.php b/framework/yii/helpers/FileHelper.php index 9241025..63954a4 100644 --- a/framework/yii/helpers/FileHelper.php +++ b/framework/yii/helpers/FileHelper.php @@ -16,6 +16,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class FileHelper extends FileHelperBase +class FileHelper extends BaseFileHelper { } diff --git a/framework/yii/helpers/Html.php b/framework/yii/helpers/Html.php index 8e4f1c9..f4fbbba 100644 --- a/framework/yii/helpers/Html.php +++ b/framework/yii/helpers/Html.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Html extends HtmlBase +class Html extends BaseHtml { } diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/yii/helpers/HtmlPurifier.php index f7203e4..e1511e4 100644 --- a/framework/yii/helpers/HtmlPurifier.php +++ b/framework/yii/helpers/HtmlPurifier.php @@ -32,6 +32,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class HtmlPurifier extends HtmlPurifierBase +class HtmlPurifier extends BaseHtmlPurifier { } diff --git a/framework/yii/helpers/Inflector.php b/framework/yii/helpers/Inflector.php index ba9c069..ab4713e 100644 --- a/framework/yii/helpers/Inflector.php +++ b/framework/yii/helpers/Inflector.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Antonio Ramirez * @since 2.0 */ -class Inflector extends InflectorBase +class Inflector extends BaseInflector { } diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 424de1f..8ca436a 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Json extends JsonBase +class Json extends BaseJson { } diff --git a/framework/yii/helpers/Markdown.php b/framework/yii/helpers/Markdown.php index 690df5d..3dcc750 100644 --- a/framework/yii/helpers/Markdown.php +++ b/framework/yii/helpers/Markdown.php @@ -30,6 +30,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class Markdown extends MarkdownBase +class Markdown extends BaseMarkdown { } diff --git a/framework/yii/helpers/Security.php b/framework/yii/helpers/Security.php index d0ca2ed..0e3ee38 100644 --- a/framework/yii/helpers/Security.php +++ b/framework/yii/helpers/Security.php @@ -24,6 +24,6 @@ namespace yii\helpers; * @author Tom Worster * @since 2.0 */ -class Security extends SecurityBase +class Security extends BaseSecurity { } diff --git a/framework/yii/helpers/StringHelper.php b/framework/yii/helpers/StringHelper.php index ef75790..5ecd390 100644 --- a/framework/yii/helpers/StringHelper.php +++ b/framework/yii/helpers/StringHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class StringHelper extends StringHelperBase +class StringHelper extends BaseStringHelper { } diff --git a/framework/yii/helpers/VarDumper.php b/framework/yii/helpers/VarDumper.php index 50e543c..1ac5aa7 100644 --- a/framework/yii/helpers/VarDumper.php +++ b/framework/yii/helpers/VarDumper.php @@ -23,6 +23,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class VarDumper extends VarDumperBase +class VarDumper extends BaseVarDumper { } diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php index 54f3a49..2046ecc 100644 --- a/framework/yii/log/Logger.php +++ b/framework/yii/log/Logger.php @@ -220,7 +220,7 @@ class Logger extends Component * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[YiiBase]] class file. + * of [[BaseYii]] class file. * @return float the total elapsed time in seconds for current request. */ public function getElapsedTime() diff --git a/framework/yii/db/redis/Connection.php b/framework/yii/redis/Connection.php similarity index 99% rename from framework/yii/db/redis/Connection.php rename to framework/yii/redis/Connection.php index ea59f22..848b408 100644 --- a/framework/yii/db/redis/Connection.php +++ b/framework/yii/redis/Connection.php @@ -7,13 +7,12 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\db\redis; +namespace yii\redis; -use \yii\base\Component; +use yii\base\Component; use yii\base\InvalidConfigException; -use \yii\db\Exception; +use yii\db\Exception; use yii\helpers\Inflector; -use yii\helpers\StringHelper; /** * diff --git a/framework/yii/db/redis/Transaction.php b/framework/yii/redis/Transaction.php similarity index 99% rename from framework/yii/db/redis/Transaction.php rename to framework/yii/redis/Transaction.php index 024f821..94cff7a 100644 --- a/framework/yii/db/redis/Transaction.php +++ b/framework/yii/redis/Transaction.php @@ -7,7 +7,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\db\redis; +namespace yii\redis; use yii\base\InvalidConfigException; use yii\db\Exception; diff --git a/framework/yii/validators/FileValidator.php b/framework/yii/validators/FileValidator.php index fbd432d..e2880af 100644 --- a/framework/yii/validators/FileValidator.php +++ b/framework/yii/validators/FileValidator.php @@ -135,7 +135,7 @@ class FileValidator extends Validator return; } foreach ($files as $i => $file) { - if (!$file instanceof UploadedFile || $file->getError() == UPLOAD_ERR_NO_FILE) { + if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) { unset($files[$i]); } } @@ -152,7 +152,7 @@ class FileValidator extends Validator } } else { $file = $object->$attribute; - if ($file instanceof UploadedFile && $file->getError() != UPLOAD_ERR_NO_FILE) { + if ($file instanceof UploadedFile && $file->error != UPLOAD_ERR_NO_FILE) { $this->validateFile($object, $attribute, $file); } else { $this->addError($object, $attribute, $this->uploadRequired); @@ -168,37 +168,37 @@ class FileValidator extends Validator */ protected function validateFile($object, $attribute, $file) { - switch ($file->getError()) { + switch ($file->error) { case UPLOAD_ERR_OK: - if ($this->maxSize !== null && $file->getSize() > $this->maxSize) { - $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit())); + if ($this->maxSize !== null && $file->size > $this->maxSize) { + $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->name, '{limit}' => $this->getSizeLimit())); } - if ($this->minSize !== null && $file->getSize() < $this->minSize) { - $this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->getName(), '{limit}' => $this->minSize)); + if ($this->minSize !== null && $file->size < $this->minSize) { + $this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->name, '{limit}' => $this->minSize)); } - if (!empty($this->types) && !in_array(strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION)), $this->types, true)) { - $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $this->types))); + if (!empty($this->types) && !in_array(strtolower(pathinfo($file->name, PATHINFO_EXTENSION)), $this->types, true)) { + $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->name, '{extensions}' => implode(', ', $this->types))); } break; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: - $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit())); + $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->name, '{limit}' => $this->getSizeLimit())); break; case UPLOAD_ERR_PARTIAL: $this->addError($object, $attribute, $this->message); - Yii::warning('File was only partially uploaded: ' . $file->getName(), __METHOD__); + Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__); break; case UPLOAD_ERR_NO_TMP_DIR: $this->addError($object, $attribute, $this->message); - Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->getName(), __METHOD__); + Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->name, __METHOD__); break; case UPLOAD_ERR_CANT_WRITE: $this->addError($object, $attribute, $this->message); - Yii::warning('Failed to write the uploaded file to disk: ' . $file->getName(), __METHOD__); + Yii::warning('Failed to write the uploaded file to disk: ' . $file->name, __METHOD__); break; case UPLOAD_ERR_EXTENSION: $this->addError($object, $attribute, $this->message); - Yii::warning('File upload was stopped by some PHP extension: ' . $file->getName(), __METHOD__); + Yii::warning('File upload was stopped by some PHP extension: ' . $file->name, __METHOD__); break; default: break; diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php index 4ae2099..df57e7d 100644 --- a/framework/yii/validators/RegularExpressionValidator.php +++ b/framework/yii/validators/RegularExpressionValidator.php @@ -30,7 +30,6 @@ class RegularExpressionValidator extends Validator /** * @var boolean whether to invert the validation logic. Defaults to false. If set to true, * the regular expression defined via [[pattern]] should NOT match the attribute value. - * @throws InvalidConfigException if the "pattern" is not a valid regular expression **/ public $not = false; @@ -82,7 +81,6 @@ class RegularExpressionValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. - * @throws InvalidConfigException if the "pattern" is not a valid regular expression */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/StringValidator.php b/framework/yii/validators/StringValidator.php index 2cbab6c..946ca6e 100644 --- a/framework/yii/validators/StringValidator.php +++ b/framework/yii/validators/StringValidator.php @@ -52,7 +52,7 @@ class StringValidator extends Validator */ public $tooLong; /** - * @var string user-defined error message used when the length of the value is not equal to [[is]]. + * @var string user-defined error message used when the length of the value is not equal to [[length]]. */ public $notEqual; /** diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php index 35d6cae..7f791b8 100644 --- a/framework/yii/web/AccessControl.php +++ b/framework/yii/web/AccessControl.php @@ -31,15 +31,17 @@ use yii\base\ActionFilter; * 'class' => \yii\web\AccessControl::className(), * 'only' => array('create', 'update'), * 'rules' => array( + * // deny all POST requests + * array( + * 'allow' => false, + * 'verbs' => array('POST') + * ), * // allow authenticated users * array( * 'allow' => true, * 'roles' => array('@'), * ), - * // deny all - * array( - * 'allow' => false, - * ), + * // everything else is denied * ), * ), * ); diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php index cd931c9..420a5bc 100644 --- a/framework/yii/web/AssetConverter.php +++ b/framework/yii/web/AssetConverter.php @@ -16,7 +16,7 @@ use yii\base\Component; * @author Qiang Xue * @since 2.0 */ -class AssetConverter extends Component implements IAssetConverter +class AssetConverter extends Component implements AssetConverterInterface { /** * @var array the commands that are used to perform the asset conversion. diff --git a/framework/yii/web/IAssetConverter.php b/framework/yii/web/AssetConverterInterface.php similarity index 83% rename from framework/yii/web/IAssetConverter.php rename to framework/yii/web/AssetConverterInterface.php index 6021963..51309c6 100644 --- a/framework/yii/web/IAssetConverter.php +++ b/framework/yii/web/AssetConverterInterface.php @@ -8,12 +8,12 @@ namespace yii\web; /** - * The IAssetConverter interface must be implemented by asset converter classes. + * The AssetConverterInterface must be implemented by asset converter classes. * * @author Qiang Xue * @since 2.0 */ -interface IAssetConverter +interface AssetConverterInterface { /** * Converts a given asset file into a CSS or JS file. diff --git a/framework/yii/web/AssetManager.php b/framework/yii/web/AssetManager.php index c6f7fea..500848b 100644 --- a/framework/yii/web/AssetManager.php +++ b/framework/yii/web/AssetManager.php @@ -16,7 +16,7 @@ use yii\helpers\FileHelper; /** * AssetManager manages asset bundles and asset publishing. * - * @property IAssetConverter $converter The asset converter. Note that the type of this property differs in + * @property AssetConverterInterface $converter The asset converter. Note that the type of this property differs in * getter and setter. See [[getConverter()]] and [[setConverter()]] for details. * * @author Qiang Xue @@ -116,7 +116,7 @@ class AssetManager extends Component /** * Returns the asset converter. - * @return IAssetConverter the asset converter. + * @return AssetConverterInterface the asset converter. */ public function getConverter() { @@ -130,8 +130,8 @@ class AssetManager extends Component /** * Sets the asset converter. - * @param array|IAssetConverter $value the asset converter. This can be either - * an object implementing the [[IAssetConverter]] interface, or a configuration + * @param array|AssetConverterInterface $value the asset converter. This can be either + * an object implementing the [[AssetConverterInterface]], or a configuration * array that can be used to create the asset converter object. */ public function setConverter($value) diff --git a/framework/yii/web/CacheSession.php b/framework/yii/web/CacheSession.php index bb387e1..b4ce2ae 100644 --- a/framework/yii/web/CacheSession.php +++ b/framework/yii/web/CacheSession.php @@ -42,13 +42,13 @@ class CacheSession extends Session */ public function init() { - parent::init(); if (is_string($this->cache)) { $this->cache = Yii::$app->getComponent($this->cache); } if (!$this->cache instanceof Cache) { throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.'); } + parent::init(); } /** diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index adb1b4d..6b8afa4 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -20,6 +20,12 @@ use yii\helpers\Html; class Controller extends \yii\base\Controller { /** + * @var boolean whether to enable CSRF validation for the actions in this controller. + * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. + */ + public $enableCsrfValidation = true; + + /** * Binds the parameters to the action. * This method is invoked by [[Action]] when it begins to run with the given parameters. * This method will check the parameter names that the action requires and return @@ -62,6 +68,21 @@ class Controller extends \yii\base\Controller } /** + * @inheritdoc + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + if ($this->enableCsrfValidation && !Yii::$app->getRequest()->validateCsrfToken()) { + throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); + } + return true; + } else { + return false; + } + } + + /** * Creates a URL using the given route and parameters. * * This method enhances [[UrlManager::createUrl()]] by supporting relative routes. @@ -105,8 +126,7 @@ class Controller extends \yii\base\Controller * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302 - * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. + * @param integer $statusCode the HTTP status code. If null, it will use 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the current response object diff --git a/framework/yii/web/Identity.php b/framework/yii/web/IdentityInterface.php similarity index 88% rename from framework/yii/web/Identity.php rename to framework/yii/web/IdentityInterface.php index 101ecdb..c796b50 100644 --- a/framework/yii/web/Identity.php +++ b/framework/yii/web/IdentityInterface.php @@ -8,13 +8,13 @@ namespace yii\web; /** - * Identity is the interface that should be implemented by a class providing identity information. + * IdentityInterface is the interface that should be implemented by a class providing identity information. * * This interface can typically be implemented by a user model class. For example, the following * code shows how to implement this interface by a User ActiveRecord class: * * ~~~ - * class User extends ActiveRecord implements Identity + * class User extends ActiveRecord implements IdentityInterface * { * public static function findIdentity($id) * { @@ -41,12 +41,12 @@ namespace yii\web; * @author Qiang Xue * @since 2.0 */ -interface Identity +interface IdentityInterface { /** * Finds an identity by the given ID. * @param string|integer $id the ID to be looked for - * @return Identity the identity object that matches the given ID. + * @return IdentityInterface the identity object that matches the given ID. * Null should be returned if such an identity cannot be found * or the identity is not in an active state (disabled, deleted, etc.) */ diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index c6e1ce3..4fb6257 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -35,6 +35,9 @@ use yii\helpers\Security; * @property boolean $isDelete Whether this is a DELETE request. This property is read-only. * @property boolean $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is * read-only. + * @property boolean $isGet Whether this is a GET request. This property is read-only. + * @property boolean $isHead Whether this is a HEAD request. This property is read-only. + * @property boolean $isOptions Whether this is a OPTIONS request. This property is read-only. * @property boolean $isPatch Whether this is a PATCH request. This property is read-only. * @property boolean $isPost Whether this is a POST request. This property is read-only. * @property boolean $isPut Whether this is a PUT request. This property is read-only. @@ -68,21 +71,31 @@ use yii\helpers\Security; class Request extends \yii\base\Request { /** - * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false. - * By setting this property to true, forms submitted to an Yii Web application must be originated + * The name of the HTTP header for sending CSRF token. + */ + const CSRF_HEADER = 'X-CSRF-Token'; + + /** + * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. + * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated * from the same application. If not, a 400 HTTP exception will be raised. * * Note, this feature requires that the user client accepts cookie. Also, to use this feature, - * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfTokenName]]. + * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfVar]]. * You may use [[\yii\web\Html::beginForm()]] to generate his hidden input. + * + * In JavaScript, you may get the values of [[csrfVar]] and [[csrfToken]] via `yii.getCsrfVar()` and + * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered. + * + * @see Controller::enableCsrfValidation * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ - public $enableCsrfValidation = false; + public $enableCsrfValidation = true; /** - * @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'. - * This property is effectively only when {@link enableCsrfValidation} is true. + * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'. + * This property is used only when [[enableCsrfValidation]] is true. */ - public $csrfTokenName = '_csrf'; + public $csrfVar = '_csrf'; /** * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true. * @see Cookie @@ -110,8 +123,6 @@ class Request extends \yii\base\Request */ public function resolve() { - $this->validateCsrfToken(); - $result = Yii::$app->getUrlManager()->parseRequest($this); if ($result !== false) { list ($route, $params) = $result; @@ -137,6 +148,33 @@ class Request extends \yii\base\Request } /** + * Returns whether this is a GET request. + * @return boolean whether this is a GET request. + */ + public function getIsGet() + { + return $this->getMethod() === 'GET'; + } + + /** + * Returns whether this is an OPTIONS request. + * @return boolean whether this is a OPTIONS request. + */ + public function getIsOptions() + { + return $this->getMethod() === 'OPTIONS'; + } + + /** + * Returns whether this is a HEAD request. + * @return boolean whether this is a HEAD request. + */ + public function getIsHead() + { + return $this->getMethod() === 'HEAD'; + } + + /** * Returns whether this is a POST request. * @return boolean whether this is a POST request. */ @@ -948,7 +986,7 @@ class Request extends \yii\base\Request public function getCsrfToken() { if ($this->_csrfCookie === null) { - $this->_csrfCookie = $this->getCookies()->get($this->csrfTokenName); + $this->_csrfCookie = $this->getCookies()->get($this->csrfVar); if ($this->_csrfCookie === null) { $this->_csrfCookie = $this->createCsrfCookie(); Yii::$app->getResponse()->getCookies()->add($this->_csrfCookie); @@ -959,6 +997,15 @@ class Request extends \yii\base\Request } /** + * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent. + */ + public function getCsrfTokenFromHeader() + { + $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER)); + return isset($_SERVER[$key]) ? $_SERVER[$key] : null; + } + + /** * Creates a cookie with a randomly generated CSRF token. * Initial values specified in [[csrfCookie]] will be applied to the generated cookie. * @return Cookie the generated cookie @@ -967,7 +1014,7 @@ class Request extends \yii\base\Request protected function createCsrfCookie() { $options = $this->csrfCookie; - $options['name'] = $this->csrfTokenName; + $options['name'] = $this->csrfVar; $options['value'] = sha1(uniqid(mt_rand(), true)); return new Cookie($options); } @@ -976,33 +1023,30 @@ class Request extends \yii\base\Request * Performs the CSRF validation. * The method will compare the CSRF token obtained from a cookie and from a POST field. * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. - * @throws HttpException if the validation fails + * This method is called in [[Controller::beforeAction()]]. + * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true. */ public function validateCsrfToken() { - if (!$this->enableCsrfValidation) { - return; - } $method = $this->getMethod(); - if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH' || $method === 'DELETE') { - $cookies = $this->getCookies(); - switch ($method) { - case 'POST': - $token = $this->getPost($this->csrfTokenName); - break; - case 'PUT': - $token = $this->getPut($this->csrfTokenName); - break; - case 'PATCH': - $token = $this->getPatch($this->csrfTokenName); - break; - case 'DELETE': - $token = $this->getDelete($this->csrfTokenName); - } - - if (empty($token) || $cookies->getValue($this->csrfTokenName) !== $token) { - throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); - } + if (!$this->enableCsrfValidation || !in_array($method, array('POST', 'PUT', 'PATCH', 'DELETE'), true)) { + return true; + } + $trueToken = $this->getCookies()->getValue($this->csrfVar); + switch ($method) { + case 'PUT': + $token = $this->getPut($this->csrfVar); + break; + case 'PATCH': + $token = $this->getPatch($this->csrfVar); + break; + case 'DELETE': + $token = $this->getDelete($this->csrfVar); + break; + default: + $token = $this->getPost($this->csrfVar); + break; } + return $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; } } diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index cfbc537..f0d506b 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -112,13 +112,6 @@ class Response extends \yii\base\Response */ public $charset; /** - * @var integer the HTTP status code that should be used when redirecting in AJAX mode. - * This is used by [[redirect()]]. A 2xx code should normally be used for this purpose - * so that the AJAX handler will treat the response as a success. - * @see redirect - */ - public $ajaxRedirectCode = 278; - /** * @var string */ public $statusText; @@ -565,25 +558,37 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. - * This method will send out a "Location" header to achieve the redirection. - * In AJAX mode, this normally will not work as expected unless there are some - * client-side JavaScript code handling the redirection. To help achieve this goal, - * this method will use [[ajaxRedirectCode]] as the HTTP status code when performing - * redirection in AJAX mode. The following JavaScript code may be used on the client - * side to handle the redirection response: + * + * This method adds a "Location" header to the current response. Note that it does not send out + * the header until [[send()]] is called. In a controller action you may use this method as follows: * * ~~~ - * $(document).ajaxSuccess(function(event, xhr, settings) { - * if (xhr.status == 278) { - * window.location = xhr.getResponseHeader('Location'); - * } - * }); + * return Yii::$app->getResponse()->redirect($url); * ~~~ * - * In a controller action you may use this method like this: + * In other places, if you want to send out the "Location" header immediately, you should use + * the following code: * * ~~~ - * return Yii::$app->getResponse()->redirect($url); + * Yii::$app->getResponse()->redirect($url)->send(); + * return; + * ~~~ + * + * In AJAX mode, this normally will not work as expected unless there are some + * client-side JavaScript code handling the redirection. To help achieve this goal, + * this method will send out a "X-Redirect" header instead of "Location". + * + * If you use the "yii" JavaScript module, it will handle the AJAX redirection as + * described above. Otherwise, you should write the following JavaScript code to + * handle the redirection: + * + * ~~~ + * $document.ajaxComplete(function (event, xhr, settings) { + * var url = xhr.getResponseHeader('X-Redirect'); + * if (url) { + * window.location = url; + * } + * }); * ~~~ * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: @@ -597,8 +602,7 @@ class Response extends \yii\base\Response * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302 - * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. + * @param integer $statusCode the HTTP status code. If null, it will use 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the response object itself @@ -613,11 +617,14 @@ class Response extends \yii\base\Response if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $url = Yii::$app->getRequest()->getHostInfo() . $url; } - if ($statusCode === null) { - $statusCode = Yii::$app->getRequest()->getIsAjax() ? $this->ajaxRedirectCode : 302; + + if (Yii::$app->getRequest()->getIsAjax()) { + $this->getHeaders()->set('X-Redirect', $url); + } else { + $this->getHeaders()->set('Location', $url); } - $this->getHeaders()->set('Location', $url); $this->setStatusCode($statusCode); + return $this; } @@ -766,10 +773,10 @@ class Response extends \yii\base\Response if (!is_object($formatter)) { $formatter = Yii::createObject($formatter); } - if ($formatter instanceof ResponseFormatter) { + if ($formatter instanceof ResponseFormatterInterface) { $formatter->format($this); } else { - throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatter interface."); + throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface."); } } else { switch ($this->format) { diff --git a/framework/yii/web/ResponseFormatter.php b/framework/yii/web/ResponseFormatterInterface.php similarity index 73% rename from framework/yii/web/ResponseFormatter.php rename to framework/yii/web/ResponseFormatterInterface.php index dc7c979..689ee1e 100644 --- a/framework/yii/web/ResponseFormatter.php +++ b/framework/yii/web/ResponseFormatterInterface.php @@ -8,12 +8,12 @@ namespace yii\web; /** - * ResponseFormatter specifies the interface needed to format a response before it is sent out. + * ResponseFormatterInterface specifies the interface needed to format a response before it is sent out. * * @author Qiang Xue * @since 2.0 */ -interface ResponseFormatter +interface ResponseFormatterInterface { /** * Formats the specified response. diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index fc9fe16..92ec3ad 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -582,12 +582,22 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * A flash message is available only in the current request and the next request. * @param string $key the key identifying the flash message * @param mixed $defaultValue value to be returned if the flash message does not exist. + * @param boolean $delete whether to delete this flash message right after this method is called. + * If false, the flash message will be automatically deleted after the next request. * @return mixed the flash message */ - public function getFlash($key, $defaultValue = null) + public function getFlash($key, $defaultValue = null, $delete = false) { $counters = $this->get($this->flashVar, array()); - return isset($counters[$key]) ? $this->get($key, $defaultValue) : $defaultValue; + if (isset($counters[$key])) { + $value = $this->get($key, $defaultValue); + if ($delete) { + $this->removeFlash($key); + } + return $value; + } else { + return $defaultValue; + } } /** diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index e02559b..f6a9bc8 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -10,6 +10,7 @@ namespace yii\web; use Yii; use yii\base\Component; use yii\base\InvalidConfigException; +use yii\base\InvalidParamException; /** * User is the class for the "user" application component that manages the user authentication status. @@ -17,12 +18,12 @@ use yii\base\InvalidConfigException; * In particular, [[User::isGuest]] returns a value indicating whether the current user is a guest or not. * Through methods [[login()]] and [[logout()]], you can change the user authentication status. * - * User works with a class implementing the [[Identity]] interface. This class implements + * User works with a class implementing the [[IdentityInterface]]. This class implements * the actual user authentication logic and is often backed by a user database table. * * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. * This property is read-only. - * @property Identity $identity The identity object associated with the currently logged user. Null is + * @property IdentityInterface $identity The identity object associated with the currently logged user. Null is * returned if the user is not logged in (not authenticated). * @property boolean $isGuest Whether the current user is a guest. This property is read-only. * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type @@ -127,7 +128,7 @@ class User extends Component /** * Returns the identity object associated with the currently logged user. - * @return Identity the identity object associated with the currently logged user. + * @return IdentityInterface the identity object associated with the currently logged user. * Null is returned if the user is not logged in (not authenticated). * @see login * @see logout @@ -139,7 +140,7 @@ class User extends Component if ($id === null) { $this->_identity = null; } else { - /** @var $class Identity */ + /** @var $class IdentityInterface */ $class = $this->identityClass; $this->_identity = $class::findIdentity($id); } @@ -155,7 +156,7 @@ class User extends Component * You should normally update the user identity via methods [[login()]], [[logout()]] * or [[switchIdentity()]]. * - * @param Identity $identity the identity object associated with the currently logged user. + * @param IdentityInterface $identity the identity object associated with the currently logged user. */ public function setIdentity($identity) { @@ -170,7 +171,7 @@ class User extends Component * and [[enableAutoLogin]] is true, it will also send out an identity * cookie to support cookie-based login. * - * @param Identity $identity the user identity (which should already be authenticated) + * @param IdentityInterface $identity the user identity (which should already be authenticated) * @param integer $duration number of seconds that the user can remain in logged-in status. * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed. * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported. @@ -199,7 +200,7 @@ class User extends Component $data = json_decode($value, true); if (count($data) === 3 && isset($data[0], $data[1], $data[2])) { list ($id, $authKey, $duration) = $data; - /** @var $class Identity */ + /** @var $class IdentityInterface */ $class = $this->identityClass; $identity = $class::findIdentity($id); if ($identity !== null && $identity->validateAuthKey($authKey)) { @@ -255,20 +256,34 @@ class User extends Component * This property is usually used by the login action. If the login is successful, * the action should read this property and use it to redirect the user browser. * @param string|array $defaultUrl the default return URL in case it was not set previously. - * If this is null, it means [[Application::homeUrl]] will be redirected to. - * Please refer to [[\yii\helpers\Html::url()]] on acceptable URL formats. + * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. + * Please refer to [[setReturnUrl()]] on accepted format of the URL. * @return string the URL that the user should be redirected to after login. * @see loginRequired */ public function getReturnUrl($defaultUrl = null) { $url = Yii::$app->getSession()->get($this->returnUrlVar, $defaultUrl); + if (is_array($url)) { + if (isset($url[0])) { + $route = array_shift($url); + return Yii::$app->getUrlManager()->createUrl($route, $url); + } else { + $url = null; + } + } return $url === null ? Yii::$app->getHomeUrl() : $url; } /** * @param string|array $url the URL that the user should be redirected to after login. - * Please refer to [[\yii\helpers\Html::url()]] on acceptable URL formats. + * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. + * The first element of the array should be the route, and the rest of + * the name-value pairs are GET parameters used to construct the URL. For example, + * + * ~~~ + * array('admin/index', 'ref' => 1) + * ~~~ */ public function setReturnUrl($url) { @@ -303,7 +318,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based * @return boolean whether the user should continue to be logged in */ @@ -322,7 +337,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based */ protected function afterLogin($identity, $cookieBased) @@ -338,7 +353,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @return boolean whether the user should continue to be logged out */ protected function beforeLogout($identity) @@ -355,7 +370,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information */ protected function afterLogout($identity) { @@ -387,9 +402,9 @@ class User extends Component /** * Sends an identity cookie. * This method is used when [[enableAutoLogin]] is true. - * It saves [[id]], [[Identity::getAuthKey()|auth key]], and the duration of cookie-based login + * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login * information in the cookie. - * @param Identity $identity + * @param IdentityInterface $identity * @param integer $duration number of seconds that the user can remain in logged-in status. * @see loginByCookie */ @@ -415,7 +430,7 @@ class User extends Component * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]] * when the current user needs to be associated with the corresponding identity information. * - * @param Identity $identity the identity information to be associated with the current user. + * @param IdentityInterface $identity the identity information to be associated with the current user. * If null, it means switching to be a guest. * @param integer $duration number of seconds that the user can remain in logged-in status. * This parameter is used only when `$identity` is not null. @@ -429,7 +444,7 @@ class User extends Component $this->setIdentity($identity); $session->remove($this->idVar); $session->remove($this->authTimeoutVar); - if ($identity instanceof Identity) { + if ($identity instanceof IdentityInterface) { $session->set($this->idVar, $identity->getId()); if ($this->authTimeout !== null) { $session->set($this->authTimeoutVar, time() + $this->authTimeout); diff --git a/framework/yii/web/UserEvent.php b/framework/yii/web/UserEvent.php index 3e403da..8577ef5 100644 --- a/framework/yii/web/UserEvent.php +++ b/framework/yii/web/UserEvent.php @@ -18,7 +18,7 @@ use yii\base\Event; class UserEvent extends Event { /** - * @var Identity the identity object associated with this event + * @var IdentityInterface the identity object associated with this event */ public $identity; /** diff --git a/framework/yii/web/XmlResponseFormatter.php b/framework/yii/web/XmlResponseFormatter.php index adf8807..737011d 100644 --- a/framework/yii/web/XmlResponseFormatter.php +++ b/framework/yii/web/XmlResponseFormatter.php @@ -20,7 +20,7 @@ use yii\helpers\StringHelper; * @author Qiang Xue * @since 2.0 */ -class XmlResponseFormatter extends Component implements ResponseFormatter +class XmlResponseFormatter extends Component implements ResponseFormatterInterface { /** * @var string the Content-Type header for the response diff --git a/framework/yii/web/YiiAsset.php b/framework/yii/web/YiiAsset.php index 8a4d77a..2ad5384 100644 --- a/framework/yii/web/YiiAsset.php +++ b/framework/yii/web/YiiAsset.php @@ -7,6 +7,9 @@ namespace yii\web; +use Yii; +use yii\base\View; + /** * @author Qiang Xue * @since 2.0 diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index bba1ead..ea8aa1b 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -216,22 +216,19 @@ class ActiveField extends Component /** * Generates a label tag for [[attribute]]. - * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param string $label the label to use. If null, it will be generated via [[Model::getAttributeLabel()]]. + * Note that this will NOT be [[Html::encode()|encoded]]. * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]]. * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * The following options are specially handled: - * - * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. - * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display - * (after encoding). - * * @return ActiveField the field object itself */ - public function label($options = array()) + public function label($label = null, $options = array()) { $options = array_merge($this->labelOptions, $options); + if ($label !== null) { + $options['label'] = $label; + } $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options); return $this; } @@ -374,7 +371,6 @@ class ActiveField extends Component */ public function radio($options = array(), $enclosedByLabel = true) { - $options = array_merge($this->inputOptions, $options); if ($enclosedByLabel) { if (!isset($options['label'])) { $options['label'] = Html::encode($this->model->getAttributeLabel($this->attribute)); diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/BaseListView.php similarity index 96% rename from framework/yii/widgets/ListViewBase.php rename to framework/yii/widgets/BaseListView.php index 6be704d..7268fbc 100644 --- a/framework/yii/widgets/ListViewBase.php +++ b/framework/yii/widgets/BaseListView.php @@ -17,7 +17,7 @@ use yii\helpers\Html; * @author Qiang Xue * @since 2.0 */ -abstract class ListViewBase extends Widget +abstract class BaseListView extends Widget { /** * @var array the HTML attributes for the container tag of the list view. @@ -25,7 +25,7 @@ abstract class ListViewBase extends Widget */ public $options = array(); /** - * @var \yii\data\IDataProvider the data provider for the view. This property is required. + * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. */ public $dataProvider; /** @@ -66,7 +66,7 @@ abstract class ListViewBase extends Widget * - `{sorter}`: the sorter. See [[renderSorter()]]. * - `{pager}`: the pager. See [[renderPager()]]. */ - public $layout = "{summary}\n{sorter}\n{items}\n{pager}"; + public $layout = "{summary}\n{items}\n{pager}"; /** diff --git a/framework/yii/widgets/LinkSorter.php b/framework/yii/widgets/LinkSorter.php index b555475..c8b30e5 100644 --- a/framework/yii/widgets/LinkSorter.php +++ b/framework/yii/widgets/LinkSorter.php @@ -28,6 +28,11 @@ class LinkSorter extends Widget */ public $sort; /** + * @var array list of the attributes that support sorting. If not set, it will be determined + * using [[Sort::attributes]]. + */ + public $attributes; + /** * @var array HTML attributes for the sorter container tag. */ public $options = array('class' => 'sorter'); @@ -58,8 +63,9 @@ class LinkSorter extends Widget */ protected function renderSortLinks() { + $attributes = empty($this->atttributes) ? array_keys($this->sort->attributes) : $this->attributes; $links = array(); - foreach (array_keys($this->sort->attributes) as $name) { + foreach ($attributes as $name) { $links[] = $this->sort->link($name); } return Html::ul($links, array('encode' => false)); diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php index c191389..1d8745d 100644 --- a/framework/yii/widgets/ListView.php +++ b/framework/yii/widgets/ListView.php @@ -16,7 +16,7 @@ use yii\helpers\Html; * @author Qiang Xue * @since 2.0 */ -class ListView extends ListViewBase +class ListView extends BaseListView { /** * @var array the HTML attributes for the container of the rendering result of each data model. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3100413..1f3056e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,7 @@ framework/yii/helpers/ArrayHelper.php framework/yii/helpers/Console.php framework/yii/i18n/GettextFile.php - framework/yii/web/ResponseFormatter.php + framework/yii/web/ResponseFormatterInterface.php framework/yii/base framework/yii/db/mssql framework/yii/bootstrap diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index 561d1ae..bbc0182 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -3,6 +3,15 @@ namespace yiiunit\data\ar; use yii\db\ActiveQuery; +/** + * Class Customer + * + * @property integer $id + * @property string $name + * @property string $email + * @property string $address + * @property integer $status + */ class Customer extends ActiveRecord { const STATUS_ACTIVE = 1; diff --git a/tests/unit/data/ar/Item.php b/tests/unit/data/ar/Item.php index 5d23378..e725be9 100644 --- a/tests/unit/data/ar/Item.php +++ b/tests/unit/data/ar/Item.php @@ -2,6 +2,13 @@ namespace yiiunit\data\ar; +/** + * Class Item + * + * @property integer $id + * @property string $name + * @property integer $category_id + */ class Item extends ActiveRecord { public static function tableName() diff --git a/tests/unit/data/ar/Order.php b/tests/unit/data/ar/Order.php index 119f332..063bb67 100644 --- a/tests/unit/data/ar/Order.php +++ b/tests/unit/data/ar/Order.php @@ -2,6 +2,14 @@ namespace yiiunit\data\ar; +/** + * Class Order + * + * @property integer $id + * @property integer $customer_id + * @property integer $create_time + * @property string $total + */ class Order extends ActiveRecord { public static function tableName() diff --git a/tests/unit/data/ar/OrderItem.php b/tests/unit/data/ar/OrderItem.php index 607133e..297432b 100644 --- a/tests/unit/data/ar/OrderItem.php +++ b/tests/unit/data/ar/OrderItem.php @@ -2,6 +2,14 @@ namespace yiiunit\data\ar; +/** + * Class OrderItem + * + * @property integer $order_id + * @property integer $item_id + * @property integer $quantity + * @property string $subtotal + */ class OrderItem extends ActiveRecord { public static function tableName() diff --git a/tests/unit/data/base/Speaker.php b/tests/unit/data/base/Speaker.php index 5668dad..b0acc6b 100644 --- a/tests/unit/data/base/Speaker.php +++ b/tests/unit/data/base/Speaker.php @@ -17,6 +17,13 @@ class Speaker extends Model protected $protectedProperty; private $_privateProperty; + public static $formName = 'Speaker'; + + public function formName() + { + return static::$formName; + } + public function attributeLabels() { return array( diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 8ead605..fda2be1 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -1,6 +1,12 @@ array( + 'cubrid' => array( + 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', + 'username' => 'dba', + 'password' => '', + 'fixture' => __DIR__ . '/cubrid.sql', + ), 'mysql' => array( 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', 'username' => 'travis', diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql new file mode 100644 index 0000000..3dcfa37 --- /dev/null +++ b/tests/unit/data/cubrid.sql @@ -0,0 +1,108 @@ +/** + * This is the database schema for testing CUBRID support of Yii DAO and Active Record. + * The database setup in config.php is required to perform then relevant tests: + */ + +DROP TABLE IF EXISTS tbl_composite_fk; +DROP TABLE IF EXISTS tbl_order_item; +DROP TABLE IF EXISTS tbl_item; +DROP TABLE IF EXISTS tbl_order; +DROP TABLE IF EXISTS tbl_category; +DROP TABLE IF EXISTS tbl_customer; +DROP TABLE IF EXISTS tbl_type; +DROP TABLE IF EXISTS tbl_constraints; + +CREATE TABLE `tbl_constraints` +( + `id` integer not null, + `field1` varchar(255) +); + + +CREATE TABLE `tbl_customer` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `email` varchar(128) NOT NULL, + `name` varchar(128) NOT NULL, + `address` string, + `status` int (11) DEFAULT 0, + PRIMARY KEY (`id`) +); + +CREATE TABLE `tbl_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(128) NOT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `tbl_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(128) NOT NULL, + `category_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_item_category_id` FOREIGN KEY (`category_id`) REFERENCES `tbl_category` (`id`) ON DELETE CASCADE +); + +CREATE TABLE `tbl_order` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `customer_id` int(11) NOT NULL, + `create_time` int(11) NOT NULL, + `total` decimal(10,0) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_order_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `tbl_customer` (`id`) ON DELETE CASCADE +); + +CREATE TABLE `tbl_order_item` ( + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + `quantity` int(11) NOT NULL, + `subtotal` decimal(10,0) NOT NULL, + PRIMARY KEY (`order_id`,`item_id`), + CONSTRAINT `FK_order_item_order_id` FOREIGN KEY (`order_id`) REFERENCES `tbl_order` (`id`) ON DELETE CASCADE, + CONSTRAINT `FK_order_item_item_id` FOREIGN KEY (`item_id`) REFERENCES `tbl_item` (`id`) ON DELETE CASCADE +); + +CREATE TABLE `tbl_type` ( + `int_col` int(11) NOT NULL, + `int_col2` int(11) DEFAULT '1', + `char_col` char(100) NOT NULL, + `char_col2` varchar(100) DEFAULT 'something', + `char_col3` string, + `enum_col` enum('a', 'b'), + `float_col` double NOT NULL, + `float_col2` double DEFAULT '1.23', + `blob_col` blob, + `numeric_col` decimal(5,2) DEFAULT '33.22', + `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00' +); + +CREATE TABLE `tbl_composite_fk` ( + `id` int(11) NOT NULL, + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE +); + +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2); + +INSERT INTO tbl_category (name) VALUES ('Books'); +INSERT INTO tbl_category (name) VALUES ('Movies'); + +INSERT INTO tbl_item (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Ice Age', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Toy Story', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2); + +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0); + +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql index 2e9458e..133348d 100644 --- a/tests/unit/data/mysql.sql +++ b/tests/unit/data/mysql.sql @@ -3,6 +3,7 @@ * The database setup in config.php is required to perform then relevant tests: */ +DROP TABLE IF EXISTS tbl_composite_fk CASCADE; DROP TABLE IF EXISTS tbl_order_item CASCADE; DROP TABLE IF EXISTS tbl_item CASCADE; DROP TABLE IF EXISTS tbl_order CASCADE; @@ -62,6 +63,14 @@ CREATE TABLE `tbl_order_item` ( CONSTRAINT `FK_order_item_item_id` FOREIGN KEY (`item_id`) REFERENCES `tbl_item` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `tbl_composite_fk` ( + `id` int(11) NOT NULL, + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE +); + CREATE TABLE `tbl_type` ( `int_col` int(11) NOT NULL, `int_col2` int(11) DEFAULT '1', diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql index f75bfa6..f031ac3 100644 --- a/tests/unit/data/sqlite.sql +++ b/tests/unit/data/sqlite.sql @@ -3,6 +3,7 @@ * The database setup in config.php is required to perform then relevant tests: */ +DROP TABLE IF EXISTS tbl_composite_fk; DROP TABLE IF EXISTS tbl_order_item; DROP TABLE IF EXISTS tbl_item; DROP TABLE IF EXISTS tbl_order; @@ -48,6 +49,14 @@ CREATE TABLE tbl_order_item ( PRIMARY KEY (order_id, item_id) ); +CREATE TABLE `tbl_composite_fk` ( + `id` int(11) NOT NULL, + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE +); + CREATE TABLE tbl_type ( int_col INTEGER NOT NULL, int_col2 INTEGER DEFAULT '1', diff --git a/tests/unit/data/travis/README.md b/tests/unit/data/travis/README.md new file mode 100644 index 0000000..c86497e --- /dev/null +++ b/tests/unit/data/travis/README.md @@ -0,0 +1,12 @@ +This directory contains scripts for automated test runs via the [Travis CI](http://travis-ci.org) build service. They are used for the preparation of worker instances by setting up needed extensions and configuring database access. + +These scripts might be used to configure your own system for test runs. But since their primary purpose remains to support Travis in running the test cases, you would be best advised to stick to the setup notes in the tests themselves. + +The scripts are: + + - [`apc-setup.sh`](apc-setup.sh) + Installs and configures the [apc pecl extension](http://pecl.php.net/package/apc) + - [`memcache-setup.sh`](memcache-setup.sh) + Compiles and installs the [memcache pecl extension](http://pecl.php.net/package/memcache) + - [`cubrid-setup.sh`](cubrid-setup.sh) + Prepares the [CUBRID](http://www.cubrid.org/) server instance by installing the server and PHP PDO driver diff --git a/tests/unit/data/travis/apc-setup.sh b/tests/unit/data/travis/apc-setup.sh new file mode 100755 index 0000000..e5e8734 --- /dev/null +++ b/tests/unit/data/travis/apc-setup.sh @@ -0,0 +1,2 @@ +echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini +echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/tests/unit/data/travis/cubrid-setup.sh b/tests/unit/data/travis/cubrid-setup.sh new file mode 100755 index 0000000..af007ff --- /dev/null +++ b/tests/unit/data/travis/cubrid-setup.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# install CUBRID DBMS https://github.com/CUBRID/node-cubrid/blob/056e734ce36bb3fd25f100c983beb3947e899c1c/.travis.yml + +sudo hostname localhost +# Update OS before installing prerequisites. +sudo apt-get update +# Install Chef Solo prerequisites. +sudo apt-get install ruby ruby-dev libopenssl-ruby rdoc ri irb build-essential ssl-cert +# Install Chef Solo. +# Chef Solo 11.4.4 is broken, so install a previous version. +# The bug is planned to be fixed in 11.4.5 which haven't been released yet. +sudo gem install --version '<11.4.4' chef --no-rdoc --no-ri +# Make sure the target directory for cookbooks exists. +mkdir -p /tmp/chef-solo +# Prepare a file with runlist for Chef Solo. +echo '{"cubrid":{"version":"'$CUBRID_VERSION'"},"run_list":["cubrid::demodb"]}' > cubrid_chef.json +# Install CUBRID via Chef Solo. Download all cookbooks from a remote URL. +sudo chef-solo -c tests/unit/data/travis/cubrid-solo.rb -j cubrid_chef.json -r http://sourceforge.net/projects/cubrid/files/CUBRID-Demo-Virtual-Machines/Vagrant/chef-cookbooks.tar.gz/download + + +install_pdo_cubrid() { + wget "http://pecl.php.net/get/PDO_CUBRID-9.1.0.0003.tgz" && + tar -zxf "PDO_CUBRID-9.1.0.0003.tgz" && + sh -c "cd PDO_CUBRID-9.1.0.0003 && phpize && ./configure && make && sudo make install" + + echo "extension=pdo_cubrid.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + + return $? +} + +install_pdo_cubrid > ~/pdo_cubrid.log || ( echo "=== PDO CUBRID BUILD FAILED ==="; cat ~/pdo_cubrid.log ) \ No newline at end of file diff --git a/tests/unit/data/travis/cubrid-solo.rb b/tests/unit/data/travis/cubrid-solo.rb new file mode 100644 index 0000000..f5f0004 --- /dev/null +++ b/tests/unit/data/travis/cubrid-solo.rb @@ -0,0 +1,5 @@ +file_cache_path "/tmp/chef-solo" +data_bag_path "/tmp/chef-solo/data_bags" +encrypted_data_bag_secret "/tmp/chef-solo/data_bag_key" +cookbook_path [ "/tmp/chef-solo/cookbooks" ] +role_path "/tmp/chef-solo/roles" \ No newline at end of file diff --git a/tests/unit/data/travis/memcache-setup.sh b/tests/unit/data/travis/memcache-setup.sh new file mode 100755 index 0000000..6b623d6 --- /dev/null +++ b/tests/unit/data/travis/memcache-setup.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini +echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/YiiBaseTest.php index e256b2b..72b5f24 100644 --- a/tests/unit/framework/YiiBaseTest.php +++ b/tests/unit/framework/YiiBaseTest.php @@ -6,6 +6,7 @@ use yiiunit\TestCase; /** * YiiBaseTest + * @group base */ class YiiBaseTest extends TestCase { diff --git a/tests/unit/framework/base/BehaviorTest.php b/tests/unit/framework/base/BehaviorTest.php index e596ea8..b6eda09 100644 --- a/tests/unit/framework/base/BehaviorTest.php +++ b/tests/unit/framework/base/BehaviorTest.php @@ -46,6 +46,9 @@ class BarBehavior extends Behavior } } +/** + * @group base + */ class BehaviorTest extends TestCase { protected function setUp() diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php index 79fb7db..98786e2 100644 --- a/tests/unit/framework/base/ComponentTest.php +++ b/tests/unit/framework/base/ComponentTest.php @@ -17,6 +17,9 @@ function globalEventHandler2($event) $event->handled = true; } +/** + * @group base + */ class ComponentTest extends TestCase { /** diff --git a/tests/unit/framework/base/ExceptionTest.php b/tests/unit/framework/base/ExceptionTest.php new file mode 100644 index 0000000..af4293a --- /dev/null +++ b/tests/unit/framework/base/ExceptionTest.php @@ -0,0 +1,23 @@ +toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + + $e = new InvalidCallException('bar', 0 ,new UserException('foo')); + $array = $e->toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + } +} diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 01dd682..ae71a5c 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -10,9 +10,7 @@ use yii\base\Formatter; use yiiunit\TestCase; /** - * - * @author Qiang Xue - * @since 2.0 + * @group base */ class FormatterTest extends TestCase { diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index e4d8976..d500933 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -9,7 +9,7 @@ use yiiunit\data\base\Singer; use yiiunit\data\base\InvalidRulesModel; /** - * ModelTest + * @group base */ class ModelTest extends TestCase { @@ -75,6 +75,32 @@ class ModelTest extends TestCase $this->assertEquals('Qiang', $speaker->firstName); } + public function testLoad() + { + $singer = new Singer(); + $this->assertEquals('Singer', $singer->formName()); + + $post = array('firstName' => 'Qiang'); + + Speaker::$formName = ''; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertTrue($model->load($post)); + $this->assertEquals('Qiang', $model->firstName); + + Speaker::$formName = 'Speaker'; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertTrue($model->load(array('Speaker' => $post))); + $this->assertEquals('Qiang', $model->firstName); + + Speaker::$formName = 'Speaker'; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertFalse($model->load(array('Example' => array()))); + $this->assertEquals('', $model->firstName); + } + public function testActiveAttributes() { // by default mass assignment doesn't work at all diff --git a/tests/unit/framework/base/ObjectTest.php b/tests/unit/framework/base/ObjectTest.php index 933b721..0bd9b1d 100644 --- a/tests/unit/framework/base/ObjectTest.php +++ b/tests/unit/framework/base/ObjectTest.php @@ -5,7 +5,7 @@ use yii\base\Object; use yiiunit\TestCase; /** - * ObjectTest + * @group base */ class ObjectTest extends TestCase { @@ -134,11 +134,6 @@ class ObjectTest extends TestCase $this->assertEquals('new text', $this->object->object->text); } - public function testAnonymousFunctionProperty() - { - $this->assertEquals(2, $this->object->execute(1)); - } - public function testConstruct() { $object = new NewObject(array('text' => 'test text')); diff --git a/tests/unit/framework/behaviors/AutoTimestampTest.php b/tests/unit/framework/behaviors/AutoTimestampTest.php index 0e17a39..c26d912 100644 --- a/tests/unit/framework/behaviors/AutoTimestampTest.php +++ b/tests/unit/framework/behaviors/AutoTimestampTest.php @@ -11,6 +11,8 @@ use yii\behaviors\AutoTimestamp; /** * Unit test for [[\yii\behaviors\AutoTimestamp]]. * @see AutoTimestamp + * + * @group behaviors */ class AutoTimestampTest extends TestCase { diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php index 1d07498..adda151 100644 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ b/tests/unit/framework/caching/ApcCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\ApcCache; /** * Class for testing APC cache backend + * @group apc + * @group caching */ class ApcCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 94894a3..480941f 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -13,6 +13,7 @@ function time() namespace yiiunit\framework\caching; +use yii\helpers\StringHelper; use yiiunit\TestCase; use yii\caching\Cache; @@ -147,7 +148,11 @@ abstract class CacheTestCase extends TestCase sleep(1); $this->assertEquals('expire_test', $cache->get('expire_test')); // wait a bit more than 2 sec to avoid random test failure - usleep(2500000); + if (isset($_ENV['TRAVIS']) && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { + sleep(3); // usleep with 2,5 seconds does not work well on travis and memcache + } else { + usleep(2500000); + } $this->assertFalse($cache->get('expire_test')); } diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php index 969b034..1e94d58 100644 --- a/tests/unit/framework/caching/DbCacheTest.php +++ b/tests/unit/framework/caching/DbCacheTest.php @@ -6,6 +6,8 @@ use yii\caching\DbCache; /** * Class for testing file cache backend + * @group db + * @group caching */ class DbCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php index 0bdbc86..263ecb4 100644 --- a/tests/unit/framework/caching/FileCacheTest.php +++ b/tests/unit/framework/caching/FileCacheTest.php @@ -5,6 +5,7 @@ use yii\caching\FileCache; /** * Class for testing file cache backend + * @group caching */ class FileCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index d54d807..32374b5 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\MemCache; /** * Class for testing memcache cache backend + * @group memcache + * @group caching */ class MemCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index faf0d56..f39ed17 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -5,6 +5,8 @@ use yii\caching\MemCache; /** * Class for testing memcached cache backend + * @group memcached + * @group caching */ class MemCachedTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/RedisCacheTest.php b/tests/unit/framework/caching/RedisCacheTest.php index 0924d0f..d02773d 100644 --- a/tests/unit/framework/caching/RedisCacheTest.php +++ b/tests/unit/framework/caching/RedisCacheTest.php @@ -6,6 +6,8 @@ use yiiunit\TestCase; /** * Class for testing redis cache backend + * @group redis + * @group caching */ class RedisCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/WinCacheTest.php b/tests/unit/framework/caching/WinCacheTest.php index b6f2425..1bce102 100644 --- a/tests/unit/framework/caching/WinCacheTest.php +++ b/tests/unit/framework/caching/WinCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\WinCache; /** * Class for testing wincache backend + * @group wincache + * @group caching */ class WinCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/XCacheTest.php b/tests/unit/framework/caching/XCacheTest.php index 7ee0a0e..989765d 100644 --- a/tests/unit/framework/caching/XCacheTest.php +++ b/tests/unit/framework/caching/XCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\XCache; /** * Class for testing xcache backend + * @group xcache + * @group caching */ class XCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/ZendDataCacheTest.php b/tests/unit/framework/caching/ZendDataCacheTest.php index 2d68bfd..96354cd 100644 --- a/tests/unit/framework/caching/ZendDataCacheTest.php +++ b/tests/unit/framework/caching/ZendDataCacheTest.php @@ -6,6 +6,8 @@ use yii\caching\ZendDataCache; /** * Class for testing Zend cache backend + * @group zenddata + * @group caching */ class ZendDataCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php index aaf5ca9..67dbdaa 100644 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ b/tests/unit/framework/console/controllers/AssetControllerTest.php @@ -6,6 +6,8 @@ use yii\console\controllers\AssetController; /** * Unit test for [[\yii\console\controllers\AssetController]]. * @see AssetController + * + * @group console */ class AssetControllerTest extends TestCase { diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php index cdd38b8..b0c697c 100644 --- a/tests/unit/framework/console/controllers/MessageControllerTest.php +++ b/tests/unit/framework/console/controllers/MessageControllerTest.php @@ -6,6 +6,8 @@ use yii\console\controllers\MessageController; /** * Unit test for [[\yii\console\controllers\MessageController]]. * @see MessageController + * + * @group console */ class MessageControllerTest extends TestCase { diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 2699a52..79c0a39 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -16,6 +16,8 @@ use yiiunit\data\ar\Order; /** * @author Qiang Xue * @since 2.0 + * + * @group data */ class ActiveDataProviderTest extends DatabaseTestCase { @@ -83,4 +85,21 @@ class ActiveDataProviderTest extends DatabaseTestCase $provider->refresh(); $this->assertEquals(2, count($provider->getModels())); } + + public function testPaginationBeforeModels() + { + $query = new Query; + $provider = new ActiveDataProvider(array( + 'db' => $this->getConnection(), + 'query' => $query->from('tbl_order')->orderBy('id'), + )); + $pagination = $provider->getPagination(); + $this->assertEquals(1, $pagination->getPageCount()); + $this->assertCount(3, $provider->getModels()); + + $provider->getPagination()->pageSize = 2; + $this->assertEquals(3, count($provider->getModels())); + $provider->refresh(); + $this->assertEquals(2, count($provider->getModels())); + } } diff --git a/tests/unit/framework/data/SortTest.php b/tests/unit/framework/data/SortTest.php index 9891ad1..c4cc6aa 100644 --- a/tests/unit/framework/data/SortTest.php +++ b/tests/unit/framework/data/SortTest.php @@ -14,6 +14,8 @@ use yii\data\Sort; /** * @author Qiang Xue * @since 2.0 + * + * @group data */ class SortTest extends TestCase { diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index 7df4159..2f9b345 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -9,6 +9,10 @@ use yiiunit\data\ar\OrderItem; use yiiunit\data\ar\Order; use yiiunit\data\ar\Item; +/** + * @group db + * @group mysql + */ class ActiveRecordTest extends DatabaseTestCase { protected function setUp() diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php index 52fd046..d9eb0e7 100644 --- a/tests/unit/framework/db/CommandTest.php +++ b/tests/unit/framework/db/CommandTest.php @@ -7,14 +7,12 @@ use yii\db\Command; use yii\db\Query; use yii\db\DataReader; +/** + * @group db + * @group mysql + */ class CommandTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - public function testConstruct() { $db = $this->getConnection(false); @@ -221,6 +219,29 @@ class CommandTest extends DatabaseTestCase $this->assertTrue(is_array($result) && isset($result[0])); } + // getPDOType is currently private +// public function testGetPDOType() +// { +// $values = array( +// array(null, \PDO::PARAM_NULL), +// array('', \PDO::PARAM_STR), +// array('hello', \PDO::PARAM_STR), +// array(0, \PDO::PARAM_INT), +// array(1, \PDO::PARAM_INT), +// array(1337, \PDO::PARAM_INT), +// array(true, \PDO::PARAM_BOOL), +// array(false, \PDO::PARAM_BOOL), +// array($fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB), +// ); +// +// $command = $this->getConnection()->createCommand(); +// +// foreach($values as $value) { +// $this->assertEquals($value[1], $command->getPdoType($value[0])); +// } +// fclose($fp); +// } + public function testInsert() { } diff --git a/tests/unit/framework/db/ConnectionTest.php b/tests/unit/framework/db/ConnectionTest.php index 42b470b..04c5d53 100644 --- a/tests/unit/framework/db/ConnectionTest.php +++ b/tests/unit/framework/db/ConnectionTest.php @@ -4,14 +4,12 @@ namespace yiiunit\framework\db; use yii\db\Connection; +/** + * @group db + * @group mysql + */ class ConnectionTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - public function testConstruct() { $connection = $this->getConnection(false); diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index 1fd2d56..d8d2916 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -1,18 +1,21 @@ mockApplication(); $databases = $this->getParam('databases'); $this->database = $databases[$this->driverName]; $pdo_database = 'pdo_'.$this->driverName; @@ -20,6 +23,15 @@ abstract class DatabaseTestCase extends TestCase if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { $this->markTestSkipped('pdo and pdo_'.$pdo_database.' extension are required.'); } + $this->mockApplication(); + } + + protected function tearDown() + { + if ($this->db) { + $this->db->close(); + } + $this->destroyApplication(); } /** diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index e08ac87..d43c901 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -8,15 +8,14 @@ use yii\db\mysql\QueryBuilder as MysqlQueryBuilder; use yii\db\sqlite\QueryBuilder as SqliteQueryBuilder; use yii\db\mssql\QueryBuilder as MssqlQueryBuilder; use yii\db\pgsql\QueryBuilder as PgsqlQueryBuilder; +use yii\db\cubrid\QueryBuilder as CubridQueryBuilder; +/** + * @group db + * @group mysql + */ class QueryBuilderTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - /** * @throws \Exception * @return QueryBuilder @@ -32,6 +31,8 @@ class QueryBuilderTest extends DatabaseTestCase return new MssqlQueryBuilder($this->getConnection()); case 'pgsql': return new PgsqlQueryBuilder($this->getConnection()); + case 'cubrid': + return new CubridQueryBuilder($this->getConnection()); } throw new \Exception('Test is not implemented for ' . $this->driverName); } diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index 5d1f1a9..a275afd 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -7,14 +7,12 @@ use yii\db\Command; use yii\db\Query; use yii\db\DataReader; +/** + * @group db + * @group mysql + */ class QueryTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - public function testSelect() { // default diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php new file mode 100644 index 0000000..2a3015d --- /dev/null +++ b/tests/unit/framework/db/SchemaTest.php @@ -0,0 +1,70 @@ +getConnection()->schema; + + $tables = $schema->getTableNames(); + $this->assertTrue(in_array('tbl_customer', $tables)); + $this->assertTrue(in_array('tbl_category', $tables)); + $this->assertTrue(in_array('tbl_item', $tables)); + $this->assertTrue(in_array('tbl_order', $tables)); + $this->assertTrue(in_array('tbl_order_item', $tables)); + $this->assertTrue(in_array('tbl_type', $tables)); + } + + public function testGetTableSchemas() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $tables = $schema->getTableSchemas(); + $this->assertEquals(count($schema->getTableNames()), count($tables)); + foreach($tables as $table) { + $this->assertInstanceOf('yii\db\TableSchema', $table); + } + } + + public function testGetNonExistingTableSchema() + { + $this->assertNull($this->getConnection()->schema->getTableSchema('nonexisting_table')); + } + + public function testSchemaCache() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $schema->db->enableSchemaCache = true; + $schema->db->schemaCache = new FileCache(); + $noCacheTable = $schema->getTableSchema('tbl_type', true); + $cachedTable = $schema->getTableSchema('tbl_type', true); + $this->assertEquals($noCacheTable, $cachedTable); + } + + public function testCompositeFk() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $table = $schema->getTableSchema('tbl_composite_fk'); + + $this->assertCount(1, $table->foreignKeys); + $this->assertTrue(isset($table->foreignKeys[0])); + $this->assertEquals('tbl_order_item', $table->foreignKeys[0][0]); + $this->assertEquals('order_id', $table->foreignKeys[0]['order_id']); + $this->assertEquals('item_id', $table->foreignKeys[0]['item_id']); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php new file mode 100644 index 0000000..2d2db15 --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -0,0 +1,36 @@ +name = 'boolean customer'; + $customer->email = 'mail@example.com'; + $customer->status = true; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(1, $customer->status); + + $customer->status = false; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(0, $customer->status); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php new file mode 100644 index 0000000..45d3c1c --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -0,0 +1,79 @@ +getConnection(); + + // bindParam + $sql = 'INSERT INTO tbl_customer(email, name, address) VALUES (:email, :name, :address)'; + $command = $db->createCommand($sql); + $email = 'user4@example.com'; + $name = 'user4'; + $address = 'address4'; + $command->bindParam(':email', $email); + $command->bindParam(':name', $name); + $command->bindParam(':address', $address); + $command->execute(); + + $sql = 'SELECT name FROM tbl_customer WHERE email=:email'; + $command = $db->createCommand($sql); + $command->bindParam(':email', $email); + $this->assertEquals($name, $command->queryScalar()); + + $sql = "INSERT INTO tbl_type (int_col, char_col, char_col2, enum_col, float_col, blob_col, numeric_col) VALUES (:int_col, '', :char_col, :enum_col, :float_col, CHAR_TO_BLOB(:blob_col), :numeric_col)"; + $command = $db->createCommand($sql); + $intCol = 123; + $charCol = 'abc'; + $enumCol = 'a'; + $floatCol = 1.23; + $blobCol = "\x10\x11\x12"; + $numericCol = '1.23'; + $command->bindParam(':int_col', $intCol); + $command->bindParam(':char_col', $charCol); + $command->bindParam(':enum_col', $enumCol); + $command->bindParam(':float_col', $floatCol); + $command->bindParam(':blob_col', $blobCol); + $command->bindParam(':numeric_col', $numericCol); + $this->assertEquals(1, $command->execute()); + + $sql = 'SELECT * FROM tbl_type'; + $row = $db->createCommand($sql)->queryOne(); + $this->assertEquals($intCol, $row['int_col']); + $this->assertEquals($enumCol, $row['enum_col']); + $this->assertEquals($charCol, $row['char_col2']); + $this->assertEquals($floatCol, $row['float_col']); + $this->assertEquals($blobCol, fread($row['blob_col'], 3)); + $this->assertEquals($numericCol, $row['numeric_col']); + + // bindValue + $sql = 'INSERT INTO tbl_customer(email, name, address) VALUES (:email, \'user5\', \'address5\')'; + $command = $db->createCommand($sql); + $command->bindValue(':email', 'user5@example.com'); + $command->execute(); + + $sql = 'SELECT email FROM tbl_customer WHERE name=:name'; + $command = $db->createCommand($sql); + $command->bindValue(':name', 'user5'); + $this->assertEquals('user5@example.com', $command->queryScalar()); + } + + public function testAutoQuoting() + { + $db = $this->getConnection(false); + + $sql = 'SELECT [[id]], [[t.name]] FROM {{tbl_customer}} t'; + $command = $db->createCommand($sql); + $this->assertEquals('SELECT "id", "t"."name" FROM "tbl_customer" t', $command->sql); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridConnectionTest.php b/tests/unit/framework/db/cubrid/CubridConnectionTest.php new file mode 100644 index 0000000..4cd6e20 --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridConnectionTest.php @@ -0,0 +1,44 @@ +getConnection(false); + $this->assertEquals(123, $connection->quoteValue(123)); + $this->assertEquals("'string'", $connection->quoteValue('string')); + $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting")); + } + + public function testQuoteTableName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"table"', $connection->quoteTableName('table')); + $this->assertEquals('"table"', $connection->quoteTableName('"table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"')); + $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); + $this->assertEquals('(table)', $connection->quoteTableName('(table)')); + } + + public function testQuoteColumnName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"column"', $connection->quoteColumnName('column')); + $this->assertEquals('"column"', $connection->quoteColumnName('"column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); + $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); + $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); + $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php new file mode 100644 index 0000000..107b73b --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php @@ -0,0 +1,84 @@ + 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_STRING, 'varchar(255)'), + array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), + array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), + array(Schema::TYPE_TEXT, 'varchar'), + array(Schema::TYPE_TEXT . '(255)', 'varchar'), + array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . ' NOT NULL', 'varchar NOT NULL'), + array(Schema::TYPE_TEXT . '(255) NOT NULL', 'varchar NOT NULL'), + array(Schema::TYPE_SMALLINT, 'smallint'), + array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), + array(Schema::TYPE_INTEGER, 'int'), + array(Schema::TYPE_INTEGER . '(8)', 'int'), + array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . ' NOT NULL', 'int NOT NULL'), + array(Schema::TYPE_BIGINT, 'bigint'), + array(Schema::TYPE_BIGINT . '(8)', 'bigint'), + array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), + array(Schema::TYPE_FLOAT, 'float(7)'), + array(Schema::TYPE_FLOAT . '(16)', 'float(16)'), + array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float(7) CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . '(16) CHECK (value > 5.6)', 'float(16) CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . ' NOT NULL', 'float(7) NOT NULL'), + array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), + array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), + array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), + array(Schema::TYPE_DATETIME, 'datetime'), + array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), + array(Schema::TYPE_TIMESTAMP, 'timestamp'), + array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), + array(Schema::TYPE_TIME, 'time'), + array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), + array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), + array(Schema::TYPE_DATE, 'date'), + array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), + array(Schema::TYPE_BINARY, 'blob'), + array(Schema::TYPE_BOOLEAN, 'smallint'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'smallint NOT NULL DEFAULT 1'), + array(Schema::TYPE_MONEY, 'decimal(19,4)'), + array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), + array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), + ); + } + +} diff --git a/tests/unit/framework/db/cubrid/CubridQueryTest.php b/tests/unit/framework/db/cubrid/CubridQueryTest.php new file mode 100644 index 0000000..b7c9009 --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridQueryTest.php @@ -0,0 +1,13 @@ +driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; } diff --git a/tests/unit/framework/db/mssql/MssqlCommandTest.php b/tests/unit/framework/db/mssql/MssqlCommandTest.php index e27fcee..86f7f45 100644 --- a/tests/unit/framework/db/mssql/MssqlCommandTest.php +++ b/tests/unit/framework/db/mssql/MssqlCommandTest.php @@ -4,13 +4,13 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\CommandTest; +/** + * @group db + * @group mssql + */ class MssqlCommandTest extends CommandTest { - public function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; public function testAutoQuoting() { diff --git a/tests/unit/framework/db/mssql/MssqlConnectionTest.php b/tests/unit/framework/db/mssql/MssqlConnectionTest.php index 7bbaae7..6531f83 100644 --- a/tests/unit/framework/db/mssql/MssqlConnectionTest.php +++ b/tests/unit/framework/db/mssql/MssqlConnectionTest.php @@ -4,13 +4,13 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group mssql + */ class MssqlConnectionTest extends ConnectionTest { - public function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; public function testQuoteValue() { diff --git a/tests/unit/framework/db/mssql/MssqlQueryTest.php b/tests/unit/framework/db/mssql/MssqlQueryTest.php index 4f37c14..a2cb019 100644 --- a/tests/unit/framework/db/mssql/MssqlQueryTest.php +++ b/tests/unit/framework/db/mssql/MssqlQueryTest.php @@ -4,11 +4,11 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\QueryTest; +/** + * @group db + * @group mssql + */ class MssqlQueryTest extends QueryTest { - public function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index 2836223..1fffad7 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -4,11 +4,11 @@ namespace yiiunit\framework\db\pgsql; use yiiunit\framework\db\ActiveRecordTest; +/** + * @group db + * @group pgsql + */ class PostgreSQLActiveRecordTest extends ActiveRecordTest { - protected function setUp() - { - $this->driverName = 'pgsql'; - parent::setUp(); - } + protected $driverName = 'pgsql'; } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php index 7ac65c5..26ac0e0 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -3,13 +3,13 @@ namespace yiiunit\framework\db\pgsql; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group pgsql + */ class PostgreSQLConnectionTest extends ConnectionTest { - public function setUp() - { - $this->driverName = 'pgsql'; - parent::setUp(); - } + protected $driverName = 'pgsql'; public function testConnection() { diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php index 7c31190..3ef329e 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php @@ -2,22 +2,24 @@ namespace yiiunit\framework\db\pgsql; -use yii\base\NotSupportedException; use yii\db\pgsql\Schema; use yiiunit\framework\db\QueryBuilderTest; +/** + * @group db + * @group pgsql + */ class PostgreSQLQueryBuilderTest extends QueryBuilderTest { - public $driverName = 'pgsql'; public function columnTypes() { return array( - array(Schema::TYPE_PK, 'serial not null primary key'), - array(Schema::TYPE_PK . '(8)', 'serial not null primary key'), - array(Schema::TYPE_PK . ' CHECK (value > 5)', 'serial not null primary key CHECK (value > 5)'), - array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial not null primary key CHECK (value > 5)'), + array(Schema::TYPE_PK, 'serial NOT NULL PRIMARY KEY'), + array(Schema::TYPE_PK . '(8)', 'serial NOT NULL PRIMARY KEY'), + array(Schema::TYPE_PK . ' CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'), array(Schema::TYPE_STRING, 'varchar(255)'), array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), diff --git a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php index 2e76162..a689e5d 100644 --- a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php +++ b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php @@ -3,11 +3,11 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\ActiveRecordTest; +/** + * @group db + * @group sqlite + */ class SqliteActiveRecordTest extends ActiveRecordTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; } diff --git a/tests/unit/framework/db/sqlite/SqliteCommandTest.php b/tests/unit/framework/db/sqlite/SqliteCommandTest.php index e330c7e..1f9ddc2 100644 --- a/tests/unit/framework/db/sqlite/SqliteCommandTest.php +++ b/tests/unit/framework/db/sqlite/SqliteCommandTest.php @@ -3,13 +3,13 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\CommandTest; +/** + * @group db + * @group sqlite + */ class SqliteCommandTest extends CommandTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; public function testAutoQuoting() { diff --git a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php index 2065200..e1a2961 100644 --- a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php +++ b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php @@ -3,13 +3,13 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group sqlite + */ class SqliteConnectionTest extends ConnectionTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; public function testConstruct() { diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php index 3291187..b20acad 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -2,13 +2,16 @@ namespace yiiunit\framework\db\sqlite; -use yii\base\NotSupportedException; use yii\db\sqlite\Schema; use yiiunit\framework\db\QueryBuilderTest; +/** + * @group db + * @group sqlite + */ class SqliteQueryBuilderTest extends QueryBuilderTest { - public $driverName = 'sqlite'; + protected $driverName = 'sqlite'; public function columnTypes() { diff --git a/tests/unit/framework/db/sqlite/SqliteQueryTest.php b/tests/unit/framework/db/sqlite/SqliteQueryTest.php index ebbb1bb..f1db36b 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryTest.php @@ -3,11 +3,11 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\QueryTest; +/** + * @group db + * @group sqlite + */ class SqliteQueryTest extends QueryTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; } diff --git a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php new file mode 100644 index 0000000..260bb4c --- /dev/null +++ b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php @@ -0,0 +1,13 @@ + array( 'class' => 'yii\web\Request', 'url' => '/test', + 'enableCsrfValidation' => false, ), 'response' => array( 'class' => 'yii\web\Response', diff --git a/tests/unit/framework/helpers/InflectorTest.php b/tests/unit/framework/helpers/InflectorTest.php index 732c10d..de7fe01 100644 --- a/tests/unit/framework/helpers/InflectorTest.php +++ b/tests/unit/framework/helpers/InflectorTest.php @@ -6,6 +6,9 @@ use Yii; use yii\helpers\Inflector; use yiiunit\TestCase; +/** + * @group helpers + */ class InflectorTest extends TestCase { public function testPluralize() diff --git a/tests/unit/framework/helpers/JsonTest.php b/tests/unit/framework/helpers/JsonTest.php index 3734744..df2ca5f 100644 --- a/tests/unit/framework/helpers/JsonTest.php +++ b/tests/unit/framework/helpers/JsonTest.php @@ -7,6 +7,9 @@ use yii\helpers\Json; use yii\test\TestCase; use yii\web\JsExpression; +/** + * @group helpers + */ class JsonTest extends TestCase { public function testEncode() diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/unit/framework/helpers/StringHelperTest.php index fe6a96c..8af731d 100644 --- a/tests/unit/framework/helpers/StringHelperTest.php +++ b/tests/unit/framework/helpers/StringHelperTest.php @@ -6,6 +6,7 @@ use yii\test\TestCase; /** * StringHelperTest + * @group helpers */ class StringHelperTest extends TestCase { diff --git a/tests/unit/framework/helpers/VarDumperTest.php b/tests/unit/framework/helpers/VarDumperTest.php index 2b40b63..d41a69d 100644 --- a/tests/unit/framework/helpers/VarDumperTest.php +++ b/tests/unit/framework/helpers/VarDumperTest.php @@ -4,6 +4,9 @@ namespace yiiunit\framework\helpers; use \yii\helpers\VarDumper; use yii\test\TestCase; +/** + * @group helpers + */ class VarDumperTest extends TestCase { public function testDumpObject() diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index c13fff3..6966853 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -13,6 +13,7 @@ use yiiunit\TestCase; /** * @author Qiang Xue * @since 2.0 + * @group i18n */ class FormatterTest extends TestCase { diff --git a/tests/unit/framework/i18n/GettextMessageSourceTest.php b/tests/unit/framework/i18n/GettextMessageSourceTest.php index 7b499f4..d039629 100644 --- a/tests/unit/framework/i18n/GettextMessageSourceTest.php +++ b/tests/unit/framework/i18n/GettextMessageSourceTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\i18n; use yii\i18n\GettextMessageSource; use yiiunit\TestCase; +/** + * @group i18n + */ class GettextMessageSourceTest extends TestCase { public function testLoadMessages() diff --git a/tests/unit/framework/i18n/GettextMoFileTest.php b/tests/unit/framework/i18n/GettextMoFileTest.php index 0aa22da..9b61145 100644 --- a/tests/unit/framework/i18n/GettextMoFileTest.php +++ b/tests/unit/framework/i18n/GettextMoFileTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\i18n; use yii\i18n\GettextMoFile; use yiiunit\TestCase; +/** + * @group i18n + */ class GettextMoFileTest extends TestCase { public function testLoad() diff --git a/tests/unit/framework/i18n/GettextPoFileTest.php b/tests/unit/framework/i18n/GettextPoFileTest.php index 8dddb40..4165b81 100644 --- a/tests/unit/framework/i18n/GettextPoFileTest.php +++ b/tests/unit/framework/i18n/GettextPoFileTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\i18n; use yii\i18n\GettextPoFile; use yiiunit\TestCase; +/** + * @group i18n + */ class GettextPoFileTest extends TestCase { public function testLoad() diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php index b3b7c4f..8c5d366 100644 --- a/tests/unit/framework/rbac/PhpManagerTest.php +++ b/tests/unit/framework/rbac/PhpManagerTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\rbac; use Yii; use yii\rbac\PhpManager; +/** + * @group rbac + */ class PhpManagerTest extends ManagerTestCase { protected function setUp() diff --git a/tests/unit/framework/requirements/YiiRequirementCheckerTest.php b/tests/unit/framework/requirements/YiiRequirementCheckerTest.php index 7554729..652d003 100644 --- a/tests/unit/framework/requirements/YiiRequirementCheckerTest.php +++ b/tests/unit/framework/requirements/YiiRequirementCheckerTest.php @@ -7,6 +7,7 @@ use yiiunit\TestCase; /** * Test case for [[YiiRequirementChecker]]. * @see YiiRequirementChecker + * @group requirements */ class YiiRequirementCheckerTest extends TestCase { diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/unit/framework/validators/EmailValidatorTest.php index 5807aed..b33a809 100644 --- a/tests/unit/framework/validators/EmailValidatorTest.php +++ b/tests/unit/framework/validators/EmailValidatorTest.php @@ -6,6 +6,7 @@ use yiiunit\TestCase; /** * EmailValidatorTest + * @group validators */ class EmailValidatorTest extends TestCase { diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php new file mode 100644 index 0000000..e740596 --- /dev/null +++ b/tests/unit/framework/web/CacheSessionTest.php @@ -0,0 +1,39 @@ +mockApplication(); + Yii::$app->setComponent('cache', new FileCache()); + } + + public function testCacheSession() + { + $session = new CacheSession(); + + $session->writeSession('test', 'sessionData'); + $this->assertEquals('sessionData', $session->readSession('test')); + $session->destroySession('test'); + $this->assertEquals('', $session->readSession('test')); + } + + public function testInvalidCache() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + + $session = new CacheSession(array( + 'cache' => 'invalid', + )); + } +} diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php index 41ed939..2a9b4bf 100644 --- a/tests/unit/framework/web/ResponseTest.php +++ b/tests/unit/framework/web/ResponseTest.php @@ -13,6 +13,9 @@ class MockResponse extends \yii\web\Response } } +/** + * @group web + */ class ResponseTest extends \yiiunit\TestCase { /** diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index efa6695..a77a66d 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -5,6 +5,9 @@ use yii\web\Request; use yii\web\UrlManager; use yiiunit\TestCase; +/** + * @group web + */ class UrlManagerTest extends TestCase { protected function setUp() diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php index d67dc58..0a0def4 100644 --- a/tests/unit/framework/web/UrlRuleTest.php +++ b/tests/unit/framework/web/UrlRuleTest.php @@ -7,6 +7,9 @@ use yii\web\UrlRule; use yii\web\Request; use yiiunit\TestCase; +/** + * @group web + */ class UrlRuleTest extends TestCase { public function testCreateUrl() diff --git a/tests/unit/framework/web/XmlResponseFormatterTest.php b/tests/unit/framework/web/XmlResponseFormatterTest.php index 590caef..e97962a 100644 --- a/tests/unit/framework/web/XmlResponseFormatterTest.php +++ b/tests/unit/framework/web/XmlResponseFormatterTest.php @@ -26,6 +26,8 @@ class Post extends Object /** * @author Qiang Xue * @since 2.0 + * + * @group web */ class XmlResponseFormatterTest extends \yiiunit\TestCase { diff --git a/tests/unit/framework/widgets/SpacelessTest.php b/tests/unit/framework/widgets/SpacelessTest.php index 6b2cf45..00f5a96 100644 --- a/tests/unit/framework/widgets/SpacelessTest.php +++ b/tests/unit/framework/widgets/SpacelessTest.php @@ -4,6 +4,9 @@ namespace yiiunit\framework\widgets; use yii\widgets\Spaceless; +/** + * @group widgets + */ class SpacelessTest extends \yiiunit\TestCase { public function testWidget()