diff --git a/.travis.yml b/.travis.yml index b7aea47..3f476f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ before_script: - tests/unit/data/travis/cubrid-setup.sh - tests/unit/data/travis/sphinx-setup.sh -#script: -# - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor +script: + - phpunit --coverage-text --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor #after_script: # - php vendor/bin/coveralls diff --git a/README.md b/README.md index 35a50f5..0ab29ca 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,22 @@ -Yii 2.0 Public Preview -====================== +Yii PHP Framework Version 2 +=========================== -Thank you for choosing Yii - a high-performance component-based PHP framework. +Thank you for choosing Yii 2 - a modern PHP framework designed for professional Web development. -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). +Yii 2 is a complete rewrite of its previous version Yii 1.1 which is one of the most popular PHP frameworks. +Yii 2 inherits the main spirit behind Yii for being simple, fast and highly extensible. +Yii 2 requires PHP 5.4 and embraces best practices and protocols found in modern Web application development. + + +**Yii 2 is not ready for production use yet.** We may make significant changes without prior notices. +We expect to make the first stable release of Yii 2 in early 2014. + +If you mainly want to learn Yii with no real project development requirement, we highly recommend +you start with Yii 2 as it will be our main focus for the next few years. + +If you have a real project with tight schedule, you should stick to [Yii 1.1](https://github.com/yiisoft/yii) +which is the latest stable release of Yii. -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2/v/stable.png)](https://packagist.org/packages/yiisoft/yii2) [![Total Downloads](https://poser.pugx.org/yiisoft/yii2/downloads.png)](https://packagist.org/packages/yiisoft/yii2) @@ -18,15 +27,14 @@ without prior notices. **Yii 2.0 is not ready for production use yet.** DIRECTORY STRUCTURE ------------------- - apps/ ready-to-use Web apps built on Yii 2 - advanced/ advanced app template with complex features - basic/ a simple app supporting user login and contact page - benchmark/ app demonstrating the minimal overhead introduced by the framework + apps/ ready-to-use application templates + advanced/ a template suitable for building sophisticated Web applications + basic/ a template suitable for building simple Web applications + benchmark/ an application demonstrating the performance of Yii build/ internally used build tools docs/ documentation extensions/ extensions - framework/ framework files - yii/ framework source files + framework/ core framework code tests/ tests of the core framework code @@ -39,11 +47,11 @@ The minimum requirement by Yii is that your Web server supports PHP 5.4. DOCUMENTATION ------------- +A draft of the [Definitive Guide](docs/guide/index.md) is available. + For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md) to have a general idea of what has changed in 2.0. -[Definitive Guide draft](docs/guide/index.md) is available. It's not complete yet but main parts are already OK. - HOW TO PARTICIPATE ------------------ diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 9fd15d2..72268b4 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -15,11 +15,11 @@ "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master", - "yiisoft/yii2-swiftmailer": "dev-master", - "yiisoft/yii2-bootstrap": "dev-master", - "yiisoft/yii2-debug": "dev-master", - "yiisoft/yii2-gii": "dev-master" + "yiisoft/yii2": "*", + "yiisoft/yii2-swiftmailer": "*", + "yiisoft/yii2-bootstrap": "*", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*" }, "scripts": { "post-create-project-cmd": [ diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index edb9cd9..db6dbe6 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -7,7 +7,7 @@ use yii\web\Controller; use common\models\LoginForm; use frontend\models\ContactForm; use common\models\User; -use yii\web\HttpException; +use yii\web\BadRequestHttpException; use yii\helpers\Security; class SiteController extends Controller @@ -132,7 +132,7 @@ class SiteController extends Controller ]); if (!$model) { - throw new HttpException(400, 'Wrong password reset token.'); + throw new BadRequestHttpException('Wrong password reset token.'); } $model->scenario = 'resetPassword'; diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php index 47bdf37..84ae427 100644 --- a/apps/advanced/requirements.php +++ b/apps/advanced/requirements.php @@ -48,6 +48,13 @@ $requirements = [ 'by' => 'All DB-related classes', 'memo' => 'Required for MySQL database.', ], + [ + 'name' => 'PDO PostgreSQL extension', + 'mandatory' => false, + 'condition' => extension_loaded('pdo_pgsql'), + 'by' => 'All DB-related classes', + 'memo' => 'Required for PostgreSQL database.', + ], // Cache : [ 'name' => 'Memcache extension', diff --git a/apps/basic/composer.json b/apps/basic/composer.json index cef46b7..a794341 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -15,11 +15,11 @@ "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master", - "yiisoft/yii2-swiftmailer": "dev-master", - "yiisoft/yii2-bootstrap": "dev-master", - "yiisoft/yii2-debug": "dev-master", - "yiisoft/yii2-gii": "dev-master" + "yiisoft/yii2": "*", + "yiisoft/yii2-swiftmailer": "*", + "yiisoft/yii2-bootstrap": "*", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*" }, "scripts": { "post-create-project-cmd": [ diff --git a/apps/benchmark/composer.json b/apps/benchmark/composer.json index c074233..d980f9a 100644 --- a/apps/benchmark/composer.json +++ b/apps/benchmark/composer.json @@ -18,6 +18,6 @@ "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master" + "yiisoft/yii2": "*" } } diff --git a/build/build.xml b/build/build.xml index b0975dc..846e4cf 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1,11 +1,11 @@ @@ -21,7 +21,7 @@ - + diff --git a/composer.json b/composer.json index 4cc1743..ad521cc 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "yiisoft/yii2-dev", - "description": "Yii2 Web Programming Framework - Development Package", + "description": "Yii PHP Framework Version 2 - Development Package", "keywords": ["yii", "framework"], "homepage": "http://www.yiiframework.com/", "type": "yii2-extension", @@ -39,21 +39,6 @@ "name": "Paul Klimov", "email": "klimov.paul@gmail.com", "role": "Core framework development" - }, - { - "name": "Wei Zhuo", - "email": "weizhuo@gmail.com", - "role": "Project site maintenance and development" - }, - { - "name": "Sebastián Thierer", - "email": "sebas@artfos.com", - "role": "Component development" - }, - { - "name": "Jeffrey Winesett", - "email": "jefftulsa@gmail.com", - "role": "Documentation and marketing" } ], "support": { @@ -67,6 +52,7 @@ "replace": { "yiisoft/yii2-bootstrap": "self.version", "yiisoft/yii2-debug": "self.version", + "yiisoft/yii2-elasticsearch": "self.version", "yiisoft/yii2-gii": "self.version", "yiisoft/yii2-jui": "self.version", "yiisoft/yii2-redis": "self.version", @@ -94,6 +80,7 @@ "psr-0": { "yii\\bootstrap\\": "extensions/bootstrap/", "yii\\debug\\": "extensions/debug/", + "yii\\elasticsearch\\": "extensions/elasticsearch/", "yii\\gii\\": "extensions/gii/", "yii\\jui\\": "extensions/jui/", "yii\\redis\\": "extensions/redis/", diff --git a/docs/guide/apps-advanced.md b/docs/guide/apps-advanced.md index 5090b00..3bdf754 100644 --- a/docs/guide/apps-advanced.md +++ b/docs/guide/apps-advanced.md @@ -139,11 +139,11 @@ directory: "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master", - "yiisoft/yii2-swiftmailer": "dev-master", - "yiisoft/yii2-bootstrap": "dev-master", - "yiisoft/yii2-debug": "dev-master", - "yiisoft/yii2-gii": "dev-master" + "yiisoft/yii2": "*", + "yiisoft/yii2-swiftmailer": "*", + "yiisoft/yii2-bootstrap": "*", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*" }, "scripts": { "post-create-project-cmd": [ diff --git a/docs/guide/apps-basic.md b/docs/guide/apps-basic.md index a6c5ed1..0125070 100644 --- a/docs/guide/apps-basic.md +++ b/docs/guide/apps-basic.md @@ -130,11 +130,11 @@ directory: "minimum-stability": "dev", "require": { "php": ">=5.4.0", - "yiisoft/yii2": "dev-master", - "yiisoft/yii2-swiftmailer": "dev-master", - "yiisoft/yii2-bootstrap": "dev-master", - "yiisoft/yii2-debug": "dev-master", - "yiisoft/yii2-gii": "dev-master" + "yiisoft/yii2": "*", + "yiisoft/yii2-swiftmailer": "*", + "yiisoft/yii2-bootstrap": "*", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*" }, "scripts": { "post-create-project-cmd": [ diff --git a/docs/guide/assets.md b/docs/guide/assets.md index 44dc29f..da3c8b3 100644 --- a/docs/guide/assets.md +++ b/docs/guide/assets.md @@ -114,4 +114,157 @@ return [ ``` There are two main benefits in enabling it. First it is faster since no copying is required and second is that assets -will always be up to date with source files. \ No newline at end of file +will always be up to date with source files. + +Compressing and combining assets +-------------------------------- + +To improve application performance you can compress and then combine several CSS or JS files into lesser number of files +therefore reducing number of HTTP requests and overall download size needed to load a web page. Yii provides a console +command that allows you to do both. + +### Preparing configuration + +In order to use `asset` command you should prepare a configuration first. A template for it can be generated using + +``` +yii asset/template /path/to/myapp/config.php +``` + +The template itself looks like the following: + +```php + [ + // 'yii\web\YiiAsset', + // 'yii\web\JqueryAsset', + ], + // Asset bundle for compression output: + 'targets' => [ + 'app\config\AllAsset' => [ + 'basePath' => 'path/to/web', + 'baseUrl' => '', + 'js' => 'js/all-{ts}.js', + 'css' => 'css/all-{ts}.css', + ], + ], + // Asset manager configuration: + 'assetManager' => [ + 'basePath' => __DIR__, + 'baseUrl' => '', + ], +]; +``` + +In the above keys are `properties` of `AssetController`. `bundles` list contains bundles that should be compressed. These are typically what's used by application. +`targets` contains a list of bundles that define how resulting files will be written. In our case we're writing +everything to `path/to/web` that can be accessed like `http://example.com/` i.e. it is website root directory. + +> Note: in the console environment some path aliases like '@webroot' and '@web' may not exist, + so corresponding paths inside the configuration should be specified directly. + +JavaScript files are combined, compressed and written to `js/all-{ts}.js` where {ts} is replaced with current UNIX +timestamp. + +### Providing compression tools + +The command relies on external compression tools that are not bundled with Yii so you need to provide CSS and JS +compressors which are correspondingly specified via `cssCompressor` and `jsCompression` properties. If compressor is +specified as a string it is treated as a shell command template which should contain two placeholders: `{from}` that +is replaced by source file name and `{to}` that is replaced by output file name. Another way to specify compressor is +to use any valid PHP callback. + +By default for JavaScript compression Yii tries to use +[Google Closure compiler](https://developers.google.com/closure/compiler/) that is expected to be in a file named +`compiler.jar`. + +For CSS compression Yii assumes that [YUI Compressor](https://github.com/yui/yuicompressor/) is looked up in a file +named `yuicompressor.jar`. + +In order to compress resources with these two you need to download both and place where your `yii` console bootstrap +file is using named mentioned above. Since both are Java tools you need JRE installed. + +### Performing compression + +After configuration is adjusted you can run the `compress` action, using created config: + +``` +yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php +``` + +Now processing takes some time and finally finished. You need to adjust your web application config to use compressed +assets file like the following: + +```php +'components' => [ + // ... + 'assetManager' => [ + 'bundles' => require /path/to/myapp/config/assets_compressed.php, + ], +], +``` + +Using asset converter +--------------------- + +Instead of using CSS and JavaScript directly often developers are using their improved versions such as LESS or SCSS +for CSS or Microsoft TypeScript for JavaScript. Using these with Yii is easy. + +First of all, corresponding compression tools should be installed and should be availabe from where `yii` console +bootstrap file is. The following lists file extensions and their corresponding conversion tool names that Yii converter +recognizes: + +- LESS: `less` - `lessc` +- SCSS: `scss`, `sass` - `sass` +- Stylus: `styl` - `stylus` +- CoffeeScript: `coffee` - `coffee` +- TypeScript: `ts` - `tsc` + +So if the corresponding tool is installed you can specify any of these in asset bundle: + +```php +class AppAsset extends AssetBundle +{ + public $basePath = '@webroot'; + public $baseUrl = '@web'; + public $css = [ + 'css/site.less', + ]; + public $js = [ + 'js/site.ts', + ]; + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} +``` + +In order to adjust conversion tool call parameters or add new ones you can use application config: + +```php +// ... +'components' => [ + 'assetManager' => [ + 'converter' => [ + 'class' => 'yii\web\AssetConverter', + 'commands' => [ + 'less' => ['css', 'lessc {from} {to} --no-color'], + 'ts' => ['js', 'tsc --out {to} {from}'], + ], + ], + ], +], +``` + +In the above we've left two types of extra file extensions. First one is `less` that can be specified in `css` part +of an asset bundle. Conversion is performed via running `lessc {from} {to} --no-color` where `{from}` is replaced with +LESS file path while `{to}` is replaced with target CSS file path. Second one is `ts` that can be specified in `js` part +of an asset bundle. The command that is run during conversion is in the same format that is used for `less`. \ No newline at end of file diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md index 8f2b97f..086aa32 100644 --- a/docs/guide/authorization.md +++ b/docs/guide/authorization.md @@ -234,10 +234,10 @@ public function editArticle($id) { $article = Article::find($id); if (!$article) { - throw new HttpException(404); + throw new NotFoundHttpException; } if (!\Yii::$app->user->checkAccess('edit_article', ['article' => $article])) { - throw new HttpException(403); + throw new AccessDeniedHttpException; } // ... } @@ -250,7 +250,7 @@ public function editArticle($id) { $article = Article::find(['id' => $id, 'author_id' => \Yii::$app->user->id]); if (!$article) { - throw new HttpException(404); + throw new NotFoundHttpException; } // ... } diff --git a/docs/guide/behaviors.md b/docs/guide/behaviors.md index 8a67270..b425fd3 100644 --- a/docs/guide/behaviors.md +++ b/docs/guide/behaviors.md @@ -1,4 +1,41 @@ Behaviors ========= -TDB \ No newline at end of file +A behavior (also knows as mixin) can be used to enhance the functionality of an existing component without modifying its +code. In particular, it can "inject" its own methods and properties into the component and make them directly accessible +via the component. It can also respond to the events triggered in the component and thus intercept the normal +code execution. Unlike PHP traits, behaviors could be attached to classes at runtime. + +Using behaviors +--------------- + +Behavior can be attached to any class that extends from `Component`. In order to do so you need to implement `behaviors` +method. Yii provides `AutoTimestamp` behavior that handles updating timestamp fields on saving active record model. + +```php +class User extends ActiveRecord +{ + // ... + + public function behaviors() + { + return [ + 'timestamp' => [ + 'class' => 'yii\behaviors\AutoTimestamp', + 'attributes' => [ + ActiveRecord::EVENT_BEFORE_INSERT => ['create_time', 'update_time'], + ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time', + ], + ], + ]; + } +} +``` + +In the above `class` value is a string containing fully qualified behavior class name. All the other key-values are +assigned to corresponding properties of the class. + +Creating your own behaviors +--------------------------- + +TBD \ No newline at end of file diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 11f2470..ab4e410 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -96,4 +96,18 @@ not be instantiated and configured at all. Setting component defaults classwide ------------------------------------ -TBD +For each component you can specifiy classwide defaults. For example, if we want to change class for all `LinkPager` +widgets without specifying it over and over again when widget is called we can do it like the following: + +```php +\Yii::$objectConfig = [ + 'yii\widgets\LinkPager' => [ + 'options' => [ + 'class' => 'pagination', + ], + ], +]; +``` + +The code above should be executed once before `LinkPager` widget is used. It can be done in `index.php`, application +config or anywhere else. \ No newline at end of file diff --git a/docs/guide/console.md b/docs/guide/console.md index 055c206..9bc6069 100644 --- a/docs/guide/console.md +++ b/docs/guide/console.md @@ -1,4 +1,83 @@ -Building console applications -============================= +Console applications +==================== -TDB \ No newline at end of file +Yii has full featured support of console. Console application structure in Yii is very similar to web application. It +consists of one or more [[\yii\console\Controller]] (often referred to as commands). Each has one or more actions. + +Usage +----- + +You can execute controller action using the following syntax: + +``` +yii [--param1=value1 --param2 ...] +``` + +For example, `MigrationController::create` with `MigrationController::$migrationTable` set can be called from command +line like the following: + +``` +yii migreate/create --migrationTable=my_migration +``` + +In the above `yii` is console application entry script described below. + +Entry script +------------ + +Console application entry script is typically called `yii`, located in your application root directory and contains +code like the following: + +```php +#!/usr/bin/env php +run(); +exit($exitCode); + +``` + +This script is a part of your application so you're free to adjust it. There `YII_DEBUG` can be turned off if you do +not want to see stacktrace on error and want to improve overall performance. In both basic and advanced application +templates it is enabled to provide more developer-friendly environment. + +Configuration +------------- + +As can be seen in the code above, console application uses its own config files named `console.php` so you need to +configure database connection and the rest of the components you're going to use there in that file. If web and console +application configs have a lot in common it's a good idea to move matching parts into their own config files as it was +done in advanced application template. + + +Creating your own console commands +---------------------------------- + +### Controller + +### Action + +### Parameters + +### Return codes + +Using return codes is the best practice of console application development. If command returns `0` it means everything +is OK. If it is a number more than zero, we have an error and integer returned is the error code. \ No newline at end of file diff --git a/docs/guide/controller.md b/docs/guide/controller.md index 571a833..97ffe49 100644 --- a/docs/guide/controller.md +++ b/docs/guide/controller.md @@ -122,7 +122,7 @@ class BlogController extends Controller { $post = Post::find($id); if (!$post) { - throw new HttpException(404); + throw new NotFoundHttpException; } if (\Yii::$app->request->isPost)) { @@ -183,7 +183,7 @@ Action Filters Action filters are implemented via behaviors. You should extend from `ActionFilter` to define a new filter. To use a filter, you should attach the filter class to the controller -as a behavior. For example, to use the `AccessControl` filter, you should have the following +as a behavior. For example, to use the [[AccessControl]] filter, you should have the following code in a controller: ```php @@ -200,7 +200,8 @@ public function behaviors() } ``` -more TDB +In order to learn more about access control check [authorization](authorization.md) section of the guide. +Two other filters, [[PageCache]] and [[HttpCache]] are described in [caching](caching.md) section of the guide. Catching all incoming requests ------------------------------ diff --git a/docs/guide/database-basics.md b/docs/guide/database-basics.md index 511ecaf..520da22 100644 --- a/docs/guide/database-basics.md +++ b/docs/guide/database-basics.md @@ -9,8 +9,8 @@ uniform API and solves some inconsistencies between different DBMS. By default Y - [SQLite](http://sqlite.org/) - [PostgreSQL](http://www.postgresql.org/) - [CUBRID](http://www.cubrid.org/) (version 9.1.0 and higher). -- Oracle -- MSSQL +- [Oracle](http://www.oracle.com/us/products/database/overview/index.html) +- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx) Configuration @@ -42,6 +42,7 @@ return [ // ... ]; ``` + 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. diff --git a/docs/guide/debugger.md b/docs/guide/debugger.md index 9006d9d..a49471a 100644 --- a/docs/guide/debugger.md +++ b/docs/guide/debugger.md @@ -7,19 +7,21 @@ about currently opened page while using debugger you can analyze data collected Installing and configuring -------------------------- -How to use it -------------- - Add these lines to your config file: ``` - 'preload' => ['debug'], - 'modules' => [ - 'debug' => ['yii\debug\Module'] - ] +'preload' => ['debug'], +'modules' => [ + 'debug' => ['yii\debug\Module'] +] ``` -**Watch out: by default the debug module only works when browsing the website from the localhost. If you want to use it on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP.** +**Watch out: by default the debug module only works when browsing the website from the localhost. If you want to use it +on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP.** + +How to use it +------------- + Creating your own panels ------------------------ diff --git a/docs/guide/gii.md b/docs/guide/gii.md index 54f6a36..525c567 100644 --- a/docs/guide/gii.md +++ b/docs/guide/gii.md @@ -7,17 +7,17 @@ as well as complete CRUD controllers. Installing and configuring -------------------------- -How to use it -------------- - Add these lines to your config file: ```php - 'modules' => [ - 'gii' => ['yii\gii\Module'] - ] +'modules' => [ + 'gii' => ['yii\gii\Module'] +] ``` +How to use it +------------- + Creating your own templates --------------------------- diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 477de0a..482b318 100644 --- a/docs/guide/i18n.md +++ b/docs/guide/i18n.md @@ -47,7 +47,7 @@ translation of the message from source language into target language. Message it echo \Yii::t('app', 'This is a string to translate!'); ``` -Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration: +Yii tries to load appropriate translation from one of the message sources defined via `i18n` component configuration: ```php 'components' => [ @@ -239,7 +239,7 @@ you'll get "Inconsistent types declared for an argument: U_ARGUMENT_TYPE_MISMATC Total {count, number} {count, plural, one{item} other{items}}. ``` -To learn which inflection forms you should specify for your language you can referer to +To learn which inflection forms you should specify for your language you can referrer to [rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). ### Selections @@ -264,3 +264,5 @@ Formatters In order to use formatters you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP extension. + +TBD: provided classes overview. diff --git a/docs/guide/index.md b/docs/guide/index.md index 422ca64..a560a3c 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -47,7 +47,7 @@ Extensions and 3rd party libraries - [Composer](composer.md) - How to manage applications dependencies via composer - [Extending Yii](extensions.md) -- [Template engines](template.md) - Using template engines such as Smary or Twig +- [Template engines](template.md) - Using template engines such as Smarty or Twig Security and access control =========================== diff --git a/docs/guide/performance.md b/docs/guide/performance.md index 42aa042..9309c2c 100644 --- a/docs/guide/performance.md +++ b/docs/guide/performance.md @@ -28,9 +28,10 @@ logger may record additional debug information for every message being logged. ### Enabling PHP opcode cache Enabling the PHP opcode cache improves any PHP application performance and lowers -memory usage significantly. Yii is no exception. It was tested with -[APC PHP extension](http://php.net/manual/en/book.apc.php) that caches -and optimizes PHP intermediate code and avoids the time spent in parsing PHP +memory usage significantly. Yii is no exception. It was tested with both +[PHP 5.5 OPcache](http://php.net/manual/en/book.opcache.php) and +[APC PHP extension](http://php.net/manual/en/book.apc.php). Both cache +and optimize PHP intermediate code and avoid the time spent in parsing PHP scripts for every incoming request. ### Turning on ActiveRecord database schema caching @@ -69,7 +70,10 @@ Note that `cache` application component should be configured. ### Combining and Minimizing Assets -TBD +It is possible to combine and minimize assets, typically JavaScript and CSS, in order to slightly improve page load +time and therefore deliver better experience for end user of your application. + +In order to learn how it can be achieved, refer to [assets](assets.md) guide section. ### Using better storage for sessions @@ -118,7 +122,38 @@ save the rendering cost for the whole page. ### Leveraging HTTP to save processing time and bandwidth -TBD +Leveraging HTTP caching saves both processing time, bandwidth and resources significantly. It can be implemented by +sending either `ETag` or `Last-Modified` header in your application response. If browser is implemented according to +HTTP specification (most browsers are), content will be fetched only if it is different from what it was prevously. + +Forming proper headers is time consuming task so Yii provides a shortcut in form of controller filter +[[\yii\web\HttpCache]]. Using it is very easy. In a controller you need to implement `behaviors` method like +the following: + +```php +public function behaviors() +{ + return [ + 'httpCache' => [ + 'class' => \yii\web\HttpCache::className(), + 'only' => ['list'], + 'lastModified' => function ($action, $params) { + $q = new Query(); + return strtotime($q->from('users')->max('updated_timestamp')); + }, + // 'etagSeed' => function ($action, $params) { + // return // generate etag seed here + //} + ], + ]; +} +``` + +In the code above one can use either `etagSeed` or `lastModified`. Implementing both isn't necessary. The goal is to +determine if content was modified in a way that is cheaper than fetching and rendering that content. `lastModified` +should return unix timestamp of the last content modification while `etagSeed` should return a string that is then +used to generate `ETag` header value. + ### Database Optimization @@ -140,7 +175,7 @@ to create one or several objects to represent each row of query result. For data intensive applications, using DAO or database APIs at lower level could be a better choice. -Last but not least, use LIMIT in your SELECT queries. This avoids fetching +Last but not least, use `LIMIT` in your `SELECT` queries. This avoids fetching overwhelming data from database and exhausting the memory allocated to PHP. ### Using asArray diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index c02a48d..d644190 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -518,5 +518,8 @@ TBD Integration with Composer ------------------------- -TBD +Yii is fully inegrated with the package manager for PHP named Composer that resolves dependencies, keeps your code +up to date updating it semi-automatically and manages autoloading for third party libraries no matter which autoloading +these are using. +In order to learn more refer to [composer](composer.md) and [installation](installation.md) sections of the guide. \ No newline at end of file diff --git a/docs/guide/url.md b/docs/guide/url.md index 454a827..e54be4b 100644 --- a/docs/guide/url.md +++ b/docs/guide/url.md @@ -92,6 +92,8 @@ return [ ### Handling REST +TBD: [[\yii\web\VerbFiler]] + URL parsing ----------- @@ -118,3 +120,72 @@ return [ Creating your own rule classes ------------------------------ + +[[\yii\web\UrlRule]] class is used for both parsing URL into parameters and creating URL based on parameters. Despite +the fact that default implementation is flexible enough for majority of projects, there could be a situation when using +your own rule class is the best choice. For example, in a car dealer website, we may want to support the URL format like +`/Manufacturer/Model`, where `Manufacturer` and `Model` must both match some data in a database table. The default rule +class will not work because it mostly relies on statically declared regular expressions which have no database knowledge. + +We can write a new URL rule class by extending from [[\yii\web\UrlRule]] and use it in one or multiple URL rules. Using +the above car dealer website as an example, we may declare the following URL rules in application config: + +```php +// ... +'components' => [ + 'urlManager' => [ + 'rules' => [ + '' => 'site/', + + // ... + + ['class' => 'app\components\CarUrlRule', 'connectionID' => 'db', ...], + ], + ], +], +``` + +In the above, we use the custom URL rule class `CarUrlRule` to handle +the URL format `/Manufacturer/Model`. The class can be written like the following: + +```php +namespace \app\components; + +use \yii\web\UrlRule; + +class CarUrlRule extends UrlRule +{ + public $connectionID = 'db'; + + public function createUrl($manager, $route, $params) + { + if ($route === 'car/index') { + if (isset($params['manufacturer'], $params['model'])) { + return $params['manufacturer'] . '/' . $params['model']; + } elseif (isset($params['manufacturer'])) { + return $params['manufacturer']; + } + } + return false; // this rule does not apply + } + + public function parseRequest($manager, $request) + { + $pathInfo = $request->getPathInfo(); + if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) { + // check $matches[1] and $matches[3] to see + // if they match a manufacturer and a model in the database + // If so, set $_GET['manufacturer'] and/or $_GET['model'] + // and return 'car/index' + } + return false; // this rule does not apply + } +} +``` + +Besides the above usage, custom URL rule classes can also be implemented +for many other purposes. For example, we can write a rule class to log the URL parsing +and creation requests. This may be useful during development stage. We can also +write a rule class to display a special 404 error page in case all other URL rules fail +to resolve the current request. Note that in this case, the rule of this special class +must be declared as the last rule. diff --git a/docs/guide/view.md b/docs/guide/view.md index 7161a5a..fdfc061 100644 --- a/docs/guide/view.md +++ b/docs/guide/view.md @@ -277,7 +277,12 @@ use yii\helpers\Html; In the markup above there's some code. First of all, `$content` is a variable that will contain result of views rendered with controller's `$this->render()` method. -TBD +We are importing `Html` helper via standard PHP `use` statement. This helper is typically used for almost all views +where one need to escape outputted data. + +Several special methods such as `beginPage`/`endPage`, `head`, `beginBody`/`endBody` are triggering page rendering events +that are used for registering scripts, links and process page in many other ways. Always include these in your layout in +order for rendering to work correctly. ### Partials diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php index 164392e..d8d799f 100644 --- a/extensions/composer/Installer.php +++ b/extensions/composer/Installer.php @@ -26,7 +26,7 @@ class Installer extends LibraryInstaller const EXTENSION_FILE = 'yiisoft/extensions.php'; /** - * {@inheritdoc} + * @inheritdoc */ public function supports($packageType) { @@ -34,7 +34,7 @@ class Installer extends LibraryInstaller } /** - * {@inheritdoc} + * @inheritdoc */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { @@ -49,7 +49,7 @@ class Installer extends LibraryInstaller } /** - * {@inheritdoc} + * @inheritdoc */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { @@ -63,7 +63,7 @@ class Installer extends LibraryInstaller } /** - * {@inheritdoc} + * @inheritdoc */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { diff --git a/extensions/composer/Plugin.php b/extensions/composer/Plugin.php index 40bd8e5..1111738 100644 --- a/extensions/composer/Plugin.php +++ b/extensions/composer/Plugin.php @@ -20,7 +20,7 @@ use Composer\Plugin\PluginInterface; class Plugin implements PluginInterface { /** - * {@inheritdoc} + * @inheritdoc */ public function activate(Composer $composer, IOInterface $io) { diff --git a/extensions/debug/Module.php b/extensions/debug/Module.php index 51f69a6..8a20021 100644 --- a/extensions/debug/Module.php +++ b/extensions/debug/Module.php @@ -10,7 +10,7 @@ namespace yii\debug; use Yii; use yii\base\Application; use yii\web\View; -use yii\web\HttpException; +use yii\web\AccessDeniedHttpException; /** * The Yii Debug Module provides the debug toolbar and debugger @@ -79,7 +79,7 @@ class Module extends \yii\base\Module } elseif ($action->id === 'toolbar') { return false; } else { - throw new HttpException(403, 'You are not allowed to access this page.'); + throw new AccessDeniedHttpException('You are not allowed to access this page.'); } } diff --git a/extensions/debug/controllers/DefaultController.php b/extensions/debug/controllers/DefaultController.php index a4ac633..6694d26 100644 --- a/extensions/debug/controllers/DefaultController.php +++ b/extensions/debug/controllers/DefaultController.php @@ -9,7 +9,7 @@ namespace yii\debug\controllers; use Yii; use yii\web\Controller; -use yii\web\HttpException; +use yii\web\NotFoundHttpException; /** * @author Qiang Xue @@ -99,7 +99,7 @@ class DefaultController extends Controller } $this->summary = $data['summary']; } else { - throw new HttpException(404, "Unable to find debug data tagged with '$tag'."); + throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'."); } } } diff --git a/extensions/elasticsearch/ActiveQuery.php b/extensions/elasticsearch/ActiveQuery.php index b444f05..96d6681 100644 --- a/extensions/elasticsearch/ActiveQuery.php +++ b/extensions/elasticsearch/ActiveQuery.php @@ -139,7 +139,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function search($db = null, $options = []) { @@ -161,7 +161,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function scalar($field, $db = null) { @@ -177,7 +177,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function column($field, $db = null) { diff --git a/extensions/elasticsearch/ActiveRecord.php b/extensions/elasticsearch/ActiveRecord.php index efdb5fe..d6ada20 100644 --- a/extensions/elasticsearch/ActiveRecord.php +++ b/extensions/elasticsearch/ActiveRecord.php @@ -10,6 +10,8 @@ namespace yii\elasticsearch; use yii\base\InvalidCallException; use yii\base\InvalidConfigException; use yii\base\NotSupportedException; +use yii\db\ActiveRecordInterface; +use yii\db\BaseActiveRecord; use yii\helpers\Inflector; use yii\helpers\Json; use yii\helpers\StringHelper; @@ -42,7 +44,7 @@ use yii\helpers\StringHelper; * @author Carsten Brandt * @since 2.0 */ -class ActiveRecord extends \yii\db\ActiveRecord +class ActiveRecord extends BaseActiveRecord { const PRIMARY_KEY_NAME = 'id'; @@ -61,7 +63,7 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * @inheritdoc */ public static function find($q = null) { @@ -138,7 +140,7 @@ class ActiveRecord extends \yii\db\ActiveRecord // TODO add percolate functionality http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-percolate.html /** - * {@inheritdoc} + * @inheritdoc */ public static function createQuery() { @@ -146,7 +148,7 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * @inheritdoc */ public static function createActiveRelation($config = []) { @@ -175,7 +177,7 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * @inheritdoc */ public function getPrimaryKey($asArray = false) { @@ -187,7 +189,7 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * @inheritdoc */ public function getOldPrimaryKey($asArray = false) { @@ -428,47 +430,4 @@ class ActiveRecord extends \yii\db\ActiveRecord } return $n; } - - /** - * {@inheritdoc} - */ - public static function updateAllCounters($counters, $condition = null, $params = []) - { - throw new NotSupportedException('Update Counters is not supported by elasticsearch ActiveRecord.'); - } - - /** - * {@inheritdoc} - */ - public static function getTableSchema() - { - throw new NotSupportedException('getTableSchema() is not supported by elasticsearch ActiveRecord.'); - } - - /** - * {@inheritdoc} - */ - public static function tableName() - { - return static::index() . '/' . static::type(); - } - - /** - * {@inheritdoc} - */ - public static function findBySql($sql, $params = []) - { - throw new NotSupportedException('findBySql() is not supported by elasticsearch ActiveRecord.'); - } - - /** - * Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. - * This method will always return false as transactional operations are not supported by elasticsearch. - * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. - * @return boolean whether the specified operation is transactional in the current [[scenario]]. - */ - public function isTransactional($operation) - { - return false; - } } diff --git a/extensions/elasticsearch/Connection.php b/extensions/elasticsearch/Connection.php index d5275e8..098d6ee 100644 --- a/extensions/elasticsearch/Connection.php +++ b/extensions/elasticsearch/Connection.php @@ -15,6 +15,9 @@ use yii\helpers\Json; /** * elasticsearch Connection is used to connect to an elasticsearch cluster version 0.20 or higher * + * @property string $driverName Name of the DB driver. This property is read-only. + * @property boolean $isActive Whether the DB connection is established. This property is read-only. + * * @author Carsten Brandt * @since 2.0 */ @@ -264,7 +267,7 @@ class Connection extends Component if (strncmp($host, 'inet[/', 6) == 0) { $host = substr($host, 6, -1); } - $profile = $q . $requestBody; + $profile = $method . ' ' . $q . '#' . $requestBody; $url = 'http://' . $host . '/' . $q; } else { $profile = false; diff --git a/extensions/elasticsearch/DebugPanel.php b/extensions/elasticsearch/DebugPanel.php new file mode 100644 index 0000000..da5a824 --- /dev/null +++ b/extensions/elasticsearch/DebugPanel.php @@ -0,0 +1,168 @@ + + * @since 2.0 + */ +class DebugPanel extends Panel +{ + public function getName() + { + return 'Elasticsearch'; + } + + public function getSummary() + { + $timings = $this->calculateTimings(); + $queryCount = count($timings); + $queryTime = 0; + foreach ($timings as $timing) { + $queryTime += $timing[3]; + } + $queryTime = number_format($queryTime * 1000) . ' ms'; + $url = $this->getUrl(); + $output = << + + ES $queryCount $queryTime + + +EOD; + return $queryCount > 0 ? $output : ''; + } + + public function getDetail() + { + $rows = []; + $i = 0; + foreach ($this->data['messages'] as $log) { + list ($message, $level, $category, $time, $traces) = $log; + if ($level == Logger::LEVEL_PROFILE_BEGIN) { + continue; + } + if (($pos = mb_strpos($message, "#")) !== false) { + $url = mb_substr($message, 0, $pos); + $body = mb_substr($message, $pos + 1); + } else { + $url = $message; + $body = null; + } + $traceString = ''; + if (!empty($traces)) { + $traceString .= Html::ul($traces, [ + 'class' => 'trace', + 'item' => function ($trace) { + return "
  • {$trace['file']}({$trace['line']})
  • "; + }, + ]); + } + $runLinks = ''; + $c = 0; + \Yii::$app->elasticsearch->open(); + foreach(\Yii::$app->elasticsearch->nodes as $node) { + $pos = mb_strpos($url, ' '); + $type = mb_substr($url, 0, $pos); + if ($type == 'GET' && !empty($body)) { + $type = 'POST'; + } + $host = $node['http_address']; + if (strncmp($host, 'inet[/', 6) == 0) { + $host = substr($host, 6, -1); + } + $nodeUrl = 'http://' . $host . '/' . mb_substr($url, $pos + 1); + $nodeUrl .= (strpos($nodeUrl, '?') === false) ? '?pretty=true' : '&pretty=true'; + $nodeBody = json_encode($body); + \Yii::$app->view->registerJs(<< "elastic-link-$i-$c"]) . '
    '; + $c++; + } + $rows[] = "
    $url

    $body

    $traceString
    $runLinks"; + $i++; + } + $rows = implode("\n", $rows); + return <<Elasticsearch Queries + + + + + + + + + +$rows + +
    Url / QueryRun Query on node
    +HTML; + } + + private $_timings; + + protected function calculateTimings() + { + if ($this->_timings !== null) { + return $this->_timings; + } + $messages = $this->data['messages']; + $timings = []; + $stack = []; + foreach ($messages as $i => $log) { + list($token, $level, $category, $timestamp) = $log; + $log[5] = $i; + if ($level == Logger::LEVEL_PROFILE_BEGIN) { + $stack[] = $log; + } elseif ($level == Logger::LEVEL_PROFILE_END) { + if (($last = array_pop($stack)) !== null && $last[0] === $token) { + $timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]]; + } + } + } + + $now = microtime(true); + while (($last = array_pop($stack)) !== null) { + $delta = $now - $last[3]; + $timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]]; + } + ksort($timings); + return $this->_timings = $timings; + } + + public function save() + { + $target = $this->module->logTarget; + $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\elasticsearch\Connection::httpRequest']); + return ['messages' => $messages]; + } +} diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php index 5ed77e6..9201f9a 100644 --- a/extensions/elasticsearch/QueryBuilder.php +++ b/extensions/elasticsearch/QueryBuilder.php @@ -14,7 +14,6 @@ use yii\helpers\Json; /** * QueryBuilder builds an elasticsearch query based on the specification given as a [[Query]] object. * - * * @author Carsten Brandt * @since 2.0 */ @@ -247,7 +246,7 @@ class QueryBuilder extends \yii\base\Object } if (count($column) > 1) { - return $this->buildCompositeInCondition($operator, $column, $values, $params); + return $this->buildCompositeInCondition($operator, $column, $values); } elseif (is_array($column)) { $column = reset($column); } @@ -289,61 +288,10 @@ class QueryBuilder extends \yii\base\Object protected function buildCompositeInCondition($operator, $columns, $values) { throw new NotSupportedException('composite in is not supported by elasticsearch.'); - $vss = array(); - foreach ($values as $value) { - $vs = array(); - foreach ($columns as $column) { - if (isset($value[$column])) { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $value[$column]; - $vs[] = $phName; - } else { - $vs[] = 'NULL'; - } - } - $vss[] = '(' . implode(', ', $vs) . ')'; - } - foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } - } - return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; } private function buildLikeCondition($operator, $operands) { throw new NotSupportedException('like conditions is not supported by elasticsearch.'); - if (!isset($operands[0], $operands[1])) { - throw new Exception("Operator '$operator' requires two operands."); - } - - list($column, $values) = $operands; - - $values = (array)$values; - - if (empty($values)) { - return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0==1' : ''; - } - - if ($operator === 'LIKE' || $operator === 'NOT LIKE') { - $andor = ' AND '; - } else { - $andor = ' OR '; - $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; - } - - if (strpos($column, '(') === false) { - $column = $this->db->quoteColumnName($column); - } - - $parts = array(); - foreach ($values as $value) { - $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $value; - $parts[] = "$column $operator $phName"; - } - - return implode($andor, $parts); } } diff --git a/extensions/elasticsearch/README.md b/extensions/elasticsearch/README.md index 57497e2..2a07155 100644 --- a/extensions/elasticsearch/README.md +++ b/extensions/elasticsearch/README.md @@ -146,3 +146,29 @@ $query->search(); // gives you all the records + stats about the visit_count fie ``` And there is so much more in it. "it’s endless what you can build"[¹](http://www.elasticsearch.org/) + + +Using the elasticsearch DebugPanel +---------------------------------- + +The yii2 elasticsearch extensions provides a `DebugPanel` that can be integrated with the yii debug module +an shows the executed elasticsearch queries. It also allows to run these queries on different cluster nodes +an view the results. + +Add the following to you application config to enable it: + +```php + // ... + 'preload' => 'debug', + 'modules' => [ + 'debug' => [ + 'class' => 'yii\\debug\\Module', + 'panels' => [ + 'elasticsearch' => [ + 'class' => 'yii\\elasticsearch\\DebugPanel', + ], + ], + ], + ], + // ... +``` diff --git a/extensions/gii/Generator.php b/extensions/gii/Generator.php index 53e54bb..05c45a7 100644 --- a/extensions/gii/Generator.php +++ b/extensions/gii/Generator.php @@ -63,7 +63,7 @@ abstract class Generator extends Model abstract public function generate(); /** - * {@inheritdoc} + * @inheritdoc */ public function init() { @@ -164,7 +164,7 @@ abstract class Generator extends Model } /** - * {@inheritdoc} + * @inheritdoc * * Child classes should override this method like the following so that the parent * rules are included: diff --git a/extensions/gii/GiiAsset.php b/extensions/gii/GiiAsset.php index 64dc62b..b100750 100644 --- a/extensions/gii/GiiAsset.php +++ b/extensions/gii/GiiAsset.php @@ -18,25 +18,25 @@ use yii\web\AssetBundle; class GiiAsset extends AssetBundle { /** - * {@inheritdoc} + * @inheritdoc */ public $sourcePath = '@yii/gii/assets'; /** - * {@inheritdoc} + * @inheritdoc */ public $css = [ 'main.css', 'typeahead.js-bootstrap.css', ]; /** - * {@inheritdoc} + * @inheritdoc */ public $js = [ 'gii.js', 'typeahead.js', ]; /** - * {@inheritdoc} + * @inheritdoc */ public $depends = [ 'yii\web\YiiAsset', diff --git a/extensions/gii/Module.php b/extensions/gii/Module.php index 5644e29..a7bb3ed 100644 --- a/extensions/gii/Module.php +++ b/extensions/gii/Module.php @@ -8,7 +8,7 @@ namespace yii\gii; use Yii; -use yii\web\HttpException; +use yii\web\AccessDeniedHttpException; /** * This is the main module class for the Gii module. @@ -54,7 +54,7 @@ use yii\web\HttpException; class Module extends \yii\base\Module { /** - * {@inheritdoc} + * @inheritdoc */ public $controllerNamespace = 'yii\gii\controllers'; /** @@ -92,7 +92,7 @@ class Module extends \yii\base\Module /** - * {@inheritdoc} + * @inheritdoc */ public function init() { @@ -103,14 +103,14 @@ class Module extends \yii\base\Module } /** - * {@inheritdoc} + * @inheritdoc */ public function beforeAction($action) { if ($this->checkAccess()) { return parent::beforeAction($action); } else { - throw new HttpException(403, 'You are not allowed to access this page.'); + throw new AccessDeniedHttpException('You are not allowed to access this page.'); } } diff --git a/extensions/gii/controllers/DefaultController.php b/extensions/gii/controllers/DefaultController.php index ef104c3..2b3bf00 100644 --- a/extensions/gii/controllers/DefaultController.php +++ b/extensions/gii/controllers/DefaultController.php @@ -9,7 +9,7 @@ namespace yii\gii\controllers; use Yii; use yii\web\Controller; -use yii\web\HttpException; +use yii\web\NotFoundHttpException; /** * @author Qiang Xue @@ -69,7 +69,7 @@ class DefaultController extends Controller } } } - throw new HttpException(404, "Code file not found: $file"); + throw new NotFoundHttpException("Code file not found: $file"); } public function actionDiff($id, $file) @@ -84,7 +84,7 @@ class DefaultController extends Controller } } } - throw new HttpException(404, "Code file not found: $file"); + throw new NotFoundHttpException("Code file not found: $file"); } /** @@ -94,7 +94,7 @@ class DefaultController extends Controller * @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. + * @throws NotFoundHttpException if the action method does not exist. */ public function actionAction($id, $name) { @@ -103,7 +103,7 @@ class DefaultController extends Controller if (method_exists($generator, $method)) { return $generator->$method(); } else { - throw new HttpException(400, "Unknown generator action: $name"); + throw new NotFoundHttpException("Unknown generator action: $name"); } } @@ -136,7 +136,7 @@ class DefaultController extends Controller * Loads the generator with the specified ID. * @param string $id the ID of the generator to be loaded. * @return \yii\gii\Generator the loaded generator - * @throws \yii\web\HttpException + * @throws NotFoundHttpException */ protected function loadGenerator($id) { @@ -146,7 +146,7 @@ class DefaultController extends Controller $this->generator->load($_POST); return $this->generator; } else { - throw new HttpException(404, "Code generator not found: $id"); + throw new NotFoundHttpException("Code generator not found: $id"); } } } diff --git a/extensions/gii/generators/controller/Generator.php b/extensions/gii/generators/controller/Generator.php index 769db0a..08b29d5 100644 --- a/extensions/gii/generators/controller/Generator.php +++ b/extensions/gii/generators/controller/Generator.php @@ -38,7 +38,7 @@ class Generator extends \yii\gii\Generator public $actions = 'index'; /** - * {@inheritdoc} + * @inheritdoc */ public function init() { @@ -47,7 +47,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function getName() { @@ -55,7 +55,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function getDescription() { @@ -64,7 +64,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function rules() { @@ -79,7 +79,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function attributeLabels() { @@ -92,7 +92,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function requiredTemplates() { @@ -103,7 +103,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function stickyAttributes() { @@ -111,7 +111,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function hints() { @@ -134,7 +134,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function successMessage() { @@ -149,7 +149,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function generate() { diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php index 107c8f2..4b31d70 100644 --- a/extensions/gii/generators/crud/Generator.php +++ b/extensions/gii/generators/crud/Generator.php @@ -9,6 +9,7 @@ namespace yii\gii\generators\crud; use Yii; use yii\db\ActiveRecord; +use yii\db\BaseActiveRecord; use yii\db\Schema; use yii\gii\CodeFile; use yii\helpers\Inflector; @@ -46,7 +47,7 @@ class Generator extends \yii\gii\Generator [['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'], [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], - [['modelClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], + [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass', 'searchModelClass'], 'validateNewClass'], @@ -69,7 +70,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function hints() { @@ -95,7 +96,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function stickyAttributes() { @@ -123,7 +124,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function generate() { @@ -167,13 +168,13 @@ class Generator extends \yii\gii\Generator public function getNameAttribute() { - /** @var \yii\db\ActiveRecord $class */ - $class = $this->modelClass; - foreach ($class::getTableSchema()->columnNames as $name) { + foreach ($this->getColumnNames() as $name) { if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { return $name; } } + /** @var \yii\db\ActiveRecord $class */ + $class = $this->modelClass; $pk = $class::primaryKey(); return $pk[0]; } @@ -185,8 +186,12 @@ class Generator extends \yii\gii\Generator public function generateActiveField($attribute) { $tableSchema = $this->getTableSchema(); - if (!isset($tableSchema->columns[$attribute])) { - return "\$form->field(\$model, '$attribute');"; + if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) { + return "\$form->field(\$model, '$attribute')->passwordInput();"; + } else { + return "\$form->field(\$model, '$attribute');"; + } } $column = $tableSchema->columns[$attribute]; if ($column->phpType === 'boolean') { @@ -214,6 +219,9 @@ class Generator extends \yii\gii\Generator public function generateActiveSearchField($attribute) { $tableSchema = $this->getTableSchema(); + if ($tableSchema === false) { + return "\$form->field(\$model, '$attribute')"; + } $column = $tableSchema->columns[$attribute]; if ($column->phpType === 'boolean') { return "\$form->field(\$model, '$attribute')->checkbox()"; @@ -249,7 +257,9 @@ class Generator extends \yii\gii\Generator */ public function generateSearchRules() { - $table = $this->getTableSchema(); + if (($table = $this->getTableSchema()) === false) { + return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"]; + } $types = []; foreach ($table->columns as $column) { switch ($column->type) { @@ -286,7 +296,7 @@ class Generator extends \yii\gii\Generator public function getSearchAttributes() { - return $this->getTableSchema()->getColumnNames(); + return $this->getColumnNames(); } /** @@ -295,17 +305,16 @@ class Generator extends \yii\gii\Generator */ public function generateSearchLabels() { - $table = $this->getTableSchema(); $labels = []; - foreach ($table->columns as $column) { - if (!strcasecmp($column->name, 'id')) { - $labels[$column->name] = 'ID'; + foreach ($this->getColumnNames() as $name) { + if (!strcasecmp($name, 'id')) { + $labels[$name] = 'ID'; } else { - $label = Inflector::camel2words($column->name); + $label = Inflector::camel2words($name); if (strcasecmp(substr($label, -3), ' id') === 0) { $label = substr($label, 0, -3) . ' ID'; } - $labels[$column->name] = $label; + $labels[$name] = $label; } } return $labels; @@ -313,10 +322,21 @@ class Generator extends \yii\gii\Generator public function generateSearchConditions() { - $table = $this->getTableSchema(); + $columns = []; + if (($table = $this->getTableSchema()) === false) { + $class = $this->modelClass; + $model = new $class(); + foreach ($model->attributes() as $attribute) { + $columns[$attribute] = 'unknown'; + } + } else { + foreach ($table->columns as $column) { + $columns[$column->name] = $column->type; + } + } $conditions = []; - foreach ($table->columns as $column) { - switch ($column->type) { + foreach ($columns as $column => $type) { + switch ($type) { case Schema::TYPE_SMALLINT: case Schema::TYPE_INTEGER: case Schema::TYPE_BIGINT: @@ -328,10 +348,11 @@ class Generator extends \yii\gii\Generator case Schema::TYPE_TIME: case Schema::TYPE_DATETIME: case Schema::TYPE_TIMESTAMP: - $conditions[] = "\$this->addCondition(\$query, '{$column->name}');"; + case + $conditions[] = "\$this->addCondition(\$query, '{$column}');"; break; default: - $conditions[] = "\$this->addCondition(\$query, '{$column->name}', true);"; + $conditions[] = "\$this->addCondition(\$query, '{$column}', true);"; break; } } @@ -369,10 +390,16 @@ class Generator extends \yii\gii\Generator public function generateActionParamComments() { - $table = $this->getTableSchema(); /** @var ActiveRecord $class */ $class = $this->modelClass; $pks = $class::primaryKey(); + if (($table = $this->getTableSchema()) === false) { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk; + } + return $params; + } if (count($pks) === 1) { return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; } else { @@ -388,6 +415,23 @@ class Generator extends \yii\gii\Generator { /** @var ActiveRecord $class */ $class = $this->modelClass; - return $class::getTableSchema(); + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema(); + } else { + return false; + } } + + public function getColumnNames() + { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema()->getColumnNames(); + } else { + $model = new $class(); + return $model->attributes(); + } + } + } diff --git a/extensions/gii/generators/crud/templates/controller.php b/extensions/gii/generators/crud/templates/controller.php index f975fd1..bb5d3f4 100644 --- a/extensions/gii/generators/crud/templates/controller.php +++ b/extensions/gii/generators/crud/templates/controller.php @@ -1,5 +1,6 @@ getTableSchema()->primaryKey; +/** @var ActiveRecordInterface $class */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); $urlParams = $generator->generateUrlParams(); $actionParams = $generator->generateActionParams(); $actionParamComments = $generator->generateActionParamComments(); @@ -29,7 +32,7 @@ namespace controllerClass, '\\')) ?> use modelClass, '\\') ?>; use searchModelClass, '\\') ?> as ; use baseControllerClass, '\\') ?>; -use yii\web\HttpException; +use yii\web\NotFoundHttpException; use yii\web\VerbFilter; /** @@ -130,7 +133,7 @@ class extends bas * 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 + * @throws NotFoundHttpException if the model cannot be found */ protected function findModel() { @@ -148,7 +151,7 @@ if (count($pks) === 1) { if (($model = ::find()) !== null) { return $model; } else { - throw new HttpException(404, 'The requested page does not exist.'); + throw new NotFoundHttpException('The requested page does not exist.'); } } } diff --git a/extensions/gii/generators/crud/templates/search.php b/extensions/gii/generators/crud/templates/search.php index 17a0024..1411896 100644 --- a/extensions/gii/generators/crud/templates/search.php +++ b/extensions/gii/generators/crud/templates/search.php @@ -40,7 +40,7 @@ class extends Model } /** - * {@inheritdoc} + * @inheritdoc */ public function attributeLabels() { diff --git a/extensions/gii/generators/crud/templates/views/_form.php b/extensions/gii/generators/crud/templates/views/_form.php index c93ef84..52538d5 100644 --- a/extensions/gii/generators/crud/templates/views/_form.php +++ b/extensions/gii/generators/crud/templates/views/_form.php @@ -12,7 +12,7 @@ use yii\helpers\StringHelper; $model = new $generator->modelClass; $safeAttributes = $model->safeAttributes(); if (empty($safeAttributes)) { - $safeAttributes = $model->getTableSchema()->columnNames; + $safeAttributes = $model->attributes(); } echo "getTableSchema()->getColumnNames() as $attribute) { +foreach ($generator->getColumnNames() as $attribute) { if (++$count < 6) { echo "\t\tgenerateActiveSearchField($attribute) . " ?>\n\n"; } else { diff --git a/extensions/gii/generators/crud/templates/views/index.php b/extensions/gii/generators/crud/templates/views/index.php index b47c3f3..3ad2138 100644 --- a/extensions/gii/generators/crud/templates/views/index.php +++ b/extensions/gii/generators/crud/templates/views/index.php @@ -45,12 +45,22 @@ $this->params['breadcrumbs'][] = $this->title; 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"; +if (($tableSchema = $generator->getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + if (++$count < 6) { + echo "\t\t\t'" . $name . "',\n"; + } else { + echo "\t\t\t// '" . $name . "',\n"; + } + } +} else { + foreach ($tableSchema->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"; + } } } ?> diff --git a/extensions/gii/generators/crud/templates/views/view.php b/extensions/gii/generators/crud/templates/views/view.php index 9b5391e..9e74aff 100644 --- a/extensions/gii/generators/crud/templates/views/view.php +++ b/extensions/gii/generators/crud/templates/views/view.php @@ -42,9 +42,15 @@ $this->params['breadcrumbs'][] = $this->title; 'model' => $model, 'attributes' => [ getTableSchema()->columns as $column) { - $format = $generator->generateColumnFormat($column); - echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; +if (($tableSchema = $generator->getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + echo "\t\t\t'" . $name . "',\n"; + } +} else { + foreach ($generator->getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } } ?> ], diff --git a/extensions/gii/generators/form/Generator.php b/extensions/gii/generators/form/Generator.php index cc4328e..3bc0be6 100644 --- a/extensions/gii/generators/form/Generator.php +++ b/extensions/gii/generators/form/Generator.php @@ -26,7 +26,7 @@ class Generator extends \yii\gii\Generator /** - * {@inheritdoc} + * @inheritdoc */ public function getName() { @@ -34,7 +34,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function getDescription() { @@ -42,7 +42,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function generate() { @@ -55,7 +55,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function rules() { @@ -72,7 +72,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function attributeLabels() { @@ -85,7 +85,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function requiredTemplates() { @@ -93,7 +93,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function stickyAttributes() { @@ -101,7 +101,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function hints() { @@ -114,7 +114,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function successMessage() { diff --git a/extensions/gii/generators/model/Generator.php b/extensions/gii/generators/model/Generator.php index 8c505f1..cd2fcbf 100644 --- a/extensions/gii/generators/model/Generator.php +++ b/extensions/gii/generators/model/Generator.php @@ -32,7 +32,7 @@ class Generator extends \yii\gii\Generator /** - * {@inheritdoc} + * @inheritdoc */ public function getName() { @@ -40,7 +40,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function getDescription() { @@ -48,7 +48,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function rules() { @@ -68,7 +68,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function attributeLabels() { @@ -84,7 +84,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function hints() { @@ -111,7 +111,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function autoCompleteData() { @@ -128,7 +128,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function requiredTemplates() { @@ -136,7 +136,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function stickyAttributes() { @@ -144,7 +144,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function generate() { diff --git a/extensions/gii/generators/model/templates/model.php b/extensions/gii/generators/model/templates/model.php index 555bee1..dcd1461 100644 --- a/extensions/gii/generators/model/templates/model.php +++ b/extensions/gii/generators/model/templates/model.php @@ -33,7 +33,7 @@ namespace ns ?>; class extends baseClass, '\\') . "\n" ?> { /** - * {@inheritdoc} + * @inheritdoc */ public static function tableName() { @@ -41,7 +41,7 @@ class extends baseClass, '\\') . } /** - * {@inheritdoc} + * @inheritdoc */ public function rules() { @@ -49,7 +49,7 @@ class extends baseClass, '\\') . } /** - * {@inheritdoc} + * @inheritdoc */ public function attributeLabels() { diff --git a/extensions/gii/generators/module/Generator.php b/extensions/gii/generators/module/Generator.php index a29555b..5946e07 100644 --- a/extensions/gii/generators/module/Generator.php +++ b/extensions/gii/generators/module/Generator.php @@ -24,7 +24,7 @@ class Generator extends \yii\gii\Generator public $moduleID; /** - * {@inheritdoc} + * @inheritdoc */ public function getName() { @@ -32,7 +32,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function getDescription() { @@ -40,7 +40,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function rules() { @@ -54,7 +54,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function attributeLabels() { @@ -65,7 +65,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function hints() { @@ -76,7 +76,7 @@ class Generator extends \yii\gii\Generator } /** - * {@inheritdoc} + * @inheritdoc */ public function successMessage() { @@ -104,7 +104,7 @@ EOD; } /** - * {@inheritdoc} + * @inheritdoc */ public function requiredTemplates() { @@ -112,7 +112,7 @@ EOD; } /** - * {@inheritdoc} + * @inheritdoc */ public function generate() { diff --git a/extensions/redis/ActiveQuery.php b/extensions/redis/ActiveQuery.php index 755fc6f..607b18e 100644 --- a/extensions/redis/ActiveQuery.php +++ b/extensions/redis/ActiveQuery.php @@ -132,7 +132,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface if ($db === null) { $db = $modelClass::getDb(); } - return $db->executeCommand('LLEN', [$modelClass::tableName()]); + return $db->executeCommand('LLEN', [$modelClass::keyPrefix()]); } else { return $this->executeScript($db, 'Count'); } @@ -296,7 +296,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface $data = []; foreach($pks as $pk) { if (++$i > $start && ($this->limit === null || $i <= $start + $this->limit)) { - $key = $modelClass::tableName() . ':a:' . $modelClass::buildKey($pk); + $key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pk); $result = $db->executeCommand('HGETALL', [$key]); if (!empty($result)) { $data[] = $result; diff --git a/extensions/redis/ActiveRecord.php b/extensions/redis/ActiveRecord.php index 8c2933c..f227ed1 100644 --- a/extensions/redis/ActiveRecord.php +++ b/extensions/redis/ActiveRecord.php @@ -9,6 +9,8 @@ namespace yii\redis; use yii\base\InvalidConfigException; use yii\base\NotSupportedException; +use yii\db\BaseActiveRecord; +use yii\helpers\Inflector; use yii\helpers\StringHelper; /** @@ -34,7 +36,7 @@ use yii\helpers\StringHelper; * @author Carsten Brandt * @since 2.0 */ -class ActiveRecord extends \yii\db\ActiveRecord +class ActiveRecord extends BaseActiveRecord { /** * Returns the database connection used by this AR class. @@ -48,7 +50,7 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * @inheritdoc */ public static function createQuery() { @@ -56,7 +58,7 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * @inheritdoc */ public static function createActiveRelation($config = []) { @@ -87,7 +89,19 @@ class ActiveRecord extends \yii\db\ActiveRecord } /** - * {@inheritdoc} + * Declares prefix of the key that represents the keys that store this records in redis. + * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]. + * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes + * 'order_item'. You may override this method if you want different key naming. + * @return string the prefix to apply to all AR keys + */ + public static function keyPrefix() + { + return Inflector::camel2id(StringHelper::basename(get_called_class()), '_'); + } + + /** + * @inheritdoc */ public function insert($runValidation = true, $attributes = null) { @@ -102,15 +116,15 @@ class ActiveRecord extends \yii\db\ActiveRecord foreach ($this->primaryKey() as $key) { $pk[$key] = $values[$key] = $this->getAttribute($key); if ($pk[$key] === null) { - $pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::tableName() . ':s:' . $key]); + $pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]); $this->setAttribute($key, $values[$key]); } } // } // save pk in a findall pool - $db->executeCommand('RPUSH', [static::tableName(), static::buildKey($pk)]); + $db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]); - $key = static::tableName() . ':a:' . static::buildKey($pk); + $key = static::keyPrefix() . ':a:' . static::buildKey($pk); // save attributes $args = [$key]; foreach($values as $attribute => $value) { @@ -150,7 +164,7 @@ class ActiveRecord extends \yii\db\ActiveRecord foreach(static::fetchPks($condition) as $pk) { $newPk = $pk; $pk = static::buildKey($pk); - $key = static::tableName() . ':a:' . $pk; + $key = static::keyPrefix() . ':a:' . $pk; // save attributes $args = [$key]; foreach($attributes as $attribute => $value) { @@ -161,13 +175,13 @@ class ActiveRecord extends \yii\db\ActiveRecord $args[] = $value; } $newPk = static::buildKey($newPk); - $newKey = static::tableName() . ':a:' . $newPk; + $newKey = static::keyPrefix() . ':a:' . $newPk; // rename index if pk changed if ($newPk != $pk) { $db->executeCommand('MULTI'); $db->executeCommand('HMSET', $args); - $db->executeCommand('LINSERT', [static::tableName(), 'AFTER', $pk, $newPk]); - $db->executeCommand('LREM', [static::tableName(), 0, $pk]); + $db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]); + $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]); $db->executeCommand('RENAME', [$key, $newKey]); $db->executeCommand('EXEC'); } else { @@ -201,7 +215,7 @@ class ActiveRecord extends \yii\db\ActiveRecord $db = static::getDb(); $n=0; foreach(static::fetchPks($condition) as $pk) { - $key = static::tableName() . ':a:' . static::buildKey($pk); + $key = static::keyPrefix() . ':a:' . static::buildKey($pk); foreach($counters as $attribute => $value) { $db->executeCommand('HINCRBY', [$key, $attribute, $value]); } @@ -233,8 +247,8 @@ class ActiveRecord extends \yii\db\ActiveRecord $db->executeCommand('MULTI'); foreach($pks as $pk) { $pk = static::buildKey($pk); - $db->executeCommand('LREM', [static::tableName(), 0, $pk]); - $attributeKeys[] = static::tableName() . ':a:' . $pk; + $db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]); + $attributeKeys[] = static::keyPrefix() . ':a:' . $pk; } if (empty($attributeKeys)) { $db->executeCommand('EXEC'); @@ -292,31 +306,4 @@ class ActiveRecord extends \yii\db\ActiveRecord } return md5(json_encode($key)); } - - /** - * {@inheritdoc} - */ - public static function getTableSchema() - { - throw new NotSupportedException('getTableSchema() is not supported by redis ActiveRecord.'); - } - - /** - * {@inheritdoc} - */ - public static function findBySql($sql, $params = []) - { - throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord.'); - } - - /** - * Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. - * This method will always return false as transactional operations are not supported by redis. - * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. - * @return boolean whether the specified operation is transactional in the current [[scenario]]. - */ - public function isTransactional($operation) - { - return false; - } } diff --git a/extensions/redis/Cache.php b/extensions/redis/Cache.php index 627acdc..0722b9d 100644 --- a/extensions/redis/Cache.php +++ b/extensions/redis/Cache.php @@ -105,7 +105,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function getValue($key) { @@ -113,7 +113,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function getValues($keys) { @@ -127,7 +127,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function setValue($key, $value, $expire) { @@ -140,7 +140,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function setValues($data, $expire) { @@ -174,7 +174,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function addValue($key, $value, $expire) { @@ -187,7 +187,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function deleteValue($key) { @@ -195,7 +195,7 @@ class Cache extends \yii\caching\Cache } /** - * {@inheritdoc} + * @inheritdoc */ protected function flushValues() { diff --git a/extensions/redis/LuaScriptBuilder.php b/extensions/redis/LuaScriptBuilder.php index 81dff3f..55bf455 100644 --- a/extensions/redis/LuaScriptBuilder.php +++ b/extensions/redis/LuaScriptBuilder.php @@ -29,7 +29,7 @@ class LuaScriptBuilder extends \yii\base\Object // TODO add support for orderBy /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks'); } @@ -43,7 +43,7 @@ class LuaScriptBuilder extends \yii\base\Object // TODO add support for orderBy /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks'); } @@ -58,7 +58,7 @@ class LuaScriptBuilder extends \yii\base\Object // TODO add support for orderBy and indexBy /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks'); } @@ -82,7 +82,7 @@ class LuaScriptBuilder extends \yii\base\Object { /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n'); } @@ -96,7 +96,7 @@ class LuaScriptBuilder extends \yii\base\Object { /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n'); } @@ -110,7 +110,7 @@ class LuaScriptBuilder extends \yii\base\Object { /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or nmodelClass; - $key = $this->quoteValue($modelClass::tableName() . ':a:'); + $key = $this->quoteValue($modelClass::keyPrefix() . ':a:'); return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v'); } @@ -152,7 +152,7 @@ class LuaScriptBuilder extends \yii\base\Object /** @var ActiveRecord $modelClass */ $modelClass = $query->modelClass; - $key = $this->quoteValue($modelClass::tableName()); + $key = $this->quoteValue($modelClass::keyPrefix()); $loadColumnValues = ''; foreach($columns as $column => $alias) { $loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, '$column')\n"; diff --git a/extensions/sphinx/ActiveQuery.php b/extensions/sphinx/ActiveQuery.php index 7ba48d2..3533c35 100644 --- a/extensions/sphinx/ActiveQuery.php +++ b/extensions/sphinx/ActiveQuery.php @@ -176,7 +176,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface } /** - * {@inheritdoc} + * @inheritdoc */ protected function defaultConnection() { @@ -201,4 +201,4 @@ class ActiveQuery extends Query implements ActiveQueryInterface } return $result; } -} \ No newline at end of file +} diff --git a/extensions/sphinx/Command.php b/extensions/sphinx/Command.php index a6c8c4f..ba802f5 100644 --- a/extensions/sphinx/Command.php +++ b/extensions/sphinx/Command.php @@ -197,7 +197,7 @@ class Command extends \yii\db\Command // Not Supported : /** - * {@inheritdoc} + * @inheritdoc */ public function createTable($table, $columns, $options = null) { @@ -205,7 +205,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function renameTable($table, $newName) { @@ -213,7 +213,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function dropTable($table) { @@ -221,7 +221,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function truncateTable($table) { @@ -229,7 +229,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function addColumn($table, $column, $type) { @@ -237,7 +237,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function dropColumn($table, $column) { @@ -245,7 +245,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function renameColumn($table, $oldName, $newName) { @@ -253,7 +253,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function alterColumn($table, $column, $type) { @@ -261,7 +261,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function addPrimaryKey($name, $table, $columns) { @@ -269,7 +269,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function dropPrimaryKey($name, $table) { @@ -277,7 +277,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) { @@ -285,7 +285,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function dropForeignKey($name, $table) { @@ -293,7 +293,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function createIndex($name, $table, $columns, $unique = false) { @@ -301,7 +301,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function dropIndex($name, $table) { @@ -309,7 +309,7 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function resetSequence($table, $value = null) { @@ -317,10 +317,10 @@ class Command extends \yii\db\Command } /** - * {@inheritdoc} + * @inheritdoc */ public function checkIntegrity($check = true, $schema = '') { throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); } -} \ No newline at end of file +} diff --git a/extensions/sphinx/Connection.php b/extensions/sphinx/Connection.php index a3492bb..b732d97 100644 --- a/extensions/sphinx/Connection.php +++ b/extensions/sphinx/Connection.php @@ -52,9 +52,6 @@ use yii\base\NotSupportedException; * * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the * sequence object. This property is read-only. - * @property Schema $schema The schema information for this Sphinx connection. This property is read-only. - * @property \yii\sphinx\QueryBuilder $queryBuilder The query builder for this Sphinx connection. This property is - * read-only. * * @author Paul Klimov * @since 2.0 @@ -62,7 +59,7 @@ use yii\base\NotSupportedException; class Connection extends \yii\db\Connection { /** - * {@inheritdoc} + * @inheritdoc */ public $schemaMap = [ 'mysqli' => 'yii\sphinx\Schema', // MySQL @@ -129,4 +126,4 @@ class Connection extends \yii\db\Connection { throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); } -} \ No newline at end of file +} diff --git a/extensions/sphinx/README.md b/extensions/sphinx/README.md index ae7c285..5f02204 100644 --- a/extensions/sphinx/README.md +++ b/extensions/sphinx/README.md @@ -1,17 +1,7 @@ -Yii 2.0 Public Preview - Sphinx Extension -========================================= +Sphinx Extension for Yii 2 +========================== -Thank you for choosing Yii - a high-performance component-based PHP framework. - -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). - -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) - -This is the yii2-sphinx extension. +This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii 2 framework. Installation @@ -20,26 +10,26 @@ Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run + ``` php composer.phar require yiisoft/yii2-sphinx "*" ``` or add -``` + +```json "yiisoft/yii2-sphinx": "*" ``` -to the require section of your composer.json. - -*Note: You might have to run `php composer.phar selfupdate`* +to the require section of your composer.json. Usage & Documentation --------------------- -This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii framework. -This extension interact with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language. +This extension interacts with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language. In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added: + ``` searchd { @@ -115,4 +105,4 @@ $provider = new ActiveDataProvider([ ] ]); $models = $provider->getModels(); -``` \ No newline at end of file +``` diff --git a/extensions/sphinx/composer.json b/extensions/sphinx/composer.json index 9a323d7..6026022 100644 --- a/extensions/sphinx/composer.json +++ b/extensions/sphinx/composer.json @@ -17,7 +17,6 @@ "email": "klimov.paul@gmail.com" } ], - "minimum-stability": "dev", "require": { "yiisoft/yii2": "*", "ext-pdo": "*", @@ -25,5 +24,6 @@ }, "autoload": { "psr-0": { "yii\\sphinx\\": "" } - } + }, + "target-dir": "yii/sphinx" } diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php index aab8434..891f2b5 100644 --- a/extensions/swiftmailer/Mailer.php +++ b/extensions/swiftmailer/Mailer.php @@ -123,7 +123,7 @@ class Mailer extends BaseMailer } /** - * {@inheritdoc} + * @inheritdoc */ protected function sendMessage($message) { diff --git a/extensions/swiftmailer/Message.php b/extensions/swiftmailer/Message.php index f7d8b34..9558d58 100644 --- a/extensions/swiftmailer/Message.php +++ b/extensions/swiftmailer/Message.php @@ -41,7 +41,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getCharset() { @@ -49,7 +49,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setCharset($charset) { @@ -58,7 +58,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getFrom() { @@ -66,7 +66,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setFrom($from) { @@ -75,7 +75,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getReplyTo() { @@ -83,7 +83,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setReplyTo($replyTo) { @@ -92,7 +92,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getTo() { @@ -100,7 +100,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setTo($to) { @@ -109,7 +109,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getCc() { @@ -117,7 +117,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setCc($cc) { @@ -126,7 +126,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getBcc() { @@ -134,7 +134,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setBcc($bcc) { @@ -143,7 +143,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function getSubject() { @@ -151,7 +151,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubject($subject) { @@ -160,7 +160,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setTextBody($text) { @@ -169,7 +169,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function setHtmlBody($html) { @@ -222,7 +222,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function attach($fileName, array $options = []) { @@ -238,7 +238,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function attachContent($content, array $options = []) { @@ -254,7 +254,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function embed($fileName, array $options = []) { @@ -269,7 +269,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function embedContent($content, array $options = []) { @@ -284,7 +284,7 @@ class Message extends BaseMessage } /** - * {@inheritdoc} + * @inheritdoc */ public function toString() { diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 90e0268..9fe5c8d 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -1,7 +1,7 @@ Yii Framework 2 Change Log ========================== -Version 2 ---------- +Version 2.0 alpha +----------------- -- Initial release. \ No newline at end of file +- Initial release. diff --git a/framework/README.md b/framework/README.md index b5c754f..0d146ce 100644 --- a/framework/README.md +++ b/framework/README.md @@ -1,21 +1,24 @@ -Yii 2.0 Public Preview -====================== +Yii PHP Framework Version 2 +=========================== -Thank you for choosing Yii - a high-performance component-based PHP framework. +This is the core framework code of [Yii 2](https://github.com/yiisoft/yii2). -If you are looking for a production-ready PHP framework, please use -[Yii v1.1](https://github.com/yiisoft/yii). -Yii 2.0 is still under heavy development. We may make significant changes -without prior notices. **Yii 2.0 is not ready for production use yet.** - -[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) +Installation +------------ +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). +Either run -REQUIREMENTS ------------- +``` +php composer.phar require yiisoft/yii2-framework "*" +``` -The minimum requirement by Yii is that your Web server supports PHP 5.4.0. +or add +```json +"yiisoft/yii2-framework": "*" +``` +to the require section of your composer.json. diff --git a/framework/composer.json b/framework/composer.json index 8260adb..bcd3927 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -1,6 +1,6 @@ { "name": "yiisoft/yii2", - "description": "Yii2 Web Programming Framework", + "description": "Yii PHP Framework Version 2", "keywords": ["yii", "framework"], "homepage": "http://www.yiiframework.com/", "type": "library", @@ -39,21 +39,6 @@ "name": "Paul Klimov", "email": "klimov.paul@gmail.com", "role": "Core framework development" - }, - { - "name": "Wei Zhuo", - "email": "weizhuo@gmail.com", - "role": "Project site maintenance and development" - }, - { - "name": "Sebastián Thierer", - "email": "sebas@artfos.com", - "role": "Component development" - }, - { - "name": "Jeffrey Winesett", - "email": "jefftulsa@gmail.com", - "role": "Documentation and marketing" } ], "support": { diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index b9f2cdd..c5904a1 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -58,14 +58,14 @@ yii = (function ($) { changeableSelector: 'select, input, textarea', /** - * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. + * @return string|undefined the CSRF variable name. Undefined is returned if 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. + * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled. */ getCsrfToken: function () { return $('meta[name=csrf-token]').prop('content'); diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 48b526c..f135b40 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -23,6 +23,7 @@ use yii\web\HttpException; * @property \yii\base\Formatter $formatter The formatter application component. This property is read-only. * @property \yii\i18n\I18N $i18n The internationalization component. This property is read-only. * @property \yii\log\Logger $log The log component. This property is read-only. + * @property \yii\mail\MailerInterface $mail The mailer interface. This property is read-only. * @property \yii\web\Request|\yii\console\Request $request The request component. This property is read-only. * @property string $runtimePath The directory that stores runtime files. Defaults to the "runtime" * subdirectory under [[basePath]]. @@ -199,12 +200,12 @@ abstract class Application extends Module } /** - * {@inheritdoc} + * @inheritdoc */ public function init() { - parent::init(); $this->initExtensions($this->extensions); + parent::init(); } /** @@ -634,9 +635,9 @@ abstract class Application extends Module { $category = get_class($exception); if ($exception instanceof HttpException) { - $category .= '\\' . $exception->statusCode; + $category = 'yii\\web\\HttpException:' . $exception->statusCode; } elseif ($exception instanceof \ErrorException) { - $category .= '\\' . $exception->getSeverity(); + $category .= ':' . $exception->getSeverity(); } Yii::error((string)$exception, $category); } diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 42a2efc..d14c9dd 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -194,7 +194,7 @@ class Controller extends Component implements ViewContextInterface $actionMap = $this->actions(); if (isset($actionMap[$id])) { return Yii::createObject($actionMap[$id], $id, $this); - } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { + } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) { $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))); if (method_exists($this, $methodName)) { $method = new \ReflectionMethod($this, $methodName); @@ -417,9 +417,13 @@ class Controller extends Component implements ViewContextInterface $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout; } - if (pathinfo($file, PATHINFO_EXTENSION) === '') { - $file .= $view->defaultExtension; + if (pathinfo($file, PATHINFO_EXTENSION) !== '') { + return $file; } - return $file; + $path = $file . '.' . $view->defaultExtension; + if ($view->defaultExtension !== 'php' && !is_file($path)) { + $path = $file . '.php'; + } + return $path; } } diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index fcfe31e..47650f0 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -67,7 +67,7 @@ class View extends Component /** * @var string the default view file extension. This will be appended to view file names if they don't have file extensions. */ - public $defaultExtension = '.php'; + public $defaultExtension = 'php'; /** * @var Theme|array the theme object or the configuration array for creating the theme object. * If not set, it means theming is not enabled. @@ -171,7 +171,14 @@ class View extends Component } } - return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . $this->defaultExtension : $file; + if (pathinfo($file, PATHINFO_EXTENSION) !== '') { + return $file; + } + $path = $file . '.' . $this->defaultExtension; + if ($this->defaultExtension !== 'php' && !is_file($path)) { + $path = $file . '.php'; + } + return $path; } /** diff --git a/framework/yii/base/Widget.php b/framework/yii/base/Widget.php index a0f7795..5e3ce2b 100644 --- a/framework/yii/base/Widget.php +++ b/framework/yii/base/Widget.php @@ -14,7 +14,8 @@ use ReflectionClass; * Widget is the base class for widgets. * * @property string $id ID of the widget. - * @property View $view The view object that can be used to render views or view files. + * @property \yii\web\View $view The view object that can be used to render views or view files. Note that the + * type of this property differs in getter and setter. See [[getView()]] and [[setView()]] for details. * @property string $viewPath The directory containing the view files for this widget. This property is * read-only. * @@ -121,7 +122,7 @@ class Widget extends Component implements ViewContextInterface * The [[render()]] and [[renderFile()]] methods will use * this view object to implement the actual view rendering. * If not set, it will default to the "view" application component. - * @return View the view object that can be used to render views or view files. + * @return \yii\web\View the view object that can be used to render views or view files. */ public function getView() { diff --git a/framework/yii/caching/WinCache.php b/framework/yii/caching/WinCache.php index 7f1eca8..6b80f3c 100644 --- a/framework/yii/caching/WinCache.php +++ b/framework/yii/caching/WinCache.php @@ -13,7 +13,7 @@ namespace yii\caching; * To use this application component, the [WinCache PHP extension](http://www.iis.net/expand/wincacheforphp) * must be loaded. Also note that "wincache.ucenabled" should be set to "On" in your php.ini file. * - * See {@link CCache} manual for common cache operations that are supported by WinCache. + * See [[Cache]] manual for common cache operations that are supported by WinCache. * * @author Qiang Xue * @since 2.0 diff --git a/framework/yii/classes.php b/framework/yii/classes.php index c8b2935..9606930 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -74,13 +74,16 @@ return [ '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\data\SqlDataProvider' => YII_PATH . '/data/SqlDataProvider.php', 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php', 'yii\db\ActiveQueryInterface' => YII_PATH . '/db/ActiveQueryInterface.php', 'yii\db\ActiveQueryTrait' => YII_PATH . '/db/ActiveQueryTrait.php', 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php', + 'yii\db\ActiveRecordInterface' => YII_PATH . '/db/ActiveRecordInterface.php', 'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php', 'yii\db\ActiveRelationInterface' => YII_PATH . '/db/ActiveRelationInterface.php', 'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php', + 'yii\db\BaseActiveRecord' => YII_PATH . '/db/BaseActiveRecord.php', 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php', 'yii\db\Command' => YII_PATH . '/db/Command.php', 'yii\db\Connection' => YII_PATH . '/db/Connection.php', @@ -192,12 +195,14 @@ return [ 'yii\validators\ValidationAsset' => YII_PATH . '/validators/ValidationAsset.php', 'yii\validators\Validator' => YII_PATH . '/validators/Validator.php', 'yii\web\AccessControl' => YII_PATH . '/web/AccessControl.php', + 'yii\web\AccessDeniedHttpException' => YII_PATH . '/web/AccessDeniedHttpException.php', 'yii\web\AccessRule' => YII_PATH . '/web/AccessRule.php', 'yii\web\Application' => YII_PATH . '/web/Application.php', 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php', 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php', + 'yii\web\BadRequestHttpException' => YII_PATH . '/web/BadRequestHttpException.php', 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php', 'yii\web\Controller' => YII_PATH . '/web/Controller.php', 'yii\web\Cookie' => YII_PATH . '/web/Cookie.php', @@ -210,6 +215,8 @@ return [ '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\MethodNotAllowedHttpException' => YII_PATH . '/web/MethodNotAllowedHttpException.php', + 'yii\web\NotFoundHttpException' => YII_PATH . '/web/NotFoundHttpException.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', diff --git a/framework/yii/console/controllers/MessageController.php b/framework/yii/console/controllers/MessageController.php index 8a0f10b..7ac1558 100644 --- a/framework/yii/console/controllers/MessageController.php +++ b/framework/yii/console/controllers/MessageController.php @@ -107,11 +107,13 @@ class MessageController extends Controller @mkdir($dir); } foreach ($messages as $category => $msgs) { + $file = str_replace("\\", '/', "$dir/$category.php"); + $path = dirname($file); + if (!is_dir($path)) { + mkdir($path, 0755, true); + } $msgs = array_values(array_unique($msgs)); - $this->generateMessageFile($msgs, $dir . DIRECTORY_SEPARATOR . $category . '.php', - $config['overwrite'], - $config['removeUnused'], - $config['sort']); + $this->generateMessageFile($msgs, $file, $config['overwrite'], $config['removeUnused'], $config['sort']); } } } diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index 0b3fc4a..2292286 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -86,14 +86,14 @@ class ActiveDataProvider extends BaseDataProvider parent::init(); if (is_string($this->db)) { $this->db = Yii::$app->getComponent($this->db); - if (!$this->db instanceof Connection) { + if ($this->db === null) { throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.'); } } } /** - * {@inheritdoc} + * @inheritdoc */ protected function prepareModels() { @@ -111,7 +111,7 @@ class ActiveDataProvider extends BaseDataProvider } /** - * {@inheritdoc} + * @inheritdoc */ protected function prepareKeys($models) { @@ -138,9 +138,9 @@ class ActiveDataProvider extends BaseDataProvider foreach ($models as $model) { $kk = []; foreach ($pks as $pk) { - $kk[] = $model[$pk]; + $kk[$pk] = $model[$pk]; } - $keys[] = json_encode($kk); + $keys[] = $kk; } } return $keys; @@ -150,7 +150,7 @@ class ActiveDataProvider extends BaseDataProvider } /** - * {@inheritdoc} + * @inheritdoc */ protected function prepareTotalCount() { @@ -162,7 +162,7 @@ class ActiveDataProvider extends BaseDataProvider } /** - * {@inheritdoc} + * @inheritdoc */ public function setSort($value) { diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index 987e364..2b694c7 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -66,7 +66,7 @@ class ArrayDataProvider extends BaseDataProvider /** - * {@inheritdoc} + * @inheritdoc */ protected function prepareModels() { @@ -87,7 +87,7 @@ class ArrayDataProvider extends BaseDataProvider } /** - * {@inheritdoc} + * @inheritdoc */ protected function prepareKeys($models) { @@ -107,7 +107,7 @@ class ArrayDataProvider extends BaseDataProvider } /** - * {@inheritdoc} + * @inheritdoc */ protected function prepareTotalCount() { diff --git a/framework/yii/data/SqlDataProvider.php b/framework/yii/data/SqlDataProvider.php new file mode 100644 index 0000000..d8f0cd1 --- /dev/null +++ b/framework/yii/data/SqlDataProvider.php @@ -0,0 +1,160 @@ +db->createCommand(' + * SELECT COUNT(*) FROM tbl_user WHERE status=:status + * ', [':status' => 1])->queryScalar(); + * + * $dataProvider = new SqlDataProvider([ + * 'sql' => 'SELECT * FROM tbl_user WHERE status=:status', + * 'params' => [':status' => 1], + * 'totalCount' => $count, + * 'sort' => [ + * 'attributes' => [ + * 'age', + * 'name' => [ + * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + * 'default' => SORT_DESC, + * 'label' => 'Name', + * ], + * ], + * ], + * 'pagination' => [ + * 'pageSize' => 20, + * ], + * ]); + * + * // get the user records in the current page + * $models = $dataProvider->getModels(); + * ~~~ + * + * Note: if you want to use the pagination feature, you must configure the [[totalCount]] property + * to be the total number of rows (without pagination). And if you want to use the sorting feature, + * you must configure the [[sort]] property so that the provider knows which columns can be sorted. + * + * @author Qiang Xue + * @since 2.0 + */ +class SqlDataProvider extends BaseDataProvider +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * If not set, the default DB connection will be used. + */ + public $db; + /** + * @var string the SQL statement to be used for fetching data rows. + */ + public $sql; + /** + * @var array parameters (name=>value) to be bound to the SQL statement. + */ + public $params = []; + /** + * @var string|callable the column that is used as the key of the data models. + * This can be either a column name, or a callable that returns the key value of a given data model. + * + * If this is not set, the keys of the [[models]] array will be used. + */ + public $key; + + + /** + * Initializes the DB connection component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.'); + } + if ($this->sql === null) { + throw new InvalidConfigException('The "sql" property must be set.'); + } + } + + /** + * @inheritdoc + */ + protected function prepareModels() + { + $sql = $this->sql; + $qb = $this->db->getQueryBuilder(); + if (($sort = $this->getSort()) !== false) { + $orderBy = $qb->buildOrderBy($sort->getOrders()); + if (!empty($orderBy)) { + $orderBy = substr($orderBy, 9); + if (preg_match('/\s+order\s+by\s+[\w\s,\.]+$/i', $sql)) { + $sql .= ', ' . $orderBy; + } else { + $sql .= ' ORDER BY ' . $orderBy; + } + } + } + + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $sql .= ' ' . $qb->buildLimit($pagination->getLimit(), $pagination->getOffset()); + } + + return $this->db->createCommand($sql, $this->params)->queryAll(); + } + + /** + * @inheritdoc + */ + protected function prepareKeys($models) + { + $keys = []; + if ($this->key !== null) { + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + return $keys; + } else { + return array_keys($models); + } + } + + /** + * @inheritdoc + */ + protected function prepareTotalCount() + { + return 0; + } +} diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index bb2016a..adcf508 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -22,62 +22,13 @@ use yii\helpers\Inflector; * * @include @yii/db/ActiveRecord.md * - * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is - * read-only. - * @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]]. - * @property array $oldAttributes The old attribute values (name-value pairs). - * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is - * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key - * value is null). This property is read-only. - * @property array $populatedRelations An array of relation data indexed by relation names. This property is - * read-only. - * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if - * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null). - * This property is read-only. - * * @author Qiang Xue * @author Carsten Brandt * @since 2.0 */ -class ActiveRecord extends Model +class ActiveRecord extends BaseActiveRecord { /** - * @event Event an event that is triggered when the record is initialized via [[init()]]. - */ - const EVENT_INIT = 'init'; - /** - * @event Event an event that is triggered after the record is created and populated with query result. - */ - const EVENT_AFTER_FIND = 'afterFind'; - /** - * @event ModelEvent an event that is triggered before inserting a record. - * You may set [[ModelEvent::isValid]] to be false to stop the insertion. - */ - const EVENT_BEFORE_INSERT = 'beforeInsert'; - /** - * @event Event an event that is triggered after a record is inserted. - */ - const EVENT_AFTER_INSERT = 'afterInsert'; - /** - * @event ModelEvent an event that is triggered before updating a record. - * You may set [[ModelEvent::isValid]] to be false to stop the update. - */ - const EVENT_BEFORE_UPDATE = 'beforeUpdate'; - /** - * @event Event an event that is triggered after a record is updated. - */ - const EVENT_AFTER_UPDATE = 'afterUpdate'; - /** - * @event ModelEvent an event that is triggered before deleting a record. - * You may set [[ModelEvent::isValid]] to be false to stop the deletion. - */ - const EVENT_BEFORE_DELETE = 'beforeDelete'; - /** - * @event Event an event that is triggered after a record is deleted. - */ - const EVENT_AFTER_DELETE = 'afterDelete'; - - /** * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. */ const OP_INSERT = 0x01; @@ -96,20 +47,6 @@ class ActiveRecord extends Model const OP_ALL = 0x07; /** - * @var array attribute values indexed by attribute names - */ - private $_attributes = []; - /** - * @var array old attribute values indexed by attribute names. - */ - private $_oldAttributes; - /** - * @var array related models indexed by the relation names - */ - private $_related = []; - - - /** * Returns the database connection used by this AR class. * By default, the "db" application component is used as the database connection. * You may override this method if you want to use a different database connection. @@ -121,41 +58,6 @@ class ActiveRecord extends Model } /** - * Creates an [[ActiveQuery]] instance for query purpose. - * - * @include @yii/db/ActiveRecord-find.md - * - * @param mixed $q the query parameter. This can be one of the followings: - * - * - a scalar value (integer or string): query by a single primary key value and return the - * corresponding record. - * - an array of name-value pairs: query by a set of column values and return a single record matching all of them. - * - null: return a new [[ActiveQuery]] object for further query purpose. - * - * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance - * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be - * returned (null will be returned if there is no matching). - * @throws InvalidConfigException if the AR class does not have a primary key - * @see createQuery() - */ - public static function find($q = null) - { - $query = static::createQuery(); - if (is_array($q)) { - return $query->where($q)->one(); - } elseif ($q !== null) { - // query by primary key - $primaryKey = static::primaryKey(); - if (isset($primaryKey[0])) { - return $query->where([$primaryKey[0] => $q])->one(); - } else { - throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); - } - } - return $query; - } - - /** * Creates an [[ActiveQuery]] instance with a given SQL statement. * * Note that because the SQL statement is already specified, calling additional @@ -309,31 +211,13 @@ class ActiveRecord extends Model } /** - * Returns the name of the column that stores the lock version for implementing optimistic locking. - * - * Optimistic locking allows multiple users to access the same record for edits and avoids - * potential conflicts. In case when a user attempts to save the record upon some staled data - * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown, - * and the update or deletion is skipped. - * - * Optimistic locking is only supported by [[update()]] and [[delete()]]. - * - * To use Optimistic locking: - * - * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`. - * Override this method to return the name of this column. - * 2. In the Web form that collects the user input, add a hidden field that stores - * the lock version of the recording being updated. - * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]] - * and implement necessary business logic (e.g. merging the changes, prompting stated data) - * to resolve the conflict. - * - * @return string the column name that stores the lock version of a table row. - * If null is returned (default implemented), optimistic locking will not be supported. + * Returns the list of all attribute names of the model. + * The default implementation will return all column names of the table associated with this AR class. + * @return array list of attribute names. */ - public function optimisticLock() + public function attributes() { - return null; + return array_keys(static::getTableSchema()->columns); } /** @@ -369,161 +253,6 @@ class ActiveRecord extends Model } /** - * PHP getter magic method. - * This method is overridden so that attributes and related objects can be accessed like properties. - * @param string $name property name - * @return mixed property value - * @see getAttribute() - */ - public function __get($name) - { - if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { - return $this->_attributes[$name]; - } elseif ($this->hasAttribute($name)) { - return null; - } else { - if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) { - return $this->_related[$name]; - } - $value = parent::__get($name); - if ($value instanceof ActiveRelationInterface) { - return $this->_related[$name] = $value->multiple ? $value->all() : $value->one(); - } else { - return $value; - } - } - } - - /** - * PHP setter magic method. - * This method is overridden so that AR attributes can be accessed like properties. - * @param string $name property name - * @param mixed $value property value - */ - public function __set($name, $value) - { - if ($this->hasAttribute($name)) { - $this->_attributes[$name] = $value; - } else { - parent::__set($name, $value); - } - } - - /** - * Checks if a property value is null. - * This method overrides the parent implementation by checking if the named attribute is null or not. - * @param string $name the property name or the event name - * @return boolean whether the property value is null - */ - public function __isset($name) - { - try { - return $this->__get($name) !== null; - } catch (\Exception $e) { - return false; - } - } - - /** - * Sets a component property to be null. - * This method overrides the parent implementation by clearing - * the specified attribute value. - * @param string $name the property name or the event name - */ - public function __unset($name) - { - if ($this->hasAttribute($name)) { - unset($this->_attributes[$name]); - } else { - if (isset($this->_related[$name])) { - unset($this->_related[$name]); - } else { - parent::__unset($name); - } - } - } - - /** - * Declares a `has-one` relation. - * The declaration is returned in terms of an [[ActiveRelation]] instance - * through which the related record can be queried and retrieved back. - * - * A `has-one` relation means that there is at most one related record matching - * the criteria set by this relation, e.g., a customer has one country. - * - * For example, to declare the `country` relation for `Customer` class, we can write - * the following code in the `Customer` class: - * - * ~~~ - * public function getCountry() - * { - * return $this->hasOne(Country::className(), ['id' => 'country_id']); - * } - * ~~~ - * - * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name - * in the related class `Country`, while the 'country_id' value refers to an attribute name - * in the current AR class. - * - * Call methods declared in [[ActiveRelation]] to further customize the relation. - * - * @param string $class the class name of the related record - * @param array $link the primary-foreign key constraint. The keys of the array refer to - * the attributes of the record associated with the `$class` model, while the values of the - * array refer to the corresponding attributes in **this** AR class. - * @return ActiveRelationInterface the relation object. - */ - public function hasOne($class, $link) - { - /** @var ActiveRecord $class */ - return $class::createActiveRelation([ - 'modelClass' => $class, - 'primaryModel' => $this, - 'link' => $link, - 'multiple' => false, - ]); - } - - /** - * Declares a `has-many` relation. - * The declaration is returned in terms of an [[ActiveRelation]] instance - * through which the related record can be queried and retrieved back. - * - * A `has-many` relation means that there are multiple related records matching - * the criteria set by this relation, e.g., a customer has many orders. - * - * For example, to declare the `orders` relation for `Customer` class, we can write - * the following code in the `Customer` class: - * - * ~~~ - * public function getOrders() - * { - * return $this->hasMany(Order::className(), ['customer_id' => 'id']); - * } - * ~~~ - * - * Note that in the above, the 'customer_id' key in the `$link` parameter refers to - * an attribute name in the related class `Order`, while the 'id' value refers to - * an attribute name in the current AR class. - * - * @param string $class the class name of the related record - * @param array $link the primary-foreign key constraint. The keys of the array refer to - * the attributes of the record associated with the `$class` model, while the values of the - * array refer to the corresponding attributes in **this** AR class. - * @return ActiveRelationInterface the relation object. - */ - public function hasMany($class, $link) - { - /** @var ActiveRecord $class */ - return $class::createActiveRelation([ - 'modelClass' => $class, - 'primaryModel' => $this, - 'link' => $link, - 'multiple' => true, - ]); - } - - /** * Creates an [[ActiveRelation]] instance. * This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance. * You may override this method to return a customized relation. @@ -536,208 +265,6 @@ class ActiveRecord extends Model } /** - * Populates the named relation with the related records. - * Note that this method does not check if the relation exists or not. - * @param string $name the relation name (case-sensitive) - * @param ActiveRecord|array|null the related records to be populated into the relation. - */ - public function populateRelation($name, $records) - { - $this->_related[$name] = $records; - } - - /** - * Check whether the named relation has been populated with records. - * @param string $name the relation name (case-sensitive) - * @return bool whether relation has been populated with records. - */ - public function isRelationPopulated($name) - { - return array_key_exists($name, $this->_related); - } - - /** - * Returns all populated relations. - * @return array an array of relation data indexed by relation names. - */ - public function getPopulatedRelations() - { - return $this->_related; - } - - /** - * Returns the list of all attribute names of the model. - * The default implementation will return all column names of the table associated with this AR class. - * @return array list of attribute names. - */ - public function attributes() - { - return array_keys(static::getTableSchema()->columns); - } - - /** - * Returns a value indicating whether the model has an attribute with the specified name. - * @param string $name the name of the attribute - * @return boolean whether the model has an attribute with the specified name. - */ - public function hasAttribute($name) - { - return isset($this->_attributes[$name]) || in_array($name, $this->attributes()); - } - - /** - * Returns the named attribute value. - * If this record is the result of a query and the attribute is not loaded, - * null will be returned. - * @param string $name the attribute name - * @return mixed the attribute value. Null if the attribute is not set or does not exist. - * @see hasAttribute() - */ - public function getAttribute($name) - { - return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; - } - - /** - * Sets the named attribute value. - * @param string $name the attribute name - * @param mixed $value the attribute value. - * @throws InvalidParamException if the named attribute does not exist. - * @see hasAttribute() - */ - public function setAttribute($name, $value) - { - if ($this->hasAttribute($name)) { - $this->_attributes[$name] = $value; - } else { - throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); - } - } - - /** - * Returns the old attribute values. - * @return array the old attribute values (name-value pairs) - */ - public function getOldAttributes() - { - return $this->_oldAttributes === null ? [] : $this->_oldAttributes; - } - - /** - * Sets the old attribute values. - * All existing old attribute values will be discarded. - * @param array $values old attribute values to be set. - */ - public function setOldAttributes($values) - { - $this->_oldAttributes = $values; - } - - /** - * Returns the old value of the named attribute. - * If this record is the result of a query and the attribute is not loaded, - * null will be returned. - * @param string $name the attribute name - * @return mixed the old attribute value. Null if the attribute is not loaded before - * or does not exist. - * @see hasAttribute() - */ - public function getOldAttribute($name) - { - return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; - } - - /** - * Sets the old value of the named attribute. - * @param string $name the attribute name - * @param mixed $value the old attribute value. - * @throws InvalidParamException if the named attribute does not exist. - * @see hasAttribute() - */ - public function setOldAttribute($name, $value) - { - if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) { - $this->_oldAttributes[$name] = $value; - } else { - throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); - } - } - - /** - * Returns a value indicating whether the named attribute has been changed. - * @param string $name the name of the attribute - * @return boolean whether the attribute has been changed - */ - public function isAttributeChanged($name) - { - if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) { - return $this->_attributes[$name] !== $this->_oldAttributes[$name]; - } else { - return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]); - } - } - - /** - * Returns the attribute values that have been modified since they are loaded or saved most recently. - * @param string[]|null $names the names of the attributes whose values may be returned if they are - * changed recently. If null, [[attributes()]] will be used. - * @return array the changed attribute values (name-value pairs) - */ - public function getDirtyAttributes($names = null) - { - if ($names === null) { - $names = $this->attributes(); - } - $names = array_flip($names); - $attributes = []; - if ($this->_oldAttributes === null) { - foreach ($this->_attributes as $name => $value) { - if (isset($names[$name])) { - $attributes[$name] = $value; - } - } - } else { - foreach ($this->_attributes as $name => $value) { - if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) { - $attributes[$name] = $value; - } - } - } - return $attributes; - } - - /** - * Saves the current record. - * - * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]] - * when [[isNewRecord]] is false. - * - * For example, to save a customer record: - * - * ~~~ - * $customer = new Customer; // or $customer = Customer::find($id); - * $customer->name = $name; - * $customer->email = $email; - * $customer->save(); - * ~~~ - * - * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be saved to database. - * @param array $attributes list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the saving succeeds - */ - public function save($runValidation = true, $attributes = null) - { - if ($this->getIsNewRecord()) { - return $this->insert($runValidation, $attributes); - } else { - return $this->update($runValidation, $attributes) !== false; - } - } - - /** * Inserts a row into the associated database table using the attribute values of this record. * * This method performs the following steps in order: @@ -810,8 +337,8 @@ class ActiveRecord extends Model } $values = $this->getDirtyAttributes($attributes); if (empty($values)) { - foreach ($this->primaryKey() as $key) { - $values[$key] = isset($this->_attributes[$key]) ? $this->_attributes[$key] : null; + foreach ($this->getPrimaryKey(true) as $key => $value) { + $values[$key] = $value; } } $db = static::getDb(); @@ -822,14 +349,16 @@ class ActiveRecord extends Model $table = $this->getTableSchema(); if ($table->sequenceName !== null) { foreach ($table->primaryKey as $name) { - if (!isset($this->_attributes[$name])) { - $this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName); + if ($this->getAttribute($name) === null) { + $id = $db->getLastInsertID($table->sequenceName); + $this->setAttribute($name, $id); + $this->setOldAttribute($name, $id); break; } } } foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $value; + $this->setOldAttribute($name, $value); } $this->afterSave(true); return true; @@ -911,73 +440,6 @@ class ActiveRecord extends Model } /** - * @see CActiveRecord::update() - * @throws StaleObjectException - */ - private function updateInternal($attributes = null) - { - if (!$this->beforeSave(false)) { - return false; - } - $values = $this->getDirtyAttributes($attributes); - if (empty($values)) { - $this->afterSave(false); - return 0; - } - $condition = $this->getOldPrimaryKey(true); - $lock = $this->optimisticLock(); - if ($lock !== null) { - if (!isset($values[$lock])) { - $values[$lock] = $this->$lock + 1; - } - $condition[$lock] = $this->$lock; - } - // We do not check the return value of updateAll() because it's possible - // that the UPDATE statement doesn't change anything and thus returns 0. - $rows = $this->updateAll($values, $condition); - - if ($lock !== null && !$rows) { - throw new StaleObjectException('The object being updated is outdated.'); - } - - foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $this->_attributes[$name]; - } - $this->afterSave(false); - return $rows; - } - - /** - * Updates one or several counter columns for the current AR object. - * Note that this method differs from [[updateAllCounters()]] in that it only - * saves counters for the current AR object. - * - * An example usage is as follows: - * - * ~~~ - * $post = Post::find($id); - * $post->updateCounters(['view_count' => 1]); - * ~~~ - * - * @param array $counters the counters to be updated (attribute name => increment value) - * Use negative values if you want to decrement the counters. - * @return boolean whether the saving is successful - * @see updateAllCounters() - */ - public function updateCounters($counters) - { - if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) { - foreach ($counters as $name => $value) { - $this->_attributes[$name] += $value; - $this->_oldAttributes[$name] = $this->_attributes[$name]; - } - return true; - } else { - return false; - } - } - - /** * Deletes the table row corresponding to this active record. * * This method performs the following steps in order: @@ -1014,7 +476,7 @@ class ActiveRecord extends Model if ($lock !== null && !$result) { throw new StaleObjectException('The object being deleted is outdated.'); } - $this->_oldAttributes = null; + $this->setOldAttributes(null); $this->afterDelete(); } if ($transaction !== null) { @@ -1034,149 +496,6 @@ class ActiveRecord extends Model } /** - * Returns a value indicating whether the current record is new. - * @return boolean whether the record is new and should be inserted when calling [[save()]]. - */ - public function getIsNewRecord() - { - return $this->_oldAttributes === null; - } - - /** - * 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. - * If you override this method, make sure you call the parent implementation at the end - * to ensure triggering of the event. - */ - public function init() - { - parent::init(); - $this->trigger(self::EVENT_INIT); - } - - /** - * This method is called when the AR object is created and populated with the query result. - * The default implementation will trigger an [[EVENT_AFTER_FIND]] event. - * When overriding this method, make sure you call the parent implementation to ensure the - * event is triggered. - */ - public function afterFind() - { - $this->trigger(self::EVENT_AFTER_FIND); - } - - /** - * 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. - * When overriding this method, make sure you call the parent implementation like the following: - * - * ~~~ - * public function beforeSave($insert) - * { - * if (parent::beforeSave($insert)) { - * // ...custom code here... - * return true; - * } else { - * return false; - * } - * } - * ~~~ - * - * @param boolean $insert whether this method called while inserting a record. - * If false, it means the method is called while updating a record. - * @return boolean whether the insertion or updating should continue. - * If false, the insertion or updating will be cancelled. - */ - public function beforeSave($insert) - { - $event = new ModelEvent; - $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event); - return $event->isValid; - } - - /** - * This method is called at the end of inserting or updating a record. - * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true, - * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. - * When overriding this method, make sure you call the parent implementation so that - * the event is triggered. - * @param boolean $insert whether this method called while inserting a record. - * If false, it means the method is called while updating a record. - */ - public function afterSave($insert) - { - $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE); - } - - /** - * This method is invoked before deleting a record. - * The default implementation raises the [[EVENT_BEFORE_DELETE]] event. - * When overriding this method, make sure you call the parent implementation like the following: - * - * ~~~ - * public function beforeDelete() - * { - * if (parent::beforeDelete()) { - * // ...custom code here... - * return true; - * } else { - * return false; - * } - * } - * ~~~ - * - * @return boolean whether the record should be deleted. Defaults to true. - */ - public function beforeDelete() - { - $event = new ModelEvent; - $this->trigger(self::EVENT_BEFORE_DELETE, $event); - return $event->isValid; - } - - /** - * This method is invoked after deleting a record. - * The default implementation raises the [[EVENT_AFTER_DELETE]] event. - * You may override this method to do postprocessing after the record is deleted. - * Make sure you call the parent implementation so that the event is raised properly. - */ - public function afterDelete() - { - $this->trigger(self::EVENT_AFTER_DELETE); - } - - /** - * Repopulates this active record with the latest data. - * @return boolean whether the row still exists in the database. If true, the latest data - * will be populated to this active record. Otherwise, this record will remain unchanged. - */ - public function refresh() - { - $record = $this->find($this->getPrimaryKey(true)); - if ($record === null) { - return false; - } - foreach ($this->attributes() as $name) { - $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null; - } - $this->_oldAttributes = $this->_attributes; - $this->_related = []; - return true; - } - - /** * Returns a value indicating whether the given active record is the same as the current one. * The comparison is made by comparing the table names and the primary key values of the two active records. * If one of the records [[isNewRecord|is new]] they are also considered not equal. @@ -1192,348 +511,6 @@ class ActiveRecord extends Model } /** - * Returns the primary key value(s). - * @param boolean $asArray whether to return the primary key value as an array. If true, - * the return value will be an array with column names as keys and column values as values. - * Note that for composite primary keys, an array will always be returned regardless of this parameter value. - * @property mixed The primary key value. An array (column name => column value) is returned if - * the primary key is composite. A string is returned otherwise (null will be returned if - * the key value is null). - * @return mixed the primary key value. An array (column name => column value) is returned if the primary key - * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if - * the key value is null). - */ - public function getPrimaryKey($asArray = false) - { - $keys = $this->primaryKey(); - if (count($keys) === 1 && !$asArray) { - return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; - } else { - $values = []; - foreach ($keys as $name) { - $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; - } - return $values; - } - } - - /** - * Returns the old primary key value(s). - * This refers to the primary key value that is populated into the record - * after executing a find method (e.g. find(), findAll()). - * The value remains unchanged even if the primary key attribute is manually assigned with a different value. - * @param boolean $asArray whether to return the primary key value as an array. If true, - * the return value will be an array with column name as key and column value as value. - * If this is false (default), a scalar value will be returned for non-composite primary key. - * @property mixed The old primary key value. An array (column name => column value) is - * returned if the primary key is composite. A string is returned otherwise (null will be - * returned if the key value is null). - * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key - * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if - * the key value is null). - */ - public function getOldPrimaryKey($asArray = false) - { - $keys = $this->primaryKey(); - if (count($keys) === 1 && !$asArray) { - return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; - } else { - $values = []; - foreach ($keys as $name) { - $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; - } - return $values; - } - } - - /** - * Creates an active record object using a row of data. - * This method is called by [[ActiveQuery]] to populate the query results - * into Active Records. It is not meant to be used to create new records. - * @param array $row attribute values (name => value) - * @return ActiveRecord the newly created active record. - */ - public static function create($row) - { - $record = static::instantiate($row); - $columns = array_flip($record->attributes()); - foreach ($row as $name => $value) { - if (isset($columns[$name])) { - $record->_attributes[$name] = $value; - } else { - $record->$name = $value; - } - } - $record->_oldAttributes = $record->_attributes; - $record->afterFind(); - return $record; - } - - /** - * Creates an active record instance. - * This method is called by [[create()]]. - * You may override this method if the instance being created - * depends on the row data to be populated into the record. - * For example, by creating a record based on the value of a column, - * you may implement the so-called single-table inheritance mapping. - * @param array $row row data to be populated into the record. - * @return ActiveRecord the newly created active record - */ - public static function instantiate($row) - { - return new static; - } - - /** - * Returns whether there is an element at the specified offset. - * This method is required by the interface ArrayAccess. - * @param mixed $offset the offset to check on - * @return boolean whether there is an element at the specified offset. - */ - public function offsetExists($offset) - { - return $this->__isset($offset); - } - - /** - * Returns the relation object with the specified name. - * A relation is defined by a getter method which returns an [[ActiveRelation]] object. - * It can be declared in either the Active Record class itself or one of its behaviors. - * @param string $name the relation name - * @return ActiveRelation the relation object - * @throws InvalidParamException if the named relation does not exist. - */ - public function getRelation($name) - { - $getter = 'get' . $name; - try { - $relation = $this->$getter(); - if ($relation instanceof ActiveRelationInterface) { - return $relation; - } else { - throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".'); - } - } catch (UnknownMethodException $e) { - throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); - } - } - - /** - * Establishes the relationship between two models. - * - * The relationship is established by setting the foreign key value(s) in one model - * to be the corresponding primary key value(s) in the other model. - * The model with the foreign key will be saved into database without performing validation. - * - * If the relationship involves a pivot table, a new row will be inserted into the - * pivot table which contains the primary key values from both models. - * - * Note that this method requires that the primary key value is not null. - * - * @param string $name the case sensitive name of the relationship - * @param ActiveRecord $model the model to be linked with the current one. - * @param array $extraColumns additional column values to be saved into the pivot table. - * This parameter is only meaningful for a relationship involving a pivot table - * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.) - * @throws InvalidCallException if the method is unable to link two models. - */ - public function link($name, $model, $extraColumns = []) - { - $relation = $this->getRelation($name); - - if ($relation->via !== null) { - if ($this->getIsNewRecord() || $model->getIsNewRecord()) { - throw new InvalidCallException('Unable to link models: both models must NOT be newly created.'); - } - if (is_array($relation->via)) { - /** @var ActiveRelation $viaRelation */ - list($viaName, $viaRelation) = $relation->via; - $viaClass = $viaRelation->modelClass; - // unset $viaName so that it can be reloaded to reflect the change - unset($this->_related[$viaName]); - } else { - $viaRelation = $relation->via; - $viaTable = reset($relation->via->from); - } - $columns = []; - foreach ($viaRelation->link as $a => $b) { - $columns[$a] = $this->$b; - } - foreach ($relation->link as $a => $b) { - $columns[$b] = $model->$a; - } - foreach ($extraColumns as $k => $v) { - $columns[$k] = $v; - } - if (is_array($relation->via)) { - /** @var $viaClass ActiveRecord */ - /** @var $record ActiveRecord */ - $record = new $viaClass(); - foreach($columns as $column => $value) { - $record->$column = $value; - } - $record->insert(false); - } else { - /** @var $viaTable string */ - static::getDb()->createCommand() - ->insert($viaTable, $columns)->execute(); - } - } else { - $p1 = $model->isPrimaryKey(array_keys($relation->link)); - $p2 = $this->isPrimaryKey(array_values($relation->link)); - if ($p1 && $p2) { - if ($this->getIsNewRecord() && $model->getIsNewRecord()) { - throw new InvalidCallException('Unable to link models: both models are newly created.'); - } elseif ($this->getIsNewRecord()) { - $this->bindModels(array_flip($relation->link), $this, $model); - } else { - $this->bindModels($relation->link, $model, $this); - } - } elseif ($p1) { - $this->bindModels(array_flip($relation->link), $this, $model); - } elseif ($p2) { - $this->bindModels($relation->link, $model, $this); - } else { - throw new InvalidCallException('Unable to link models: the link does not involve any primary key.'); - } - } - - // update lazily loaded related objects - if (!$relation->multiple) { - $this->_related[$name] = $model; - } elseif (isset($this->_related[$name])) { - if ($relation->indexBy !== null) { - $indexBy = $relation->indexBy; - $this->_related[$name][$model->$indexBy] = $model; - } else { - $this->_related[$name][] = $model; - } - } - } - - /** - * Destroys the relationship between two models. - * - * The model with the foreign key of the relationship will be deleted if `$delete` is true. - * Otherwise, the foreign key will be set null and the model will be saved without validation. - * - * @param string $name the case sensitive name of the relationship. - * @param ActiveRecord $model the model to be unlinked from the current one. - * @param boolean $delete whether to delete the model that contains the foreign key. - * If false, the model's foreign key will be set null and saved. - * If true, the model containing the foreign key will be deleted. - * @throws InvalidCallException if the models cannot be unlinked - */ - public function unlink($name, $model, $delete = false) - { - $relation = $this->getRelation($name); - - if ($relation->via !== null) { - if (is_array($relation->via)) { - /** @var ActiveRelation $viaRelation */ - list($viaName, $viaRelation) = $relation->via; - $viaClass = $viaRelation->modelClass; - unset($this->_related[$viaName]); - } else { - $viaRelation = $relation->via; - $viaTable = reset($relation->via->from); - } - $columns = []; - foreach ($viaRelation->link as $a => $b) { - $columns[$a] = $this->$b; - } - foreach ($relation->link as $a => $b) { - $columns[$b] = $model->$a; - } - if (is_array($relation->via)) { - /** @var $viaClass ActiveRecord */ - if ($delete) { - $viaClass::deleteAll($columns); - } else { - $nulls = []; - foreach (array_keys($columns) as $a) { - $nulls[$a] = null; - } - $viaClass::updateAll($nulls, $columns); - } - } else { - /** @var $viaTable string */ - $command = static::getDb()->createCommand(); - if ($delete) { - $command->delete($viaTable, $columns)->execute(); - } else { - $nulls = []; - foreach (array_keys($columns) as $a) { - $nulls[$a] = null; - } - $command->update($viaTable, $nulls, $columns)->execute(); - } - } - } else { - $p1 = $model->isPrimaryKey(array_keys($relation->link)); - $p2 = $this->isPrimaryKey(array_values($relation->link)); - if ($p1 && $p2 || $p2) { - foreach ($relation->link as $a => $b) { - $model->$a = null; - } - $delete ? $model->delete() : $model->save(false); - } elseif ($p1) { - foreach ($relation->link as $b) { - $this->$b = null; - } - $delete ? $this->delete() : $this->save(false); - } else { - throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.'); - } - } - - if (!$relation->multiple) { - unset($this->_related[$name]); - } elseif (isset($this->_related[$name])) { - /** @var ActiveRecord $b */ - foreach ($this->_related[$name] as $a => $b) { - if ($model->getPrimaryKey() == $b->getPrimaryKey()) { - unset($this->_related[$name][$a]); - } - } - } - } - - /** - * @param array $link - * @param ActiveRecord $foreignModel - * @param ActiveRecord $primaryModel - * @throws InvalidCallException - */ - private function bindModels($link, $foreignModel, $primaryModel) - { - foreach ($link as $fk => $pk) { - $value = $primaryModel->$pk; - if ($value === null) { - throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.'); - } - $foreignModel->$fk = $value; - } - $foreignModel->save(false); - } - - /** - * Returns a value indicating whether the given set of attributes represents the primary key for this model - * @param array $keys the set of attributes to check - * @return boolean whether the given set of attributes represents the primary key for this model - */ - public static function isPrimaryKey($keys) - { - $pks = static::primaryKey(); - foreach ($keys as $key) { - if (!in_array($key, $pks, true)) { - return false; - } - } - return count($keys) === count($pks); - } - - /** * Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. * @return boolean whether the specified operation is transactional in the current [[scenario]]. diff --git a/framework/yii/db/ActiveRecordInterface.php b/framework/yii/db/ActiveRecordInterface.php new file mode 100644 index 0000000..4965a26 --- /dev/null +++ b/framework/yii/db/ActiveRecordInterface.php @@ -0,0 +1,279 @@ + + */ + +namespace yii\db; + +/** + * ActiveRecordInterface + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +interface ActiveRecordInterface +{ + /** + * Returns the primary key **name(s)** for this AR class. + * + * Note that an array should be returned even when the record only has a single primary key. + * + * For the primary key **value** see [[getPrimaryKey()]] instead. + * + * @return string[] the primary key name(s) for this AR class. + */ + public static function primaryKey(); + + /** + * Returns the list of all attribute names of the record. + * @return array list of attribute names. + */ + public function attributes(); + + /** + * Returns the named attribute value. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * @param string $name the attribute name + * @return mixed the attribute value. Null if the attribute is not set or does not exist. + * @see hasAttribute() + */ + public function getAttribute($name); + + /** + * Sets the named attribute value. + * @param string $name the attribute name. + * @param mixed $value the attribute value. + * @see hasAttribute() + */ + public function setAttribute($name, $value); + + /** + * Returns a value indicating whether the record has an attribute with the specified name. + * @param string $name the name of the attribute + * @return boolean whether the record has an attribute with the specified name. + */ + public function hasAttribute($name); + + /** + * Returns the primary key value(s). + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with attribute names as keys and attribute values as values. + * Note that for composite primary keys, an array will always be returned regardless of this parameter value. + * @return mixed the primary key value. An array (attribute name => attribute value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + */ + public function getPrimaryKey($asArray = false); + + /** + * Creates an [[ActiveQueryInterface|ActiveQuery]] instance for query purpose. + * + * This method is usually ment to be used like this: + * + * ```php + * Customer::find(1); // find one customer by primary key + * Customer::find()->all(); // find all customers + * ``` + * + * @param mixed $q the query parameter. This can be one of the followings: + * + * - a scalar value (integer or string): query by a single primary key value and return the + * corresponding record. + * - an array of name-value pairs: query by a set of attribute values and return a single record matching all of them. + * - null (not specified): return a new [[ActiveQuery]] object for further query purpose. + * + * @return ActiveQueryInterface|static|null When `$q` is null, a new [[ActiveQuery]] instance + * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be + * returned (null will be returned if there is no matching). + */ + public static function find($q = null); + + /** + * Creates an [[ActiveQueryInterface|ActiveQuery]] instance. + * This method is called by [[find()]] to start a SELECT query. + * You may override this method to return a customized query (e.g. `CustomerQuery` specified + * written for querying `Customer` purpose.) + * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. + */ + public static function createQuery(); + + /** + * Updates records using the provided attribute values and conditions. + * For example, to change the status to be 1 for all customers whose status is 2: + * + * ~~~ + * Customer::updateAll(['status' => 1], ['status' => '2']); + * ~~~ + * + * @param array $attributes attribute values (name-value pairs) to be saved for the record. + * Unlike [[update()]] these are not going to be validated. + * @param array $condition the condition that matches the records that should get updated. + * Please refer to [[QueryInterface::where()]] on how to specify this parameter. + * An empty condition will match all records. + * @return integer the number of rows updated + */ + public static function updateAll($attributes, $condition = null); + + /** + * Deletes records using the provided conditions. + * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. + * + * For example, to delete all customers whose status is 3: + * + * ~~~ + * Customer::deleteAll([status = 3]); + * ~~~ + * + * @param array $condition the condition that matches the records that should get deleted. + * Please refer to [[QueryInterface::where()]] on how to specify this parameter. + * An empty condition will match all records. + * @return integer the number of rows deleted + */ + public static function deleteAll($condition = null); + + /** + * Saves the current record. + * + * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]] + * when [[isNewRecord]] is false. + * + * For example, to save a customer record: + * + * ~~~ + * $customer = new Customer; // or $customer = Customer::find($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->save(); + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be saved to database. `false` will be returned + * in this case. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the saving succeeds + */ + public function save($runValidation = true, $attributes = null); + + /** + * Inserts the record into the database using the attribute values of this record. + * + * Usage example: + * + * ```php + * $customer = new Customer; + * $customer->name = $name; + * $customer->email = $email; + * $customer->insert(); + * ``` + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the attributes are valid and the record is inserted successfully. + */ + public function insert($runValidation = true, $attributes = null); + + /** + * Saves the changes to this active record into the database. + * + * Usage example: + * + * ```php + * $customer = Customer::find($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->update(); + * ``` + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return integer|boolean the number of rows affected, or false if validation fails + * or updating process is stopped for other reasons. + * Note that it is possible that the number of rows affected is 0, even though the + * update execution is successful. + */ + public function update($runValidation = true, $attributes = null); + + /** + * Deletes the record from the database. + * + * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful. + */ + public function delete(); + + /** + * Returns a value indicating whether the current record is new (not saved in the database). + * @return boolean whether the record is new and should be inserted when calling [[save()]]. + */ + public function getIsNewRecord(); + + /** + * Returns a value indicating whether the given active record is the same as the current one. + * Two [[isNewRecord|new]] records are considered to be not equal. + * @param static $record record to compare to + * @return boolean whether the two active records refer to the same row in the same database table. + */ + public function equals($record); + + /** + * Creates an [[ActiveRelationInterface|ActiveRelation]] instance. + * This method is called by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to + * create a relation instance. + * You may override this method to return a customized relation. + * @param array $config the configuration passed to the ActiveRelation class. + * @return ActiveRelation the newly created [[ActiveRelation]] instance. + */ + public static function createActiveRelation($config = []); + + /** + * Returns the relation object with the specified name. + * A relation is defined by a getter method which returns an [[ActiveRelationInterface|ActiveRelation]] object. + * It can be declared in either the ActiveRecord class itself or one of its behaviors. + * @param string $name the relation name + * @return ActiveRelation the relation object + */ + public function getRelation($name); + + /** + * Establishes the relationship between two records. + * + * The relationship is established by setting the foreign key value(s) in one record + * to be the corresponding primary key value(s) in the other record. + * The record with the foreign key will be saved into database without performing validation. + * + * If the relationship involves a pivot table, a new row will be inserted into the + * pivot table which contains the primary key values from both records. + * + * This method requires that the primary key value is not null. + * + * @param string $name the case sensitive name of the relationship. + * @param static $model the record to be linked with the current one. + * @param array $extraColumns additional column values to be saved into the pivot table. + * This parameter is only meaningful for a relationship involving a pivot table + * (i.e., a relation set with `[[ActiveRelationInterface::via()]]`.) + */ + public function link($name, $model, $extraColumns = []); + + /** + * Destroys the relationship between two records. + * + * The record with the foreign key of the relationship will be deleted if `$delete` is true. + * Otherwise, the foreign key will be set null and the record will be saved without validation. + * + * @param string $name the case sensitive name of the relationship. + * @param static $model the model to be unlinked from the current one. + * @param boolean $delete whether to delete the model that contains the foreign key. + * If false, the model's foreign key will be set null and saved. + * If true, the model containing the foreign key will be deleted. + */ + public function unlink($name, $model, $delete = false); +} \ No newline at end of file diff --git a/framework/yii/db/ActiveRelationTrait.php b/framework/yii/db/ActiveRelationTrait.php index 2960196..832bb62 100644 --- a/framework/yii/db/ActiveRelationTrait.php +++ b/framework/yii/db/ActiveRelationTrait.php @@ -104,7 +104,7 @@ trait ActiveRelationTrait if (count($primaryModels) === 1 && !$this->multiple) { $model = $this->one(); foreach ($primaryModels as $i => $primaryModel) { - if ($primaryModel instanceof ActiveRecord) { + if ($primaryModel instanceof ActiveRecordInterface) { $primaryModel->populateRelation($name, $model); } else { $primaryModels[$i][$name] = $model; @@ -123,7 +123,7 @@ trait ActiveRelationTrait foreach ($primaryModels as $i => $primaryModel) { $key = $this->getModelKey($primaryModel, $link); $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null); - if ($primaryModel instanceof ActiveRecord) { + if ($primaryModel instanceof ActiveRecordInterface) { $primaryModel->populateRelation($name, $value); } else { $primaryModels[$i][$name] = $value; diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/yii/db/BaseActiveRecord.php new file mode 100644 index 0000000..00360c4 --- /dev/null +++ b/framework/yii/db/BaseActiveRecord.php @@ -0,0 +1,1229 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +use yii\base\InvalidConfigException; +use yii\base\Model; +use yii\base\InvalidParamException; +use yii\base\ModelEvent; +use yii\base\NotSupportedException; +use yii\base\UnknownMethodException; +use yii\base\InvalidCallException; +use yii\helpers\StringHelper; +use yii\helpers\Inflector; + +/** + * ActiveRecord is the base class for classes representing relational data in terms of objects. + * + * @include @yii/db/ActiveRecord.md + * + * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is + * read-only. + * @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]]. + * @property array $oldAttributes The old attribute values (name-value pairs). + * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is + * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key + * value is null). This property is read-only. + * @property array $populatedRelations An array of relation data indexed by relation names. This property is + * read-only. + * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if + * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null). + * This property is read-only. + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +abstract class BaseActiveRecord extends Model implements ActiveRecordInterface +{ + /** + * @event Event an event that is triggered when the record is initialized via [[init()]]. + */ + const EVENT_INIT = 'init'; + /** + * @event Event an event that is triggered after the record is created and populated with query result. + */ + const EVENT_AFTER_FIND = 'afterFind'; + /** + * @event ModelEvent an event that is triggered before inserting a record. + * You may set [[ModelEvent::isValid]] to be false to stop the insertion. + */ + const EVENT_BEFORE_INSERT = 'beforeInsert'; + /** + * @event Event an event that is triggered after a record is inserted. + */ + const EVENT_AFTER_INSERT = 'afterInsert'; + /** + * @event ModelEvent an event that is triggered before updating a record. + * You may set [[ModelEvent::isValid]] to be false to stop the update. + */ + const EVENT_BEFORE_UPDATE = 'beforeUpdate'; + /** + * @event Event an event that is triggered after a record is updated. + */ + const EVENT_AFTER_UPDATE = 'afterUpdate'; + /** + * @event ModelEvent an event that is triggered before deleting a record. + * You may set [[ModelEvent::isValid]] to be false to stop the deletion. + */ + const EVENT_BEFORE_DELETE = 'beforeDelete'; + /** + * @event Event an event that is triggered after a record is deleted. + */ + const EVENT_AFTER_DELETE = 'afterDelete'; + + /** + * @var array attribute values indexed by attribute names + */ + private $_attributes = []; + /** + * @var array old attribute values indexed by attribute names. + */ + private $_oldAttributes; + /** + * @var array related models indexed by the relation names + */ + private $_related = []; + + + /** + * Creates an [[ActiveQuery]] instance for query purpose. + * + * @include @yii/db/ActiveRecord-find.md + * + * @param mixed $q the query parameter. This can be one of the followings: + * + * - a scalar value (integer or string): query by a single primary key value and return the + * corresponding record. + * - an array of name-value pairs: query by a set of column values and return a single record matching all of them. + * - null: return a new [[ActiveQuery]] object for further query purpose. + * + * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance + * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be + * returned (null will be returned if there is no matching). + * @throws InvalidConfigException if the AR class does not have a primary key + * @see createQuery() + */ + public static function find($q = null) + { + $query = static::createQuery(); + if (is_array($q)) { + return $query->where($q)->one(); + } elseif ($q !== null) { + // query by primary key + $primaryKey = static::primaryKey(); + if (isset($primaryKey[0])) { + return $query->where([$primaryKey[0] => $q])->one(); + } else { + throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); + } + } + return $query; + } + + /** + * Updates the whole table using the provided attribute values and conditions. + * For example, to change the status to be 1 for all customers whose status is 2: + * + * ~~~ + * Customer::updateAll(['status' => 1], 'status = 2'); + * ~~~ + * + * @param array $attributes attribute values (name-value pairs) to be saved into the table + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return integer the number of rows updated + */ + public static function updateAll($attributes, $condition = '') + { + throw new NotSupportedException(__METHOD__ . ' is not supported.'); + } + + /** + * Updates the whole table using the provided counter changes and conditions. + * For example, to increment all customers' age by 1, + * + * ~~~ + * Customer::updateAllCounters(['age' => 1]); + * ~~~ + * + * @param array $counters the counters to be updated (attribute name => increment value). + * Use negative values if you want to decrement the counters. + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method. + * @return integer the number of rows updated + */ + public static function updateAllCounters($counters, $condition = '') + { + throw new NotSupportedException(__METHOD__ . ' is not supported.'); + } + + /** + * Deletes rows in the table using the provided conditions. + * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. + * + * For example, to delete all customers whose status is 3: + * + * ~~~ + * Customer::deleteAll('status = 3'); + * ~~~ + * + * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return integer the number of rows deleted + */ + public static function deleteAll($condition = '', $params = []) + { + throw new NotSupportedException(__METHOD__ . ' is not supported.'); + } + + /** + * Returns the name of the column that stores the lock version for implementing optimistic locking. + * + * Optimistic locking allows multiple users to access the same record for edits and avoids + * potential conflicts. In case when a user attempts to save the record upon some staled data + * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown, + * and the update or deletion is skipped. + * + * Optimistic locking is only supported by [[update()]] and [[delete()]]. + * + * To use Optimistic locking: + * + * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`. + * Override this method to return the name of this column. + * 2. In the Web form that collects the user input, add a hidden field that stores + * the lock version of the recording being updated. + * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]] + * and implement necessary business logic (e.g. merging the changes, prompting stated data) + * to resolve the conflict. + * + * @return string the column name that stores the lock version of a table row. + * If null is returned (default implemented), optimistic locking will not be supported. + */ + public function optimisticLock() + { + return null; + } + + /** + * PHP getter magic method. + * This method is overridden so that attributes and related objects can be accessed like properties. + * @param string $name property name + * @return mixed property value + * @see getAttribute() + */ + public function __get($name) + { + if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { + return $this->_attributes[$name]; + } elseif ($this->hasAttribute($name)) { + return null; + } else { + if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) { + return $this->_related[$name]; + } + $value = parent::__get($name); + if ($value instanceof ActiveRelationInterface) { + return $this->_related[$name] = $value->multiple ? $value->all() : $value->one(); + } else { + return $value; + } + } + } + + /** + * PHP setter magic method. + * This method is overridden so that AR attributes can be accessed like properties. + * @param string $name property name + * @param mixed $value property value + */ + public function __set($name, $value) + { + if ($this->hasAttribute($name)) { + $this->_attributes[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + /** + * Checks if a property value is null. + * This method overrides the parent implementation by checking if the named attribute is null or not. + * @param string $name the property name or the event name + * @return boolean whether the property value is null + */ + public function __isset($name) + { + try { + return $this->__get($name) !== null; + } catch (\Exception $e) { + return false; + } + } + + /** + * Sets a component property to be null. + * This method overrides the parent implementation by clearing + * the specified attribute value. + * @param string $name the property name or the event name + */ + public function __unset($name) + { + if ($this->hasAttribute($name)) { + unset($this->_attributes[$name]); + } else { + if (isset($this->_related[$name])) { + unset($this->_related[$name]); + } else { + parent::__unset($name); + } + } + } + + /** + * Declares a `has-one` relation. + * The declaration is returned in terms of an [[ActiveRelation]] instance + * through which the related record can be queried and retrieved back. + * + * A `has-one` relation means that there is at most one related record matching + * the criteria set by this relation, e.g., a customer has one country. + * + * For example, to declare the `country` relation for `Customer` class, we can write + * the following code in the `Customer` class: + * + * ~~~ + * public function getCountry() + * { + * return $this->hasOne(Country::className(), ['id' => 'country_id']); + * } + * ~~~ + * + * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name + * in the related class `Country`, while the 'country_id' value refers to an attribute name + * in the current AR class. + * + * Call methods declared in [[ActiveRelation]] to further customize the relation. + * + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the attributes of the record associated with the `$class` model, while the values of the + * array refer to the corresponding attributes in **this** AR class. + * @return ActiveRelationInterface the relation object. + */ + public function hasOne($class, $link) + { + /** @var ActiveRecord $class */ + return $class::createActiveRelation([ + 'modelClass' => $class, + 'primaryModel' => $this, + 'link' => $link, + 'multiple' => false, + ]); + } + + /** + * Declares a `has-many` relation. + * The declaration is returned in terms of an [[ActiveRelation]] instance + * through which the related record can be queried and retrieved back. + * + * A `has-many` relation means that there are multiple related records matching + * the criteria set by this relation, e.g., a customer has many orders. + * + * For example, to declare the `orders` relation for `Customer` class, we can write + * the following code in the `Customer` class: + * + * ~~~ + * public function getOrders() + * { + * return $this->hasMany(Order::className(), ['customer_id' => 'id']); + * } + * ~~~ + * + * Note that in the above, the 'customer_id' key in the `$link` parameter refers to + * an attribute name in the related class `Order`, while the 'id' value refers to + * an attribute name in the current AR class. + * + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the attributes of the record associated with the `$class` model, while the values of the + * array refer to the corresponding attributes in **this** AR class. + * @return ActiveRelationInterface the relation object. + */ + public function hasMany($class, $link) + { + /** @var ActiveRecord $class */ + return $class::createActiveRelation([ + 'modelClass' => $class, + 'primaryModel' => $this, + 'link' => $link, + 'multiple' => true, + ]); + } + + /** + * Populates the named relation with the related records. + * Note that this method does not check if the relation exists or not. + * @param string $name the relation name (case-sensitive) + * @param ActiveRecord|array|null the related records to be populated into the relation. + */ + public function populateRelation($name, $records) + { + $this->_related[$name] = $records; + } + + /** + * Check whether the named relation has been populated with records. + * @param string $name the relation name (case-sensitive) + * @return bool whether relation has been populated with records. + */ + public function isRelationPopulated($name) + { + return array_key_exists($name, $this->_related); + } + + /** + * Returns all populated relations. + * @return array an array of relation data indexed by relation names. + */ + public function getPopulatedRelations() + { + return $this->_related; + } + + /** + * Returns a value indicating whether the model has an attribute with the specified name. + * @param string $name the name of the attribute + * @return boolean whether the model has an attribute with the specified name. + */ + public function hasAttribute($name) + { + return isset($this->_attributes[$name]) || in_array($name, $this->attributes()); + } + + /** + * Returns the named attribute value. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * @param string $name the attribute name + * @return mixed the attribute value. Null if the attribute is not set or does not exist. + * @see hasAttribute() + */ + public function getAttribute($name) + { + return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; + } + + /** + * Sets the named attribute value. + * @param string $name the attribute name + * @param mixed $value the attribute value. + * @throws InvalidParamException if the named attribute does not exist. + * @see hasAttribute() + */ + public function setAttribute($name, $value) + { + if ($this->hasAttribute($name)) { + $this->_attributes[$name] = $value; + } else { + throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); + } + } + + /** + * Returns the old attribute values. + * @return array the old attribute values (name-value pairs) + */ + public function getOldAttributes() + { + return $this->_oldAttributes === null ? [] : $this->_oldAttributes; + } + + /** + * Sets the old attribute values. + * All existing old attribute values will be discarded. + * @param array $values old attribute values to be set. + */ + public function setOldAttributes($values) + { + $this->_oldAttributes = $values; + } + + /** + * Returns the old value of the named attribute. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * @param string $name the attribute name + * @return mixed the old attribute value. Null if the attribute is not loaded before + * or does not exist. + * @see hasAttribute() + */ + public function getOldAttribute($name) + { + return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; + } + + /** + * Sets the old value of the named attribute. + * @param string $name the attribute name + * @param mixed $value the old attribute value. + * @throws InvalidParamException if the named attribute does not exist. + * @see hasAttribute() + */ + public function setOldAttribute($name, $value) + { + if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) { + $this->_oldAttributes[$name] = $value; + } else { + throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); + } + } + + /** + * Returns a value indicating whether the named attribute has been changed. + * @param string $name the name of the attribute + * @return boolean whether the attribute has been changed + */ + public function isAttributeChanged($name) + { + if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) { + return $this->_attributes[$name] !== $this->_oldAttributes[$name]; + } else { + return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]); + } + } + + /** + * Returns the attribute values that have been modified since they are loaded or saved most recently. + * @param string[]|null $names the names of the attributes whose values may be returned if they are + * changed recently. If null, [[attributes()]] will be used. + * @return array the changed attribute values (name-value pairs) + */ + public function getDirtyAttributes($names = null) + { + if ($names === null) { + $names = $this->attributes(); + } + $names = array_flip($names); + $attributes = []; + if ($this->_oldAttributes === null) { + foreach ($this->_attributes as $name => $value) { + if (isset($names[$name])) { + $attributes[$name] = $value; + } + } + } else { + foreach ($this->_attributes as $name => $value) { + if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) { + $attributes[$name] = $value; + } + } + } + return $attributes; + } + + /** + * Saves the current record. + * + * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]] + * when [[isNewRecord]] is false. + * + * For example, to save a customer record: + * + * ~~~ + * $customer = new Customer; // or $customer = Customer::find($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->save(); + * ~~~ + * + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be saved to database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the saving succeeds + */ + public function save($runValidation = true, $attributes = null) + { + if ($this->getIsNewRecord()) { + return $this->insert($runValidation, $attributes); + } else { + return $this->update($runValidation, $attributes) !== false; + } + } + + /** + * Saves the changes to this active record into the associated database table. + * + * This method performs the following steps in order: + * + * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation + * fails, it will skip the rest of the steps; + * 2. call [[afterValidate()]] when `$runValidation` is true. + * 3. call [[beforeSave()]]. If the method returns false, it will skip the + * rest of the steps; + * 4. save the record into database. If this fails, it will skip the rest of the steps; + * 5. call [[afterSave()]]; + * + * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], + * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]] + * will be raised by the corresponding methods. + * + * Only the [[changedAttributes|changed attribute values]] will be saved into database. + * + * For example, to update a customer record: + * + * ~~~ + * $customer = Customer::find($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->update(); + * ~~~ + * + * Note that it is possible the update does not affect any row in the table. + * In this case, this method will return 0. For this reason, you should use the following + * code to check if update() is successful or not: + * + * ~~~ + * if ($this->update() !== false) { + * // update successful + * } else { + * // update failed + * } + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return integer|boolean the number of rows affected, or false if validation fails + * or [[beforeSave()]] stops the updating process. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being updated is outdated. + * @throws \Exception in case update failed. + */ + public function update($runValidation = true, $attributes = null) + { + if ($runValidation && !$this->validate($attributes)) { + return false; + } + return $this->updateInternal($attributes); + } + + /** + * @see CActiveRecord::update() + * @throws StaleObjectException + */ + protected function updateInternal($attributes = null) + { + if (!$this->beforeSave(false)) { + return false; + } + $values = $this->getDirtyAttributes($attributes); + if (empty($values)) { + $this->afterSave(false); + return 0; + } + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + if (!isset($values[$lock])) { + $values[$lock] = $this->$lock + 1; + } + $condition[$lock] = $this->$lock; + } + // We do not check the return value of updateAll() because it's possible + // that the UPDATE statement doesn't change anything and thus returns 0. + $rows = $this->updateAll($values, $condition); + + if ($lock !== null && !$rows) { + throw new StaleObjectException('The object being updated is outdated.'); + } + + foreach ($values as $name => $value) { + $this->_oldAttributes[$name] = $this->_attributes[$name]; + } + $this->afterSave(false); + return $rows; + } + + /** + * Updates one or several counter columns for the current AR object. + * Note that this method differs from [[updateAllCounters()]] in that it only + * saves counters for the current AR object. + * + * An example usage is as follows: + * + * ~~~ + * $post = Post::find($id); + * $post->updateCounters(['view_count' => 1]); + * ~~~ + * + * @param array $counters the counters to be updated (attribute name => increment value) + * Use negative values if you want to decrement the counters. + * @return boolean whether the saving is successful + * @see updateAllCounters() + */ + public function updateCounters($counters) + { + if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) { + foreach ($counters as $name => $value) { + $this->_attributes[$name] += $value; + $this->_oldAttributes[$name] = $this->_attributes[$name]; + } + return true; + } else { + return false; + } + } + + /** + * Deletes the table row corresponding to this active record. + * + * This method performs the following steps in order: + * + * 1. call [[beforeDelete()]]. If the method returns false, it will skip the + * rest of the steps; + * 2. delete the record from the database; + * 3. call [[afterDelete()]]. + * + * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] + * will be raised by the corresponding methods. + * + * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being deleted is outdated. + * @throws \Exception in case delete failed. + */ + public function delete() + { + $result = false; + if ($this->beforeDelete()) { + // we do not check the return value of deleteAll() because it's possible + // the record is already deleted in the database and thus the method will return 0 + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + $condition[$lock] = $this->$lock; + } + $result = $this->deleteAll($condition); + if ($lock !== null && !$result) { + throw new StaleObjectException('The object being deleted is outdated.'); + } + $this->_oldAttributes = null; + $this->afterDelete(); + } + return $result; + } + + /** + * Returns a value indicating whether the current record is new. + * @return boolean whether the record is new and should be inserted when calling [[save()]]. + */ + public function getIsNewRecord() + { + return $this->_oldAttributes === null; + } + + /** + * 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. + * If you override this method, make sure you call the parent implementation at the end + * to ensure triggering of the event. + */ + public function init() + { + parent::init(); + $this->trigger(self::EVENT_INIT); + } + + /** + * This method is called when the AR object is created and populated with the query result. + * The default implementation will trigger an [[EVENT_AFTER_FIND]] event. + * When overriding this method, make sure you call the parent implementation to ensure the + * event is triggered. + */ + public function afterFind() + { + $this->trigger(self::EVENT_AFTER_FIND); + } + + /** + * 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. + * When overriding this method, make sure you call the parent implementation like the following: + * + * ~~~ + * public function beforeSave($insert) + * { + * if (parent::beforeSave($insert)) { + * // ...custom code here... + * return true; + * } else { + * return false; + * } + * } + * ~~~ + * + * @param boolean $insert whether this method called while inserting a record. + * If false, it means the method is called while updating a record. + * @return boolean whether the insertion or updating should continue. + * If false, the insertion or updating will be cancelled. + */ + public function beforeSave($insert) + { + $event = new ModelEvent; + $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event); + return $event->isValid; + } + + /** + * This method is called at the end of inserting or updating a record. + * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true, + * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. + * When overriding this method, make sure you call the parent implementation so that + * the event is triggered. + * @param boolean $insert whether this method called while inserting a record. + * If false, it means the method is called while updating a record. + */ + public function afterSave($insert) + { + $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE); + } + + /** + * This method is invoked before deleting a record. + * The default implementation raises the [[EVENT_BEFORE_DELETE]] event. + * When overriding this method, make sure you call the parent implementation like the following: + * + * ~~~ + * public function beforeDelete() + * { + * if (parent::beforeDelete()) { + * // ...custom code here... + * return true; + * } else { + * return false; + * } + * } + * ~~~ + * + * @return boolean whether the record should be deleted. Defaults to true. + */ + public function beforeDelete() + { + $event = new ModelEvent; + $this->trigger(self::EVENT_BEFORE_DELETE, $event); + return $event->isValid; + } + + /** + * This method is invoked after deleting a record. + * The default implementation raises the [[EVENT_AFTER_DELETE]] event. + * You may override this method to do postprocessing after the record is deleted. + * Make sure you call the parent implementation so that the event is raised properly. + */ + public function afterDelete() + { + $this->trigger(self::EVENT_AFTER_DELETE); + } + + /** + * Repopulates this active record with the latest data. + * @return boolean whether the row still exists in the database. If true, the latest data + * will be populated to this active record. Otherwise, this record will remain unchanged. + */ + public function refresh() + { + $record = $this->find($this->getPrimaryKey(true)); + if ($record === null) { + return false; + } + foreach ($this->attributes() as $name) { + $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null; + } + $this->_oldAttributes = $this->_attributes; + $this->_related = []; + return true; + } + + /** + * Returns a value indicating whether the given active record is the same as the current one. + * The comparison is made by comparing the table names and the primary key values of the two active records. + * If one of the records [[isNewRecord|is new]] they are also considered not equal. + * @param ActiveRecord $record record to compare to + * @return boolean whether the two active records refer to the same row in the same database table. + */ + public function equals($record) + { + if ($this->isNewRecord || $record->isNewRecord) { + return false; + } + return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey(); + } + + /** + * Returns the primary key value(s). + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column names as keys and column values as values. + * Note that for composite primary keys, an array will always be returned regardless of this parameter value. + * @property mixed The primary key value. An array (column name => column value) is returned if + * the primary key is composite. A string is returned otherwise (null will be returned if + * the key value is null). + * @return mixed the primary key value. An array (column name => column value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + */ + public function getPrimaryKey($asArray = false) + { + $keys = $this->primaryKey(); + if (count($keys) === 1 && !$asArray) { + return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; + } else { + $values = []; + foreach ($keys as $name) { + $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; + } + return $values; + } + } + + /** + * Returns the old primary key value(s). + * This refers to the primary key value that is populated into the record + * after executing a find method (e.g. find(), findAll()). + * The value remains unchanged even if the primary key attribute is manually assigned with a different value. + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column name as key and column value as value. + * If this is false (default), a scalar value will be returned for non-composite primary key. + * @property mixed The old primary key value. An array (column name => column value) is + * returned if the primary key is composite. A string is returned otherwise (null will be + * returned if the key value is null). + * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + */ + public function getOldPrimaryKey($asArray = false) + { + $keys = $this->primaryKey(); + if (count($keys) === 1 && !$asArray) { + return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; + } else { + $values = []; + foreach ($keys as $name) { + $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; + } + return $values; + } + } + + /** + * Creates an active record object using a row of data. + * This method is called by [[ActiveQuery]] to populate the query results + * into Active Records. It is not meant to be used to create new records. + * @param array $row attribute values (name => value) + * @return ActiveRecord the newly created active record. + */ + public static function create($row) + { + $record = static::instantiate($row); + $columns = array_flip($record->attributes()); + foreach ($row as $name => $value) { + if (isset($columns[$name])) { + $record->_attributes[$name] = $value; + } else { + $record->$name = $value; + } + } + $record->_oldAttributes = $record->_attributes; + $record->afterFind(); + return $record; + } + + /** + * Creates an active record instance. + * This method is called by [[create()]]. + * You may override this method if the instance being created + * depends on the row data to be populated into the record. + * For example, by creating a record based on the value of a column, + * you may implement the so-called single-table inheritance mapping. + * @param array $row row data to be populated into the record. + * @return ActiveRecord the newly created active record + */ + public static function instantiate($row) + { + return new static; + } + + /** + * Returns whether there is an element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to check on + * @return boolean whether there is an element at the specified offset. + */ + public function offsetExists($offset) + { + return $this->__isset($offset); + } + + /** + * Returns the relation object with the specified name. + * A relation is defined by a getter method which returns an [[ActiveRelation]] object. + * It can be declared in either the Active Record class itself or one of its behaviors. + * @param string $name the relation name + * @return ActiveRelation the relation object + * @throws InvalidParamException if the named relation does not exist. + */ + public function getRelation($name) + { + $getter = 'get' . $name; + try { + $relation = $this->$getter(); + if ($relation instanceof ActiveRelationInterface) { + return $relation; + } else { + throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".'); + } + } catch (UnknownMethodException $e) { + throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); + } + } + + /** + * Establishes the relationship between two models. + * + * The relationship is established by setting the foreign key value(s) in one model + * to be the corresponding primary key value(s) in the other model. + * The model with the foreign key will be saved into database without performing validation. + * + * If the relationship involves a pivot table, a new row will be inserted into the + * pivot table which contains the primary key values from both models. + * + * Note that this method requires that the primary key value is not null. + * + * @param string $name the case sensitive name of the relationship + * @param ActiveRecord $model the model to be linked with the current one. + * @param array $extraColumns additional column values to be saved into the pivot table. + * This parameter is only meaningful for a relationship involving a pivot table + * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.) + * @throws InvalidCallException if the method is unable to link two models. + */ + public function link($name, $model, $extraColumns = []) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if ($this->getIsNewRecord() || $model->getIsNewRecord()) { + throw new InvalidCallException('Unable to link models: both models must NOT be newly created.'); + } + if (is_array($relation->via)) { + /** @var ActiveRelation $viaRelation */ + list($viaName, $viaRelation) = $relation->via; + $viaClass = $viaRelation->modelClass; + // unset $viaName so that it can be reloaded to reflect the change + unset($this->_related[$viaName]); + } else { + $viaRelation = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = []; + foreach ($viaRelation->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + foreach ($extraColumns as $k => $v) { + $columns[$k] = $v; + } + if (is_array($relation->via)) { + /** @var $viaClass ActiveRecord */ + /** @var $record ActiveRecord */ + $record = new $viaClass(); + foreach($columns as $column => $value) { + $record->$column = $value; + } + $record->insert(false); + } else { + /** @var $viaTable string */ + static::getDb()->createCommand() + ->insert($viaTable, $columns)->execute(); + } + } else { + $p1 = $model->isPrimaryKey(array_keys($relation->link)); + $p2 = $this->isPrimaryKey(array_values($relation->link)); + if ($p1 && $p2) { + if ($this->getIsNewRecord() && $model->getIsNewRecord()) { + throw new InvalidCallException('Unable to link models: both models are newly created.'); + } elseif ($this->getIsNewRecord()) { + $this->bindModels(array_flip($relation->link), $this, $model); + } else { + $this->bindModels($relation->link, $model, $this); + } + } elseif ($p1) { + $this->bindModels(array_flip($relation->link), $this, $model); + } elseif ($p2) { + $this->bindModels($relation->link, $model, $this); + } else { + throw new InvalidCallException('Unable to link models: the link does not involve any primary key.'); + } + } + + // update lazily loaded related objects + if (!$relation->multiple) { + $this->_related[$name] = $model; + } elseif (isset($this->_related[$name])) { + if ($relation->indexBy !== null) { + $indexBy = $relation->indexBy; + $this->_related[$name][$model->$indexBy] = $model; + } else { + $this->_related[$name][] = $model; + } + } + } + + /** + * Destroys the relationship between two models. + * + * The model with the foreign key of the relationship will be deleted if `$delete` is true. + * Otherwise, the foreign key will be set null and the model will be saved without validation. + * + * @param string $name the case sensitive name of the relationship. + * @param ActiveRecord $model the model to be unlinked from the current one. + * @param boolean $delete whether to delete the model that contains the foreign key. + * If false, the model's foreign key will be set null and saved. + * If true, the model containing the foreign key will be deleted. + * @throws InvalidCallException if the models cannot be unlinked + */ + public function unlink($name, $model, $delete = false) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if (is_array($relation->via)) { + /** @var ActiveRelation $viaRelation */ + list($viaName, $viaRelation) = $relation->via; + $viaClass = $viaRelation->modelClass; + unset($this->_related[$viaName]); + } else { + $viaRelation = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = []; + foreach ($viaRelation->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + if (is_array($relation->via)) { + /** @var $viaClass ActiveRecord */ + if ($delete) { + $viaClass::deleteAll($columns); + } else { + $nulls = []; + foreach (array_keys($columns) as $a) { + $nulls[$a] = null; + } + $viaClass::updateAll($nulls, $columns); + } + } else { + /** @var $viaTable string */ + $command = static::getDb()->createCommand(); + if ($delete) { + $command->delete($viaTable, $columns)->execute(); + } else { + $nulls = []; + foreach (array_keys($columns) as $a) { + $nulls[$a] = null; + } + $command->update($viaTable, $nulls, $columns)->execute(); + } + } + } else { + $p1 = $model->isPrimaryKey(array_keys($relation->link)); + $p2 = $this->isPrimaryKey(array_values($relation->link)); + if ($p1 && $p2 || $p2) { + foreach ($relation->link as $a => $b) { + $model->$a = null; + } + $delete ? $model->delete() : $model->save(false); + } elseif ($p1) { + foreach ($relation->link as $b) { + $this->$b = null; + } + $delete ? $this->delete() : $this->save(false); + } else { + throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.'); + } + } + + if (!$relation->multiple) { + unset($this->_related[$name]); + } elseif (isset($this->_related[$name])) { + /** @var ActiveRecord $b */ + foreach ($this->_related[$name] as $a => $b) { + if ($model->getPrimaryKey() == $b->getPrimaryKey()) { + unset($this->_related[$name][$a]); + } + } + } + } + + /** + * @param array $link + * @param ActiveRecord $foreignModel + * @param ActiveRecord $primaryModel + * @throws InvalidCallException + */ + private function bindModels($link, $foreignModel, $primaryModel) + { + foreach ($link as $fk => $pk) { + $value = $primaryModel->$pk; + if ($value === null) { + throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.'); + } + $foreignModel->$fk = $value; + } + $foreignModel->save(false); + } + + /** + * Returns a value indicating whether the given set of attributes represents the primary key for this model + * @param array $keys the set of attributes to check + * @return boolean whether the given set of attributes represents the primary key for this model + */ + public static function isPrimaryKey($keys) + { + $pks = static::primaryKey(); + foreach ($keys as $key) { + if (!in_array($key, $pks, true)) { + return false; + } + } + return count($keys) === count($pks); + } +} diff --git a/framework/yii/db/Migration.php b/framework/yii/db/Migration.php index 37fdf3f..79a5abc 100644 --- a/framework/yii/db/Migration.php +++ b/framework/yii/db/Migration.php @@ -312,7 +312,7 @@ class Migration extends \yii\base\Component * Builds and executes a SQL statement for changing the definition of a column. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. * @param string $column the name of the column to be changed. The name will be properly quoted by the method. - * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) + * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any) * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. */ diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php index 20d13a8..870c66d 100644 --- a/framework/yii/db/Query.php +++ b/framework/yii/db/Query.php @@ -266,6 +266,10 @@ class Query extends Component implements QueryInterface * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id"). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). + * + * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should + * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts. + * * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. * @return static the query object itself diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 45bd4a2..9acf91f 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -69,7 +69,7 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** - * {@inheritdoc} + * @inheritdoc */ public function buildLimit($limit, $offset) { diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 9cb321f..e9481a4 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -142,7 +142,7 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** - * {@inheritdoc} + * @inheritdoc */ public function buildLimit($limit, $offset) { diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index ae049e7..bddc436 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -255,7 +255,7 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** - * {@inheritdoc} + * @inheritdoc */ public function buildLimit($limit, $offset) { diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php index 2ee1db2..707d411 100644 --- a/framework/yii/grid/ActionColumn.php +++ b/framework/yii/grid/ActionColumn.php @@ -32,27 +32,27 @@ class ActionColumn extends Column protected function initDefaultButtons() { if (!isset($this->buttons['view'])) { - $this->buttons['view'] = function ($model, $column) { + $this->buttons['view'] = function ($model, $key, $index, $column) { /** @var ActionColumn $column */ - $url = $column->createUrl($model, 'view'); + $url = $column->createUrl($model, $key, $index, 'view'); return Html::a('', $url, [ 'title' => Yii::t('yii', 'View'), ]); }; } if (!isset($this->buttons['update'])) { - $this->buttons['update'] = function ($model, $column) { + $this->buttons['update'] = function ($model, $key, $index, $column) { /** @var ActionColumn $column */ - $url = $column->createUrl($model, 'update'); + $url = $column->createUrl($model, $key, $index, 'update'); return Html::a('', $url, [ 'title' => Yii::t('yii', 'Update'), ]); }; } if (!isset($this->buttons['delete'])) { - $this->buttons['delete'] = function ($model, $column) { + $this->buttons['delete'] = function ($model, $key, $index, $column) { /** @var ActionColumn $column */ - $url = $column->createUrl($model, 'delete'); + $url = $column->createUrl($model, $key, $index, 'delete'); return Html::a('', $url, [ 'title' => Yii::t('yii', 'Delete'), 'data-confirm' => Yii::t('yii', 'Are you sure to delete this item?'), @@ -64,34 +64,30 @@ class ActionColumn extends Column /** * @param \yii\db\ActiveRecord $model + * @param mixed $key the key associated with the data model + * @param integer $index * @param string $action * @return string */ - public function createUrl($model, $action) + public function createUrl($model, $key, $index, $action) { if ($this->urlCreator instanceof Closure) { - return call_user_func($this->urlCreator, $model, $action); + return call_user_func($this->urlCreator, $model, $key, $index, $action); } else { - $params = $model->getPrimaryKey(true); - if (count($params) === 1) { - $params = ['id' => reset($params)]; - } + $params = is_array($key) ? $key : ['id' => $key]; return Yii::$app->controller->createUrl($action, $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 + * @inheritdoc */ - protected function renderDataCellContent($model, $index) + protected function renderDataCellContent($model, $key, $index) { - return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model) { + return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $key, $index) { $name = $matches[1]; if (isset($this->buttons[$name])) { - return call_user_func($this->buttons[$name], $model, $this); + return call_user_func($this->buttons[$name], $model, $key, $index, $this); } else { return ''; } diff --git a/framework/yii/grid/CheckboxColumn.php b/framework/yii/grid/CheckboxColumn.php index d029648..6970d4b 100644 --- a/framework/yii/grid/CheckboxColumn.php +++ b/framework/yii/grid/CheckboxColumn.php @@ -44,7 +44,7 @@ class CheckboxColumn extends Column /** * Renders the header cell content. - * The default implementation simply renders {@link header}. + * The default implementation simply renders [[header]]. * This method may be overridden to customize the rendering of the header cell. * @return string the rendering result */ @@ -67,15 +67,12 @@ class CheckboxColumn extends Column } /** - * 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 + * @inheritdoc */ - protected function renderDataCellContent($model, $index) + protected function renderDataCellContent($model, $key, $index) { if ($this->checkboxOptions instanceof Closure) { - $options = call_user_func($this->checkboxOptions, $model, $index, $this); + $options = call_user_func($this->checkboxOptions, $model, $key, $index, $this); } else { $options = $this->checkboxOptions; } diff --git a/framework/yii/grid/Column.php b/framework/yii/grid/Column.php index ec0c886..5cc4c42 100644 --- a/framework/yii/grid/Column.php +++ b/framework/yii/grid/Column.php @@ -71,17 +71,18 @@ class Column extends Object /** * Renders a data cell. * @param mixed $model the data model being rendered + * @param mixed $key the key associated with the data model * @param integer $index the zero-based index of the data item among the item array returned by [[dataProvider]]. * @return string the rendering result */ - public function renderDataCell($model, $index) + public function renderDataCell($model, $key, $index) { if ($this->contentOptions instanceof Closure) { - $options = call_user_func($this->contentOptions, $model, $index, $this); + $options = call_user_func($this->contentOptions, $model, $key, $index, $this); } else { $options = $this->contentOptions; } - return Html::tag('td', $this->renderDataCellContent($model, $index), $options); + return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options); } /** @@ -94,7 +95,7 @@ class Column extends Object /** * Renders the header cell content. - * The default implementation simply renders {@link header}. + * The default implementation simply renders [[header]]. * This method may be overridden to customize the rendering of the header cell. * @return string the rendering result */ @@ -105,7 +106,7 @@ class Column extends Object /** * Renders the footer cell content. - * The default implementation simply renders {@link footer}. + * The default implementation simply renders [[footer]]. * This method may be overridden to customize the rendering of the footer cell. * @return string the rendering result */ @@ -117,13 +118,14 @@ class Column extends Object /** * Renders the data cell content. * @param mixed $model the data model + * @param mixed $key the key associated with 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) + protected function renderDataCellContent($model, $key, $index) { if ($this->content !== null) { - return call_user_func($this->content, $model, $index, $this); + return call_user_func($this->content, $model, $key, $index, $this); } else { return $this->grid->emptyCell; } diff --git a/framework/yii/grid/DataColumn.php b/framework/yii/grid/DataColumn.php index bd6eacb..d51af8f 100644 --- a/framework/yii/grid/DataColumn.php +++ b/framework/yii/grid/DataColumn.php @@ -133,14 +133,17 @@ class DataColumn extends Column } } - protected function renderDataCellContent($model, $index) + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) { if ($this->value !== null) { $value = call_user_func($this->value, $model, $index, $this); } elseif ($this->content === null && $this->attribute !== null) { $value = ArrayHelper::getValue($model, $this->attribute); } else { - return parent::renderDataCellContent($model, $index); + return parent::renderDataCellContent($model, $key, $index); } return $this->grid->formatter->format($value, $this->format); } diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php index de99a18..35d89b2 100644 --- a/framework/yii/grid/GridView.php +++ b/framework/yii/grid/GridView.php @@ -162,7 +162,7 @@ class GridView extends BaseListView /** * Initializes the grid view. - * This method will initialize required property values and instantiate {@link columns} objects. + * This method will initialize required property values and instantiate [[columns]] objects. */ public function init() { @@ -366,14 +366,14 @@ class GridView extends BaseListView $cells = []; /** @var Column $column */ foreach ($this->columns as $column) { - $cells[] = $column->renderDataCell($model, $index); + $cells[] = $column->renderDataCell($model, $key, $index); } if ($this->rowOptions instanceof Closure) { $options = call_user_func($this->rowOptions, $model, $key, $index, $this); } else { $options = $this->rowOptions; } - $options['data-key'] = $key; + $options['data-key'] = is_array($key) ? json_encode($key) : $key; return Html::tag('tr', implode('', $cells), $options); } diff --git a/framework/yii/grid/SerialColumn.php b/framework/yii/grid/SerialColumn.php index 6a875ae..8179ead 100644 --- a/framework/yii/grid/SerialColumn.php +++ b/framework/yii/grid/SerialColumn.php @@ -18,12 +18,9 @@ class SerialColumn extends Column public $header = '#'; /** - * 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 + * @inheritdoc */ - protected function renderDataCellContent($model, $index) + protected function renderDataCellContent($model, $key, $index) { $pagination = $this->grid->dataProvider->getPagination(); if ($pagination !== false) { diff --git a/framework/yii/helpers/BaseConsole.php b/framework/yii/helpers/BaseConsole.php index 480badf..92f7c46 100644 --- a/framework/yii/helpers/BaseConsole.php +++ b/framework/yii/helpers/BaseConsole.php @@ -142,7 +142,7 @@ class BaseConsole /** * Saves the current cursor position by sending ANSI control code SCP to the terminal. - * Position can then be restored with {@link restoreCursorPosition}. + * Position can then be restored with [[restoreCursorPosition()]]. */ public static function saveCursorPosition() { @@ -150,7 +150,7 @@ class BaseConsole } /** - * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. + * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal. */ public static function restoreCursorPosition() { @@ -159,7 +159,7 @@ class BaseConsole /** * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. - * Use {@link showCursor} to bring it back. + * Use [[showCursor()]] to bring it back. * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. */ public static function hideCursor() @@ -168,7 +168,7 @@ class BaseConsole } /** - * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. + * Will show a cursor again when it has been hidden by [[hideCursor()]] by sending ANSI DECTCEM code ?25h to the terminal. */ public static function showCursor() { diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index 9533b95..325cc8a 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -283,6 +283,11 @@ class BaseFileHelper return $result; } } + + if (empty($options['except']) && empty($options['only'])) { + return true; + } + $path = str_replace('\\', '/', $path); if ($isDir = is_dir($path)) { $path .= '/'; diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php index 6f8e4e5..d435d71 100644 --- a/framework/yii/helpers/BaseHtml.php +++ b/framework/yii/helpers/BaseHtml.php @@ -739,6 +739,7 @@ class BaseHtml * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. * This option is ignored if `item` option is set. * - separator: string, the HTML code that separates items. + * - itemOptions: array, the options for generating the radio button tag using [[checkbox()]]. * - item: callable, a callback that can be used to customize the generation of the HTML code * corresponding to a single item in $items. The signature of this callback must be: * @@ -758,6 +759,7 @@ class BaseHtml } $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; $encode = !isset($options['encode']) || $options['encode']; $lines = []; $index = 0; @@ -768,10 +770,10 @@ class BaseHtml if ($formatter !== null) { $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); } else { - $lines[] = static::checkbox($name, $checked, [ + $lines[] = static::checkbox($name, $checked, array_merge($itemOptions, [ 'value' => $value, 'label' => $encode ? static::encode($label) : $label, - ]); + ])); } $index++; } @@ -786,7 +788,7 @@ class BaseHtml $separator = isset($options['separator']) ? $options['separator'] : "\n"; $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']); return $hidden . static::tag($tag, implode($separator, $lines), $options); } @@ -797,7 +799,7 @@ class BaseHtml * @param string $name the name attribute of each radio button. * @param string|array $selection the selected value(s). * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. + * The array values are the labels, while the array keys are the corresponding radio button values. * @param array $options options (name => config) for the radio button list. The following options are supported: * * - unselect: string, the value that should be submitted when none of the radio buttons is selected. @@ -805,6 +807,7 @@ class BaseHtml * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. * This option is ignored if `item` option is set. * - separator: string, the HTML code that separates items. + * - itemOptions: array, the options for generating the radio button tag using [[radio()]]. * - item: callable, a callback that can be used to customize the generation of the HTML code * corresponding to a single item in $items. The signature of this callback must be: * @@ -821,6 +824,7 @@ class BaseHtml { $encode = !isset($options['encode']) || $options['encode']; $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; $lines = []; $index = 0; foreach ($items as $value => $label) { @@ -830,10 +834,10 @@ class BaseHtml if ($formatter !== null) { $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); } else { - $lines[] = static::radio($name, $checked, [ + $lines[] = static::radio($name, $checked, array_merge($itemOptions, [ 'value' => $value, 'label' => $encode ? static::encode($label) : $label, - ]); + ])); } $index++; } @@ -847,7 +851,7 @@ class BaseHtml } $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']); return $hidden . static::tag($tag, implode($separator, $lines), $options); } diff --git a/framework/yii/log/EmailTarget.php b/framework/yii/log/EmailTarget.php index 95234d2..b924420 100644 --- a/framework/yii/log/EmailTarget.php +++ b/framework/yii/log/EmailTarget.php @@ -14,9 +14,8 @@ use yii\mail\MailerInterface; /** * EmailTarget sends selected log messages to the specified email addresses. * - * The target email addresses may be specified via [[emails]] property. - * Optionally, you may set the email [[subject]], [[sentFrom]] address and - * additional [[headers]]. + * You may configure the email to be sent by setting the [[message]] property, through which + * you can set the target email addresses, subject, etc. * * @author Qiang Xue * @since 2.0 diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php index f95b4fb..d2594fc 100644 --- a/framework/yii/mail/BaseMessage.php +++ b/framework/yii/mail/BaseMessage.php @@ -18,15 +18,13 @@ use Yii; * * @see BaseMailer * - * @property MailerInterface $mailer The mailer component. This property is read-only. - * * @author Paul Klimov * @since 2.0 */ abstract class BaseMessage extends Object implements MessageInterface { /** - * {@inheritdoc} + * @inheritdoc */ public function send(MailerInterface $mailer = null) { diff --git a/framework/yii/rbac/PhpManager.php b/framework/yii/rbac/PhpManager.php index 78e4d8c..3d02e4f 100644 --- a/framework/yii/rbac/PhpManager.php +++ b/framework/yii/rbac/PhpManager.php @@ -33,13 +33,12 @@ class PhpManager extends Manager { /** * @var string the path of the PHP script that contains the authorization data. - * If not set, it will be using 'protected/data/rbac.php' as the data file. - * Make sure this file is writable by the Web server process if the authorization - * needs to be changed. + * This can be either a file path or a path alias to the file. + * Make sure this file is writable by the Web server process if the authorization needs to be changed online. * @see loadFromFile() * @see saveToFile() */ - public $authFile; + public $authFile = '@app/data/rbac.php'; private $_items = []; // itemName => item private $_children = []; // itemName, childName => child @@ -53,9 +52,7 @@ class PhpManager extends Manager public function init() { parent::init(); - if ($this->authFile === null) { - $this->authFile = Yii::getAlias('@app/data/rbac') . '.php'; - } + $this->authFile = Yii::getAlias($this->authFile); $this->load(); } diff --git a/framework/yii/test/DbFixtureManager.php b/framework/yii/test/DbFixtureManager.php index ed90284..23d25d4 100644 --- a/framework/yii/test/DbFixtureManager.php +++ b/framework/yii/test/DbFixtureManager.php @@ -11,6 +11,7 @@ use Yii; use yii\base\Component; use yii\base\InvalidConfigException; use yii\db\ActiveRecord; +use yii\db\ActiveRecordInterface; use yii\db\Connection; /** @@ -92,7 +93,7 @@ class DbFixtureManager extends Component foreach ($fixtures as $name => $fixture) { if (strpos($fixture, '\\') !== false) { $model = new $fixture; - if ($model instanceof ActiveRecord) { + if ($model instanceof ActiveRecordInterface) { $this->_modelClasses[$name] = $fixture; $fixtures[$name] = $model->getTableSchema()->name; } else { diff --git a/framework/yii/validators/SafeValidator.php b/framework/yii/validators/SafeValidator.php index 7cdc0a1..25da899 100644 --- a/framework/yii/validators/SafeValidator.php +++ b/framework/yii/validators/SafeValidator.php @@ -16,7 +16,7 @@ namespace yii\validators; class SafeValidator extends Validator { /** - * {@inheritdoc} + * @inheritdoc */ public function validateAttribute($object, $attribute) { diff --git a/framework/yii/validators/UniqueValidator.php b/framework/yii/validators/UniqueValidator.php index 053f795..d123aad 100644 --- a/framework/yii/validators/UniqueValidator.php +++ b/framework/yii/validators/UniqueValidator.php @@ -10,6 +10,7 @@ namespace yii\validators; use Yii; use yii\base\InvalidConfigException; use yii\db\ActiveRecord; +use yii\db\ActiveRecordInterface; /** * UniqueValidator validates that the attribute value is unique in the corresponding database table. @@ -67,7 +68,7 @@ class UniqueValidator extends Validator $query = $className::find(); $query->where([$attributeName => $value]); - if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) { + if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) { // if current $object isn't in the database yet then it's OK just to call exists() $exists = $query->exists(); } else { diff --git a/framework/yii/validators/Validator.php b/framework/yii/validators/Validator.php index f0602b6..2cd611b 100644 --- a/framework/yii/validators/Validator.php +++ b/framework/yii/validators/Validator.php @@ -159,7 +159,7 @@ abstract class Validator extends Component } /** - * {@inheritdoc} + * @inheritdoc */ public function init() { diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php index 549f087..e755e80 100644 --- a/framework/yii/web/AccessControl.php +++ b/framework/yii/web/AccessControl.php @@ -131,14 +131,14 @@ class AccessControl extends ActionFilter * The default implementation will redirect the user to the login page if he is a guest; * if the user is already logged, a 403 HTTP exception will be thrown. * @param User $user the current user - * @throws HttpException if the user is already logged in. + * @throws AccessDeniedHttpException if the user is already logged in. */ protected function denyAccess($user) { if ($user->getIsGuest()) { $user->loginRequired(); } else { - throw new HttpException(403, Yii::t('yii', 'You are not allowed to perform this action.')); + throw new AccessDeniedHttpException(Yii::t('yii', 'You are not allowed to perform this action.')); } } } diff --git a/framework/yii/web/AccessDeniedHttpException.php b/framework/yii/web/AccessDeniedHttpException.php new file mode 100644 index 0000000..d83700b --- /dev/null +++ b/framework/yii/web/AccessDeniedHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class AccessDeniedHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(403, $message, $code, $previous); + } +} diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php index 13b5588..17c1411 100644 --- a/framework/yii/web/Application.php +++ b/framework/yii/web/Application.php @@ -58,7 +58,7 @@ class Application extends \yii\base\Application * Handles the specified request. * @param Request $request the request to be handled * @return Response the resulting response - * @throws HttpException if the requested route is invalid + * @throws NotFoundHttpException if the requested route is invalid */ public function handleRequest($request) { @@ -85,7 +85,7 @@ class Application extends \yii\base\Application return $response; } } catch (InvalidRouteException $e) { - throw new HttpException(404, $e->getMessage(), $e->getCode(), $e); + throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e); } } diff --git a/framework/yii/web/BadRequestHttpException.php b/framework/yii/web/BadRequestHttpException.php new file mode 100644 index 0000000..3a6cfbb --- /dev/null +++ b/framework/yii/web/BadRequestHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class BadRequestHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(400, $message, $code, $previous); + } +} diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 1424d0c..0df48bd 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -62,7 +62,7 @@ class Controller extends \yii\base\Controller } elseif (!is_array($params[$name])) { $args[] = $actionParams[$name] = $params[$name]; } else { - throw new HttpException(400, Yii::t('yii', 'Invalid data received for parameter "{param}".', [ + throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [ 'param' => $name, ])); } @@ -75,7 +75,7 @@ class Controller extends \yii\base\Controller } if (!empty($missing)) { - throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', [ + throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [ 'params' => implode(', ', $missing), ])); } @@ -86,13 +86,13 @@ class Controller extends \yii\base\Controller } /** - * {@inheritdoc} + * @inheritdoc */ public function beforeAction($action) { if (parent::beforeAction($action)) { if ($this->enableCsrfValidation && Yii::$app->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) { - throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); + throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); } return true; } else { diff --git a/framework/yii/web/MethodNotAllowedHttpException.php b/framework/yii/web/MethodNotAllowedHttpException.php new file mode 100644 index 0000000..d894f57 --- /dev/null +++ b/framework/yii/web/MethodNotAllowedHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(405, $message, $code, $previous); + } +} diff --git a/framework/yii/web/NotFoundHttpException.php b/framework/yii/web/NotFoundHttpException.php new file mode 100644 index 0000000..71f246d --- /dev/null +++ b/framework/yii/web/NotFoundHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class NotFoundHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(404, $message, $code, $previous); + } +} diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 04bf0e3..9736043 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -139,7 +139,7 @@ class Request extends \yii\base\Request $_GET = array_merge($_GET, $params); return [$route, $_GET]; } else { - throw new HttpException(404, Yii::t('yii', 'Page not found.')); + throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); } } diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 682d78e..b640756 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -331,7 +331,7 @@ class User extends Component Yii::$app->getResponse()->redirect($this->loginUrl)->send(); exit(); } else { - throw new HttpException(403, Yii::t('yii', 'Login Required')); + throw new AccessDeniedHttpException(Yii::t('yii', 'Login Required')); } } diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php index e673bae..65d182b 100644 --- a/framework/yii/web/VerbFilter.php +++ b/framework/yii/web/VerbFilter.php @@ -100,7 +100,7 @@ class VerbFilter extends Behavior $event->isValid = false; // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed)); - throw new HttpException(405, Yii::t('yii', 'Method Not Allowed. This url can only handle the following request methods: {methods}.', [ + throw new MethodNotAllowedHttpException(Yii::t('yii', 'Method Not Allowed. This url can only handle the following request methods: {methods}.', [ 'methods' => implode(', ', $allowed), ])); } diff --git a/framework/yii/widgets/FragmentCache.php b/framework/yii/widgets/FragmentCache.php index 3005df5..57c4659 100644 --- a/framework/yii/widgets/FragmentCache.php +++ b/framework/yii/widgets/FragmentCache.php @@ -94,7 +94,7 @@ class FragmentCache extends Widget /** * Marks the end of content to be cached. - * Content displayed before this method call and after {@link init()} + * Content displayed before this method call and after [[init()]] * will be captured and saved in cache. * This method does nothing if valid content is already found in cache. */ diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php index ad13420..11d638c 100644 --- a/framework/yii/widgets/ListView.php +++ b/framework/yii/widgets/ListView.php @@ -88,7 +88,7 @@ class ListView extends BaseListView $options = $this->itemOptions; $tag = ArrayHelper::remove($options, 'tag', 'div'); if ($tag !== false) { - $options['data-key'] = $key; + $options['data-key'] = is_array($key) ? json_encode($key) : $key; return Html::tag($tag, $content, $options); } else { return $content; diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php index 8bd2b12..465294f 100644 --- a/tests/unit/framework/console/controllers/MessageControllerTest.php +++ b/tests/unit/framework/console/controllers/MessageControllerTest.php @@ -108,7 +108,7 @@ class MessageControllerTest extends TestCase } /** - * Creates message command config file at {@link configFileName} + * Creates message command config file named as [[configFileName]]. * @param array $config message command config. */ protected function composeConfigFile(array $config)