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[] = " | $runLinks |
|
";
+ $i++;
+ }
+ $rows = implode("\n", $rows);
+ return <<Elasticsearch Queries
+
+
+
+
+ Url / Query |
+ Run Query on node |
+
+
+
+$rows
+
+
+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 = StringHelper::dirname(ltrim($generator->controllerClass, '\\')) ?>
use = ltrim($generator->modelClass, '\\') ?>;
use = ltrim($generator->searchModelClass, '\\') ?> as = $searchModelAlias ?>;
use = ltrim($generator->baseControllerClass, '\\') ?>;
-use yii\web\HttpException;
+use yii\web\NotFoundHttpException;
use yii\web\VerbFilter;
/**
@@ -130,7 +133,7 @@ class = $controllerClass ?> extends = StringHelper::basename($generator->bas
* If the model is not found, a 404 HTTP exception will be thrown.
* = implode("\n\t * ", $actionParamComments) . "\n" ?>
* @return = $modelClass ?> the loaded model
- * @throws HttpException if the model cannot be found
+ * @throws NotFoundHttpException if the model cannot be found
*/
protected function findModel(= $actionParams ?>)
{
@@ -148,7 +151,7 @@ if (count($pks) === 1) {
if (($model = = $modelClass ?>::find(= $condition ?>)) !== 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 = $searchModelClass ?> 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\t= " . $generator->generateActiveSearchField($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 = $generator->ns ?>;
class = $className ?> extends = '\\' . ltrim($generator->baseClass, '\\') . "\n" ?>
{
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function tableName()
{
@@ -41,7 +41,7 @@ class = $className ?> extends = '\\' . ltrim($generator->baseClass, '\\') .
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function rules()
{
@@ -49,7 +49,7 @@ class = $className ?> extends = '\\' . ltrim($generator->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)