Commit 83a459cc by Alexander Makarov

Merge branch 'master'

parents e539482d 89f9f0e9
......@@ -22,7 +22,8 @@ install:
- tests/unit/data/travis/cubrid-setup.sh
# basic application:
- composer install --dev --prefer-dist -d apps/basic
- cd apps/basic && php vendor/bin/codecept build && cd ../..
- cd apps/basic && composer require --dev codeception/codeception:1.8.*@dev codeception/specify:* codeception/verify:*
- php vendor/bin/codecept build && cd ../..
- cd apps && php -S localhost:8080 &
before_script:
......
<?php
namespace backend\controllers;
use Yii;
use yii\web\AccessControl;
use yii\web\Controller;
use common\models\LoginForm;
/**
* Site controller
*/
class SiteController extends Controller
{
/**
* @inheritdoc
*/
public function behaviors()
{
return [
'access' => [
'class' => \yii\web\AccessControl::className(),
'class' => AccessControl::className(),
'rules' => [
[
'actions' => ['login', 'error'],
......@@ -28,6 +34,9 @@ class SiteController extends Controller
];
}
/**
* @inheritdoc
*/
public function actions()
{
return [
......
......@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/**
* @var yii\web\View $this
* @var yii\widgets\ActiveForm $form
* @var common\models\LoginForm $model
* @var \common\models\LoginForm $model
*/
$this->title = 'Login';
$this->params['breadcrumbs'][] = $this->title;
......
......@@ -6,9 +6,5 @@ return [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mails',
],
],
];
......@@ -2,4 +2,5 @@
return [
'adminEmail' => 'admin@example.com',
'supportEmail' => 'support@example.com',
'user.passwordResetTokenExpire' => 3600,
];
<?php
namespace common\models;
use Yii;
use yii\base\Model;
use Yii;
/**
* LoginForm is the model behind the login form.
* Login form
*/
class LoginForm extends Model
{
......@@ -17,7 +16,7 @@ class LoginForm extends Model
private $_user = false;
/**
* @return array the validation rules.
* @inheritdoc
*/
public function rules()
{
......
......@@ -6,8 +6,7 @@ use yii\helpers\Security;
use yii\web\IdentityInterface;
/**
* Class User
* @package common\models
* User model
*
* @property integer $id
* @property string $username
......@@ -19,19 +18,32 @@ use yii\web\IdentityInterface;
* @property integer $status
* @property integer $created_at
* @property integer $updated_at
* @property string $password write-only password
*/
class User extends ActiveRecord implements IdentityInterface
{
/**
* @var string the raw password. Used to collect password input and isn't saved in database
*/
public $password;
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
const ROLE_USER = 10;
public static function create($attributes)
{
/** @var User $user */
$user = new static();
$user->setAttributes($attributes);
$user->setPassword($attributes['password']);
$user->generateAuthKey();
if ($user->save()) {
return $user;
} else {
return null;
}
}
/**
* @inheritdoc
*/
public function behaviors()
{
return [
......@@ -46,10 +58,7 @@ class User extends ActiveRecord implements IdentityInterface
}
/**
* Finds an identity by the given ID.
*
* @param string|integer $id the ID to be looked for
* @return IdentityInterface|null the identity object that matches the given ID.
* @inheritdoc
*/
public static function findIdentity($id)
{
......@@ -60,7 +69,7 @@ class User extends ActiveRecord implements IdentityInterface
* Finds user by username
*
* @param string $username
* @return null|User
* @return self
*/
public static function findByUsername($username)
{
......@@ -68,7 +77,29 @@ class User extends ActiveRecord implements IdentityInterface
}
/**
* @return int|string|array current user ID
* Finds user by password reset token
*
* @param string $token password reset token
* @return self
*/
public static function findByPasswordResetToken($token)
{
$expire = \Yii::$app->params['user.passwordResetTokenExpire'];
$parts = explode('_', $token);
$timestamp = (int)end($parts);
if ($timestamp + $expire < time()) {
// token expired
return null;
}
return User::find([
'password_reset_token' => $token,
'status' => User::STATUS_ACTIVE,
]);
}
/**
* @inheritdoc
*/
public function getId()
{
......@@ -76,7 +107,7 @@ class User extends ActiveRecord implements IdentityInterface
}
/**
* @return string current user auth key
* @inheritdoc
*/
public function getAuthKey()
{
......@@ -84,8 +115,7 @@ class User extends ActiveRecord implements IdentityInterface
}
/**
* @param string $authKey
* @return boolean if auth key is valid for current user
* @inheritdoc
*/
public function validateAuthKey($authKey)
{
......@@ -93,6 +123,8 @@ class User extends ActiveRecord implements IdentityInterface
}
/**
* Validates password
*
* @param string $password password to validate
* @return bool if password provided is valid for current user
*/
......@@ -101,6 +133,43 @@ class User extends ActiveRecord implements IdentityInterface
return Security::validatePassword($password, $this->password_hash);
}
/**
* Generates password hash from password and sets it to the model
*
* @param string $password
*/
public function setPassword($password)
{
$this->password_hash = Security::generatePasswordHash($password);
}
/**
* Generates "remember me" authentication key
*/
public function generateAuthKey()
{
$this->auth_key = Security::generateRandomKey();
}
/**
* Generates new password reset token
*/
public function generatePasswordResetToken()
{
$this->password_reset_token = Security::generateRandomKey() . '_' . time();
}
/**
* Removes password reset token
*/
public function removePasswordResetToken()
{
$this->password_reset_token = null;
}
/**
* @inheritdoc
*/
public function rules()
{
return [
......@@ -117,34 +186,7 @@ class User extends ActiveRecord implements IdentityInterface
['email', 'filter', 'filter' => 'trim'],
['email', 'required'],
['email', 'email'],
['email', 'unique', 'message' => 'This email address has already been taken.', 'on' => 'signup'],
['email', 'exist', 'message' => 'There is no user with such email.', 'on' => 'requestPasswordResetToken'],
['password', 'required'],
['password', 'string', 'min' => 6],
['email', 'unique'],
];
}
public function scenarios()
{
return [
'signup' => ['username', 'email', 'password', '!status', '!role'],
'resetPassword' => ['password'],
'requestPasswordResetToken' => ['email'],
];
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if (($this->isNewRecord || $this->getScenario() === 'resetPassword') && !empty($this->password)) {
$this->password_hash = Security::generatePasswordHash($this->password);
}
if ($this->isNewRecord) {
$this->auth_key = Security::generateRandomKey();
}
return true;
}
return false;
}
}
......@@ -16,7 +16,7 @@ class m130524_201442_init extends \yii\db\Migration
'username' => Schema::TYPE_STRING . ' NOT NULL',
'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL',
'password_hash' => Schema::TYPE_STRING . ' NOT NULL',
'password_reset_token' => Schema::TYPE_STRING . '(32)',
'password_reset_token' => Schema::TYPE_STRING,
'email' => Schema::TYPE_STRING . ' NOT NULL',
'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10',
......
......@@ -8,5 +8,10 @@ return [
'password' => '',
'charset' => 'utf8',
],
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mails',
'useFileTransport' => true,
],
],
];
......@@ -8,5 +8,9 @@ return [
'password' => '',
'charset' => 'utf8',
],
'mail' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mails',
],
],
];
<?php
namespace frontend\controllers;
use Yii;
use yii\web\Controller;
use common\models\LoginForm;
use frontend\models\PasswordResetRequestForm;
use frontend\models\ResetPasswordForm;
use frontend\models\SignupForm;
use frontend\models\ContactForm;
use common\models\User;
use yii\base\InvalidParamException;
use yii\web\BadRequestHttpException;
use yii\helpers\Security;
use yii\web\Controller;
use Yii;
/**
* Site controller
*/
class SiteController extends Controller
{
/**
* @inheritdoc
*/
public function behaviors()
{
return [
......@@ -34,6 +41,9 @@ class SiteController extends Controller
];
}
/**
* @inheritdoc
*/
public function actions()
{
return [
......@@ -59,7 +69,7 @@ class SiteController extends Controller
}
$model = new LoginForm();
if ($model->load($_POST) && $model->login()) {
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
} else {
return $this->render('login', [
......@@ -94,11 +104,13 @@ class SiteController extends Controller
public function actionSignup()
{
$model = new User();
$model->setScenario('signup');
if ($model->load($_POST) && $model->save()) {
if (Yii::$app->getUser()->login($model)) {
return $this->goHome();
$model = new SignupForm();
if ($model->load(Yii::$app->request->post())) {
$user = $model->signup();
if ($user) {
if (Yii::$app->getUser()->login($user)) {
return $this->goHome();
}
}
}
......@@ -109,16 +121,16 @@ class SiteController extends Controller
public function actionRequestPasswordReset()
{
$model = new User();
$model->scenario = 'requestPasswordResetToken';
if ($model->load($_POST) && $model->validate()) {
if ($this->sendPasswordResetEmail($model->email)) {
$model = new PasswordResetRequestForm();
if ($model->load(Yii::$app->request->post())) {
if ($model->sendEmail()) {
Yii::$app->getSession()->setFlash('success', 'Check your email for further instructions.');
return $this->goHome();
} else {
Yii::$app->getSession()->setFlash('error', 'There was an error sending email.');
Yii::$app->getSession()->setFlash('error', 'Sorry, we are unable to reset password for email provided.');
}
}
return $this->render('requestPasswordResetToken', [
'model' => $model,
]);
......@@ -126,21 +138,13 @@ class SiteController extends Controller
public function actionResetPassword($token)
{
if (empty($token) || is_array($token)) {
throw new BadRequestHttpException('Invalid password reset token.');
}
$model = User::find([
'password_reset_token' => $token,
'status' => User::STATUS_ACTIVE,
]);
if ($model === null) {
throw new BadRequestHttpException('Wrong password reset token.');
try {
$model = new ResetPasswordForm($token);
} catch (InvalidParamException $e) {
throw new BadRequestHttpException($e->getMessage());
}
$model->scenario = 'resetPassword';
if ($model->load($_POST) && $model->save()) {
if ($model->load($_POST) && $model->resetPassword()) {
Yii::$app->getSession()->setFlash('success', 'New password was saved.');
return $this->goHome();
}
......@@ -149,27 +153,4 @@ class SiteController extends Controller
'model' => $model,
]);
}
private function sendPasswordResetEmail($email)
{
$user = User::find([
'status' => User::STATUS_ACTIVE,
'email' => $email,
]);
if (!$user) {
return false;
}
$user->password_reset_token = Security::generateRandomKey();
if ($user->save(false)) {
return \Yii::$app->mail->compose('passwordResetToken', ['user' => $user])
->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'])
->setTo($email)
->setSubject('Password reset for ' . \Yii::$app->name)
->send();
}
return false;
}
}
<?php
namespace frontend\models;
use common\models\User;
use yii\base\Model;
/**
* Password reset request form
*/
class PasswordResetRequestForm extends Model
{
public $email;
/**
* @inheritdoc
*/
public function rules()
{
return [
['email', 'filter', 'filter' => 'trim'],
['email', 'required'],
['email', 'email'],
['email', 'exist', 'targetClass' => '\common\models\User', 'message' => 'There is no user with such email.'],
];
}
/**
*
* @return boolean sends an email
*/
public function sendEmail()
{
/** @var User $user */
$user = User::find([
'status' => User::STATUS_ACTIVE,
'email' => $this->email,
]);
if (!$user) {
return false;
}
$user->generatePasswordResetToken();
if ($user->save()) {
return \Yii::$app->mail->compose('passwordResetToken', ['user' => $user])
->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'])
->setTo($this->email)
->setSubject('Password reset for ' . \Yii::$app->name)
->send();
}
return false;
}
}
\ No newline at end of file
<?php
namespace frontend\models;
use common\models\User;
use yii\base\InvalidParamException;
use yii\base\Model;
use Yii;
/**
* Password reset form
*/
class ResetPasswordForm extends Model
{
public $password;
/**
* @var \common\models\User
*/
private $_user;
/**
* Creates a form model given a token
*
* @param string $token
* @param array $config name-value pairs that will be used to initialize the object properties
* @throws \yii\base\InvalidParamException if token is empty or not valid
*/
public function __construct($token, $config = [])
{
if (empty($token) || !is_string($token)) {
throw new InvalidParamException('Password reset token cannot be blank.');
}
$this->_user = User::findByPasswordResetToken($token);
if (!$this->_user) {
throw new InvalidParamException('Wrong password reset token.');
}
parent::__construct($config);
}
/**
* @return array the validation rules.
*/
public function rules()
{
return [
['password', 'required'],
['password', 'string', 'min' => 6],
];
}
/**
* Resets password.
* @return boolean if password was reset.
*/
public function resetPassword()
{
$user = $this->_user;
$user->password = $this->password;
$user->removePasswordResetToken();
return $user->save();
}
}
\ No newline at end of file
<?php
namespace frontend\models;
use common\models\User;
use yii\base\Model;
use Yii;
/**
* Signup form
*/
class SignupForm extends Model
{
public $username;
public $email;
public $password;
/**
* @inheritdoc
*/
public function rules()
{
return [
['username', 'filter', 'filter' => 'trim'],
['username', 'required'],
['username', 'string', 'min' => 2, 'max' => 255],
['email', 'filter', 'filter' => 'trim'],
['email', 'required'],
['email', 'email'],
['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken.'],
['password', 'required'],
['password', 'string', 'min' => 6],
];
}
/**
* Signs user up.
* @return User saved model
*/
public function signup()
{
if ($this->validate()) {
return User::create($this->attributes);
}
return null;
}
}
\ No newline at end of file
......@@ -6,7 +6,7 @@ use yii\captcha\Captcha;
/**
* @var yii\web\View $this
* @var yii\widgets\ActiveForm $form
* @var frontend\models\ContactForm $model
* @var \frontend\models\ContactForm $model
*/
$this->title = 'Contact';
$this->params['breadcrumbs'][] = $this->title;
......
......@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/**
* @var yii\web\View $this
* @var yii\widgets\ActiveForm $form
* @var common\models\LoginForm $model
* @var \common\models\LoginForm $model
*/
$this->title = 'Login';
$this->params['breadcrumbs'][] = $this->title;
......
......@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/**
* @var yii\web\View $this
* @var yii\widgets\ActiveForm $form
* @var common\models\User $model
* @var \frontend\models\PasswordResetRequestForm $model
*/
$this->title = 'Request password reset';
$this->params['breadcrumbs'][] = $this->title;
......
......@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/**
* @var yii\web\View $this
* @var yii\widgets\ActiveForm $form
* @var common\models\User $model
* @var \frontend\models\ResetPasswordForm $model
*/
$this->title = 'Reset password';
$this->params['breadcrumbs'][] = $this->title;
......
......@@ -5,7 +5,7 @@ use yii\widgets\ActiveForm;
/**
* @var yii\web\View $this
* @var yii\widgets\ActiveForm $form
* @var common\models\User $model
* @var \frontend\models\SignupForm $model
*/
$this->title = 'Signup';
$this->params['breadcrumbs'][] = $this->title;
......
......@@ -16,7 +16,7 @@ namespace frontend\widgets;
* - \Yii::$app->getSession()->setFlash('info', 'This is the message');
*
* @author Kartik Visweswaran <kartikv2@gmail.com>
* @author Alexander Makarov <sam@rmcerative.ru>
* @author Alexander Makarov <sam@rmcreative.ru>
*/
class Alert extends \yii\bootstrap\Widget
{
......
......@@ -21,12 +21,14 @@
},
"require-dev": {
"yiisoft/yii2-codeception": "*",
"codeception/codeception": "*",
"codeception/specify": "*",
"codeception/verify": "*",
"yiisoft/yii2-debug": "*",
"yiisoft/yii2-gii": "*"
},
"suggest": {
"codeception/codeception": "Codeception, 1.8.*@dev is currently works well with Yii.",
"codeception/specify": "BDD style code blocks for PHPUnit / Codeception",
"codeception/verify": "BDD Assertions for PHPUnit and Codeception"
},
"scripts": {
"post-create-project-cmd": [
"yii\\composer\\Installer::setPermission"
......
......@@ -3,9 +3,14 @@ These tests are developed with [Codeception PHP Testing Framework](http://codece
After creating the basic application, follow these steps to prepare for the tests:
1. In the file `_bootstrap.php`, modify the definition of the constant `TEST_ENTRY_URL` so
1. Install additional composer packages:
```
php composer.phar require --dev "codeception/codeception: 1.8.*@dev" "codeception/specify: *" "codeception/verify: *"
```
2. In the file `_bootstrap.php`, modify the definition of the constant `TEST_ENTRY_URL` so
that it points to the correct entry script URL.
2. Go to the application base directory and build the test suites:
3. Go to the application base directory and build the test suites:
```
vendor/bin/codecept build
......
......@@ -13,10 +13,10 @@ class ContactPage extends BasePage
*/
public function submit(array $contactData)
{
$data = [];
foreach ($contactData as $name => $value) {
$data["ContactForm[$name]"] = $value;
foreach ($contactData as $field => $value) {
$inputType = $field === 'body' ? 'textarea' : 'input';
$this->guy->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value);
}
$this->guy->submitForm('#contact-form', $data);
$this->guy->click('Submit', '#contact-form');
}
}
......@@ -14,9 +14,8 @@ class LoginPage extends BasePage
*/
public function login($username, $password)
{
$this->guy->submitForm('#login-form', [
'LoginForm[username]' => $username,
'LoginForm[password]' => $password,
]);
$this->guy->fillField('input[name="LoginForm[username]"]',$username);
$this->guy->fillField('input[name="LoginForm[password]"]',$password);
$this->guy->click('Login','#login-form');
}
}
......@@ -15,6 +15,8 @@ modules:
- PhpBrowser
# you can use WebDriver instead of PhpBrowser to test javascript and ajax.
# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium
# "restart" option is used by the WebDriver to start each time per test-file new session and cookies,
# it is useful if you want to login in your app in each test.
# - WebDriver
config:
PhpBrowser:
......@@ -22,3 +24,4 @@ modules:
# WebDriver:
# url: 'http://localhost'
# browser: firefox
# restart: true
......@@ -93,7 +93,7 @@
"imagine/imagine": "required by yii2-imagine extension",
"smarty/smarty": "required by yii2-smarty extension",
"swiftmailer/swiftmailer": "required by yii2-swiftmailer extension",
"twig/twig": "required by yii2-twig extension"
"yiisoft/yii2-coding-standards": "you can use this package to check for code style issues when contributing to yii"
},
"autoload": {
"psr-4": {
......
......@@ -33,7 +33,7 @@ the installed application. You only need to do these once for all.
```
php /path/to/yii-application/init
```
2. Create a new database and adjust the `components.db` configuration in `common/config/params-local.php` accordingly.
2. Create a new database and adjust the `components.db` configuration in `common/config/main-local.php` accordingly.
3. Apply migrations with console command `yii migrate`.
4. Set document roots of your Web server:
......
Database Fixtures
Managing Fixtures
=================
// todo: this tutorial may be merged into test-fixture.md
Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing
different cases. With this data using your tests becoming more efficient and useful.
Yii supports database fixtures via the `yii fixture` command line tool. This tool supports:
Yii supports fixtures via the `yii fixture` command line tool. This tool supports:
* Applying new fixtures to database tables;
* Clearing, database tables (with sequences);
* Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
* Unloading fixtures in different ways (usually it is clearing storage);
* Auto-generating fixtures and populating it with random data.
Fixtures format
---------------
Fixtures are just plain php files returning array. These files are usually stored under `@tests/unit/fixtures` path, but it
can be [configured](#configure-command-globally) in other way. Example of fixture file:
Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md) on them.
Lets assume we have fixtures data to load:
```
#users.php file under fixtures path
#users.php file under fixtures data path, by default @tests\unit\fixtures\data
return [
[
......@@ -36,61 +38,72 @@ return [
],
];
```
This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reset.
If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb`
fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md).
Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures).
Fixture classes name should not be plural.
Loading fixtures
----------------
Applying fixtures
-----------------
Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can
change this behavior with config or command options.
To apply fixture to the table, run the following command:
To load fixture, run the following command:
```
yii fixture/apply <tbl_name>
yii fixture/load <fixture_name>
```
The required `tbl_name` parameter specifies a database table to which data will be loaded. You can load data to several tables at once.
The required `fixture_name` parameter specifies a fixture name which data will be loaded. You can load several fixtures at once.
Below are correct formats of this command:
```
// apply fixtures to the "users" table of database
yii fixture/apply users
// load `users` fixture
yii fixture/load User
// same as above, because default action of "fixture" command is "apply"
yii fixture users
// same as above, because default action of "fixture" command is "load"
yii fixture User
// apply several fixtures to several tables. Note that there should not be any whitespace between ",", it should be one string.
yii fixture users,users_profiles
// load several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture User,UserProfile
// apply all fixtures
yii fixture/apply all
// load all fixtures
yii fixture/load all
// same as above
yii fixture all
// apply fixtures to the table users, but fixtures will be taken from different path.
yii fixture users --fixturePath='@app/my/custom/path/to/fixtures'
// load fixtures, but for other database connection.
yii fixture User --db='customDbConnectionId'
// apply fixtures to the table users, but for other database connection.
yii fixtures users --db='customDbConnectionId'
// load fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
yii fixture User --namespace='alias\my\custom\namespace'
// load global fixture `some\name\space\CustomFixture` before other fixtures will be loaded.
// By default this option is set to `InitDbFixture` to disable/enable integrity checks. You can specify several
// global fixtures separated by comma.
yii fixture User --globalFixtures='some\name\space\Custom'
```
Clearing tables
---------------
Unloading fixtures
------------------
To clear table, run the following command:
To unload fixture, run the following command:
```
// clear given table: delete all data and reset sequence.
yii fixture/clear users
// unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture).
yii fixture/unload User
// clear several tables. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear users,users_profile
// Unload several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/unload User,UserProfile
// clear all tables of current connection in current schema
yii fixture/clear all
// unload all fixtures
yii fixture/unload all
```
Same command options like: `db`, `namespace`, `globalFixtures` also can be applied to this command.
Configure Command Globally
--------------------------
While command line options allow us to configure the migration command
......@@ -100,9 +113,13 @@ different migration path as follows:
```
'controllerMap' => [
'fixture' => [
'class' => 'yii\console\FixtureController',
'fixturePath' => '@app/my/custom/path/to/fixtures',
'class' => 'yii\console\controllers\FixtureController',
'db' => 'customDbConnectionId',
'namespace' => 'myalias\some\custom\namespace',
'globalFixtures' => [
'some\name\space\Foo',
'other\name\space\Bar'
],
],
]
```
......
......@@ -91,6 +91,9 @@ to a console command. The method should return a list of public property names o
When running a command, you may specify the value of an option using the syntax `--OptionName=OptionValue`.
This will assign `OptionValue` to the `OptionName` property of the controller class.
If the default value of an option is of array type, then if you set this option while running the command,
the option value will be converted into an array by splitting the input string by commas.
### Arguments
Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action
......
......@@ -91,7 +91,7 @@ If controller is located inside a module its action internal route will be `modu
In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404.
> Note: If controller name or action name contains camelCased words, internal route will use dashes i.e. for
> Note: If module name, controller name or action name contains camelCased words, internal route will use dashes i.e. for
`DateTimeController::actionFastForward` route will be `date-time/fast-forward`.
### Defaults
......
Database basics
===============
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/book.pdo.php). It provides
uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS:
- [MySQL](http://www.mysql.com/)
......
Error Handling
==============
Error handling in Yii is different from plain PHP. First of all, all non-fatal errors are converted to exceptions so
you can use `try`-`catch` to work with these. Second, even fatal errors are rendered in a nice way. In debug mode that
means you have a trace and a piece of code where it happened so it takes less time to analyze and fix it.
Error handling in Yii is different than handling errors in plain PHP. First of all, Yii will convert all non-fatal errors to *exceptions*. By doing so, you can gracefully handle them using `try`-`catch`. Second, even fatal errors in Yii are rendered in a nice way. This means that in debugging mode, you can trace the causes of fatal errors in order to more quickly identify the cause of the problem.
Using controller action to render errors
----------------------------------------
Rendering errors in a dedicated controller action
-------------------------------------------------
Default Yii error page is great for development mode and is OK for production if `YII_DEBUG` is turned off but you may
have an idea how to make it more suitable for your project. An easiest way to customize it is to use controller action
for error rendering. In order to do so you need to configure `errorHandler` component via application config:
The default Yii error page is great when developing a site, and is acceptable for production sites if `YII_DEBUG` is turned off in your bootstrap index.php file. But but you may want to customize the default error page to make it more suitable for your project.
The easiest way to create a custom error page it is to use a dedicated controller action for error rendering. First, you'll need to configure the `errorHandler` component in the application's configuration:
```php
return [
......@@ -22,7 +20,7 @@ return [
],
```
After it is done in case of error, Yii will launch `SiteController::actionError()`:
With that configuration in place, whenever an error occurs, Yii will execute the "error" acction of the "Site" controller. That action should look for an exception and, if present, render the proper view file, passing along the exception:
```php
public function actionError()
......@@ -33,8 +31,22 @@ public function actionError()
}
```
Since most of the time you need to adjust look and feel only, Yii provides `ErrorAction` class that can be used in
controller instead of implementing action yourself:
Next, you would create the `views/site/error.php` file, which would make use of the exception. The exception object has the following properties:
* `code`: the HTTP status code (e.g. 403, 500)
* `type`: the error type (e.g. CHttpException, PHP Error)
* `message`: the error message
* `file`: the name of the PHP script file where the error occurs
* `line`: the line number of the code where the error occurs
* `trace`: the call stack of the error
* `source`: the context source code where the error occurs
[[Need to confirm the above for Yii 2.]]
Rendering errors without a dedicated controller action
------------------------------------------------------
Instead of creating a dedicated action within the Site controller, you could just indicate to Yii what class should be used to handle errors:
```php
public function actions()
......@@ -47,10 +59,10 @@ public function actions()
}
```
After defining `actions` in `SiteController` as shown above you can create `views/site/error.php`. In the view there
are three variables available:
After associating the class with the error as in the above, define the `views/site/error.php` file, which will automatically be used. The view will be passed three variables:
- `$name`: the error name
- `$message`: the error message
- `$exception`: the exception being handled
The `$exception` object will have the same properties outlined above.
\ No newline at end of file
......@@ -3,36 +3,95 @@ Events
TBD, see also [Component.md](../api/base/Component.md).
There is no longer the need to define an `on`-method in order to define an event in Yii 2.0.
Instead, you can use whatever event names. To attach a handler to an event, you should
use the `on` method now:
[[ADD INTRODUCTION]]
Creating Event Handlers
-----------------------
In Yii 1, events were defined using the `onEventName` method syntax, such as `onBeforeSave`. This is no longer necessary in Yii 2, as event handling is now assigned using the `on` method. The method's first argument is the name of the event to watch for; the second is the handling method to be called when that event occurs:
```php
$component->on($eventName, $handler);
// To detach the handler, use:
```
[[LINK TO LIST OF EVENTS]]
The handler must be a valid PHP callback. This could be represented as:
* The name of a global function
* An array consisting of a model name and method name
* An array consisting of an object and a method name
* An anonymous function
```php
// Global function:
$component->on($eventName, 'functionName');
// Model and method names:
$component->on($eventName, ['Modelname', 'functionName']);
// Object and method name:
$component->on($eventName, [$obj, 'functionName']);
// Anonymous function:
$component->on($eventName, function($event) {
// Use $event.
});
```
As shown in the anonymous function example, the event handling function must be defined so that it takes one argument. This will be an [[Event]] object.
Removing Event Handlers
-----------------------
The correspondoing `off` method removes an event handler:
```php
// $component->off($eventName);
```
Yii supports the ability to associate multiple handlers with the same event. When using `off` as in the above, every handler is removed. To remove only a specific handler, provide that as the second argument to `off`:
```php
// $component->off($eventName, $handler);
```
The `$handler` should be presented in the `off` method in the same way as was presented in `on` in order to remove it.
Event Parameters
----------------
When you attach a handler, you can now associate it with some parameters which can be later
accessed via the event parameter by the handler:
You can make your event handlers easier to work with and more powerful by passing additional values as parameters.
```php
$component->on($eventName, $handler, $params);
```
The passed parameters will be available in the event handler through `$event->data`, which will be an array.
Because of this change, you can now use "global" events. Simply trigger and attach handlers to
an event of the application instance:
[[NEED TO CONFIRM THE ABOVE]]
Global Events
-------------
Thanks to the change in Yii 2 as to how event handlers are created, you can now use "global" events. To create a global event, simply attach handlers to an event on the application instance:
```php
Yii::$app->on($eventName, $handler);
....
// this will trigger the event and cause $handler to be invoked.
```
You can use the `trigger` method to trigger these events manually:
```php
// this will trigger the event and cause $handler to be invoked:
Yii::$app->trigger($eventName);
```
If you need to handle all instances of a class instead of the object you can attach a handler like the following:
Class Events
------------
You can also attach event handlers to all instances of a class instead of individual instances. To do so, use the static `Event::on` method:
```php
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
......
......@@ -356,7 +356,8 @@ class Module extends \yii\base\Module
}
```
In the example above we are using wildcard for matching and then filtering each category per needed file.
In the example above we are using wildcard for matching and then filtering each category per needed file. Instead of using `fileMap` you can simply
use convention of category mapping to the same named file and use `Module::t('validation', 'your custom validation message')` or `Module::t('form', 'some form label')` directly.
###Translating widgets messages
......@@ -405,6 +406,8 @@ class Menu extends Widget
}
```
Instead of using `fileMap` you can simply use convention of category mapping to the same named file and use `Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])` directly.
> **Note**: For widgets you also can use i18n views, same rules as for controllers are applied to them too.
TBD: provided classes overview.
......@@ -15,8 +15,8 @@ PHP 5.4.0 or greater.
For developers who want to use Yii, understanding object-oriented
programming (OOP) is very helpful, because Yii is a pure OOP framework.
Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php)
so you should be familiar with how they work.
Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php),
so you should be familiar with how they work, too.
What is Yii Best for?
......@@ -24,16 +24,16 @@ What is Yii Best for?
Yii is a generic Web programming framework that can be used for developing
virtually any type of Web application. Because it is light-weight and
equipped with sophisticated caching mechanisms, it is especially suited
to high-traffic applications, such as portals, forums, content
management systems (CMS), e-commerce projects, etc.
equipped with sophisticated caching mechanisms, Yii is especially suited
to high-traffic applications such as portals, forums, content
management systems (CMS), e-commerce projects, and so on.
How does Yii Compare with Other Frameworks?
-------------------------------------------
- Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach.
- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching etc.
- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching, etc.
- Yii strikes a good balance between simplicity and features.
- Syntax and overall development usability are taken seriously by the Yii development team.
- Performance is one of the key goals for the Yii framework.
......
......@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase
{
protected function fixtures()
public function fixtures()
{
return [
'profiles' => UserProfileFixture::className(),
......@@ -175,6 +175,43 @@ This means you only need to work with `@app/tests/fixtures/initdb.php` if you wa
before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures.
Organizing Fixture Classes and Data Files
-----------------------------------------
By default, fixture classes look for the corresponding data files under the `data` folder which is a sub-folder
of the folder containing the fixture class files. You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch different data files for the same fixture class for
different tests. We thus recommend that you organize the data files in a hierarchical way that is similar to
your class namespaces. For example,
```
# under folder tests\unit\fixtures
data\
components\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
```
In this way you will avoid collision of fixture data files between tests and use them as you need.
> Note: In the example above fixture files are named only for example purpose. In real life you should name them
> according to which fixture class your fixture classes are extending from. For example, if you are extending
> from [[\yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names;
> If you are extending for [[\yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names.
The similar hierarchy can be used to organize fixture class files. Instead of using `data` as the root directory, you may
want to use `fixtures` as the root directory to avoid conflict with the data files.
Summary
-------
......@@ -186,5 +223,5 @@ of running unit tests related with DB:
- Load fixtures: clean up the relevant DB tables and populate them with fixture data;
- Perform the actual test;
- Unload fixtures.
3. Repeat 2 until all tests finish.
3. Repeat Step 2 until all tests finish.
......@@ -26,4 +26,4 @@ automatically re-extract messages keeping unchanged ones intact.
In the translation file each array element represents the translation (value) of a message (key). If the value is empty,
the message is considered as not translated. Messages that no longer need translation will have their translations
enclosed between a pair of '@@' marks. Message string can be used with plural forms format. Check [i18n section
of the guide](i18n.md) for details.
of the guide](../guide/i18n.md) for details.
......@@ -9,7 +9,6 @@ namespace yii\apidoc\models;
use phpDocumentor\Reflection\FileReflector;
use yii\base\Component;
use yii\base\Exception;
/**
*
......
......@@ -11,7 +11,6 @@ use phpDocumentor\Reflection\DocBlock\Tag\ParamTag;
use phpDocumentor\Reflection\DocBlock\Tag\PropertyTag;
use phpDocumentor\Reflection\DocBlock\Tag\ReturnTag;
use phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag;
use yii\base\Exception;
/**
* Represents API documentation information for a `function`.
......
......@@ -63,7 +63,7 @@ class PropertyDoc extends BaseDoc
$this->defaultValue = PrettyPrinter::getRepresentationOfValue($reflector->getNode()->default);
}
foreach($this->tags as $i => $tag) {
foreach($this->tags as $tag) {
if ($tag instanceof VarTag) {
$this->type = $tag->getType();
$this->types = $tag->getTypes();
......
......@@ -8,7 +8,6 @@
namespace yii\apidoc\models;
use phpDocumentor\Reflection\DocBlock\Tag\AuthorTag;
use yii\base\Exception;
use yii\helpers\StringHelper;
/**
......
......@@ -11,7 +11,6 @@ use yii\apidoc\models\Context;
use yii\console\Controller;
use Yii;
use yii\helpers\Console;
use yii\helpers\FileHelper;
/**
*
......@@ -186,7 +185,7 @@ class Renderer extends \yii\apidoc\templates\html\Renderer
protected function fixMarkdownLinks($content)
{
$content = preg_replace('/href\s*=\s*"([^"]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content);
$content = preg_replace('/href\s*=\s*"([^"\/]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content);
return $content;
}
}
\ No newline at end of file
......@@ -6,7 +6,6 @@
*/
namespace yii\apidoc\templates\bootstrap\assets;
use yii\web\JqueryAsset;
use yii\web\View;
/**
......
<?php
use yii\apidoc\templates\bootstrap\SideNavWidget;
use yii\helpers\StringHelper;
/**
* @var yii\web\View $this
......
<?php
use yii\apidoc\templates\bootstrap\SideNavWidget;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\helpers\Html;
use yii\helpers\StringHelper;
use yii\widgets\Menu;
/**
* @var yii\web\View $this
......
......@@ -6,11 +6,7 @@
*/
namespace yii\apidoc\templates\offline;
use yii\apidoc\models\Context;
use yii\console\Controller;
use Yii;
use yii\helpers\Console;
use yii\helpers\FileHelper;
/**
*
......
......@@ -6,7 +6,6 @@
*/
namespace yii\apidoc\templates\offline\assets;
use yii\web\JqueryAsset;
use yii\web\View;
/**
......
......@@ -11,8 +11,6 @@ use yii\apidoc\models\TypeDoc;
use yii\console\Controller;
use Yii;
use yii\helpers\Console;
use yii\helpers\FileHelper;
use yii\helpers\StringHelper;
/**
*
......
......@@ -315,7 +315,7 @@ class AuthAction extends Action
return Yii::$app->getResponse()->redirect($url);
} else {
// Upgrade to access token.
$accessToken = $client->fetchAccessToken();
$client->fetchAccessToken();
return $this->authSuccess($client);
}
}
......
......@@ -29,6 +29,8 @@ class Dropdown extends Widget
* - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item link.
* - options: array, optional, the HTML attributes of the item.
* - items: array, optional, the submenu items. The structure is the same as this property.
* Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
*
* To insert divider use `<li role="presentation" class="divider"></li>`.
*/
......@@ -84,6 +86,10 @@ class Dropdown extends Widget
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
$linkOptions['tabindex'] = '-1';
$content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
if (!empty($item['items'])) {
$content .= $this->renderItems($item['items']);
Html::addCssClass($options, 'dropdown-submenu');
}
$lines[] = Html::tag('li', $content, $options);
}
......
......@@ -18,7 +18,7 @@ class DbTestCase extends TestCase
/**
* @inheritdoc
*/
protected function globalFixtures()
public function globalFixtures()
{
return [
InitDbFixture::className(),
......
......@@ -5,6 +5,9 @@ namespace yii\codeception;
use Yii;
use yii\base\InvalidConfigException;
use Codeception\TestCase\Test;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
use yii\test\ActiveFixture;
use yii\test\FixtureTrait;
/**
......@@ -26,12 +29,52 @@ class TestCase extends Test
public $appConfig = '@tests/unit/_config.php';
/**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/**
* @inheritdoc
*/
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->unloadFixtures();
$this->loadFixtures();
}
......@@ -40,7 +83,6 @@ class TestCase extends Test
*/
protected function tearDown()
{
$this->unloadFixtures();
$this->destroyApplication();
parent::tearDown();
}
......
......@@ -8,6 +8,7 @@ Yii Framework 2 debug extension Change Log
- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue)
- Bug #1747: Fixed problems with displaying toolbar on small screens (cebe)
- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue)
- Enh #2006: Added total queries count monitoring (o-rey, Ragazzo)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -6,6 +6,7 @@
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
......
......@@ -154,5 +154,4 @@ class LogTarget extends Target
# / 2 because messages are in couple (begin/end)
return count($profileLogs['messages']) / 2;
}
}
......@@ -39,7 +39,7 @@ class DefaultController extends Controller
public function actions()
{
$actions = [];
foreach($this->module->panels as $panel) {
foreach ($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
......@@ -50,7 +50,13 @@ class DefaultController extends Controller
$searchModel = new Debug();
$dataProvider = $searchModel->search($_GET, $this->getManifest());
// load latest request
$tags = array_keys($this->getManifest());
$tag = reset($tags);
$this->loadData($tag);
return $this->render('index', [
'panels' => $this->module->panels,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
......@@ -87,10 +93,6 @@ class DefaultController extends Controller
]);
}
public function actionPhpinfo()
{
phpinfo();
}
private $_manifest;
......
......@@ -82,7 +82,7 @@ class Debug extends Base
'ajax' => 'Ajax',
'url' => 'url',
'statusCode' => 'Status code',
'sqlCount' => 'Total queries count',
'sqlCount' => 'Total queries',
];
}
......
......@@ -27,16 +27,6 @@ class ConfigPanel extends Panel
}
/**
* Returns Yii logo ready to use in `<img src="`
*
* @return string base64 representation of the image
*/
public static function getYiiLogo()
{
return 'data:image/png;base64,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';
}
/**
* @inheritdoc
*/
public function getSummary()
......@@ -67,6 +57,23 @@ class ConfigPanel extends Panel
}
/**
* Returns the BODY contents of the phpinfo() output
*
* @return array
*/
public function getPhpInfo ()
{
ob_start();
phpinfo();
$pinfo = ob_get_contents();
ob_end_clean();
$phpinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
$phpinfo = str_replace('<table ', '<table class="table table-condensed table-bordered table-striped table-hover"', $phpinfo);
return $phpinfo;
}
/**
* @inheritdoc
*/
public function save()
......
......@@ -21,6 +21,12 @@ use yii\debug\models\search\Db;
class DbPanel extends Panel
{
/**
* @var integer the threshold for determining whether the request has involved
* critical number of DB queries. If the number of queries exceeds this number,
* the execution is considered taking critical number of DB queries.
*/
public $criticalQueryThreshold;
/**
* @var array db queries info extracted to array as models, to use with data provider.
*/
private $_models;
......@@ -48,7 +54,7 @@ class DbPanel extends Panel
$queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms';
return Yii::$app->view->render('panels/db/summary', [
'timings' => $this->calculateTimings(),
'timings' => $this->calculateTimings(),
'panel' => $this,
'queryCount' => $queryCount,
'queryTime' => $queryTime,
......@@ -121,7 +127,7 @@ class DbPanel extends Panel
$this->_models = [];
$timings = $this->calculateTimings();
foreach($timings as $seq => $dbTiming) {
foreach ($timings as $seq => $dbTiming) {
$this->_models[] = [
'type' => $this->getQueryType($dbTiming['info']),
'query' => $dbTiming['info'],
......@@ -147,4 +153,15 @@ class DbPanel extends Panel
preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? $matches[0] : '';
}
/**
* Check if given queries count is critical according settings.
*
* @param integer $count queries count
* @return boolean
*/
public function isQueryCountCritical($count)
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
}
......@@ -50,7 +50,7 @@ class LogPanel extends Panel
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels());
return Yii::$app->view->render('panels/log/detail', [
'dataProvider' => $dataProvider,
'dataProvider' => $dataProvider,
'panel' => $this,
'searchModel' => $searchModel,
]);
......@@ -78,7 +78,7 @@ class LogPanel extends Panel
if ($this->_models === null || $refresh) {
$this->_models = [];
foreach($this->data['messages'] as $message) {
foreach ($this->data['messages'] as $message) {
$this->_models[] = [
'message' => $message[0],
'level' => $message[1],
......
......@@ -86,7 +86,7 @@ class ProfilingPanel extends Panel
$this->_models = [];
$timings = Yii::$app->getLog()->calculateTimings($this->data['messages']);
foreach($timings as $seq => $profileTiming) {
foreach ($timings as $seq => $profileTiming) {
$this->_models[] = [
'duration' => $profileTiming['duration'] * 1000, // in milliseconds
'category' => $profileTiming['category'],
......
......@@ -9,15 +9,24 @@ use yii\data\ArrayDataProvider;
* @var array $manifest
* @var \yii\debug\models\search\Debug $searchModel
* @var ArrayDataProvider $dataProvider
* @var \yii\debug\Panel[] $panels
*/
$this->title = 'Yii Debugger';
?>
<div class="default-index">
<div id="yii-debug-toolbar" class="yii-debug-toolbar-top">
<div class="yii-debug-toolbar-block title">
Yii Debugger
</div>
<div id="yii-debug-toolbar" class="yii-debug-toolbar-top">
<div class="yii-debug-toolbar-block title">
<a href="#">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
Yii Debugger
</a>
</div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
<?php endforeach; ?>
</div>
<div class="container">
......@@ -32,7 +41,9 @@ echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) {
if ($searchModel->isCodeCritical($model['statusCode'])) {
$dbPanel = $this->context->module->panels['db'];
if ($searchModel->isCodeCritical($model['statusCode']) || $dbPanel->isQueryCountCritical($model['sqlCount'])) {
return ['class'=>'danger'];
} else {
return [];
......@@ -42,23 +53,36 @@ echo GridView::widget([
['class' => 'yii\grid\SerialColumn'],
[
'attribute' => 'tag',
'value' => function ($data)
{
'value' => function ($data) {
return Html::a($data['tag'], ['view', 'tag' => $data['tag']]);
},
'format' => 'html',
],
[
'attribute' => 'time',
'value' => function ($data) use ($timeFormatter)
{
return $timeFormatter->asDateTime($data['time'], 'long');
'value' => function ($data) use ($timeFormatter) {
return $timeFormatter->asDateTime($data['time'], 'short');
},
],
'ip',
[
'attribute' => 'sqlCount',
'label' => 'Total queries count'
'label' => 'Total queries',
'value' => function ($data) {
$dbPanel = $this->context->module->panels['db'];
if ($dbPanel->isQueryCountCritical($data['sqlCount'])) {
$content = Html::tag('b', $data['sqlCount']) . ' ' . Html::tag('span', '', ['class' => 'glyphicon glyphicon-exclamation-sign']);
return Html::a($content, ['view', 'panel' => 'db', 'tag' => $data['tag']], [
'title' => 'Too many queries. Allowed count is ' . $dbPanel->criticalQueryThreshold,
]);
} else {
return $data['sqlCount'];
}
},
'format' => 'html',
],
[
'attribute' => 'method',
......@@ -66,8 +90,7 @@ echo GridView::widget([
],
[
'attribute'=>'ajax',
'value' => function ($data)
{
'value' => function ($data) {
return $data['ajax'] ? 'Yes' : 'No';
},
'filter' => ['No', 'Yes'],
......
......@@ -7,6 +7,7 @@ use yii\helpers\Html;
$extensions = $panel->getExtensions();
?>
<h1>Configuration</h1>
<?php
echo $this->render('panels/config/table', [
'caption' => 'Application Configuration',
......@@ -34,5 +35,6 @@ echo $this->render('panels/config/table', [
'Memcache' => $panel->data['php']['memcache'] ? 'Enabled' : 'Disabled',
],
]);
?>
<div><?= Html::a('Show phpinfo() »', ['phpinfo'], ['class' => 'btn btn-primary']) ?></div>
echo $panel->getPhpInfo();
?>
\ No newline at end of file
......@@ -8,10 +8,9 @@ use yii\helpers\Html;
?>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>">
<img width="29" height="30" alt="" src="<?= $panel->getYiiLogo() ?>">
<span><?= $panel->data['application']['yii'] ?></span>
Yii
<span class="label label-info"><?= $panel->data['application']['yii'] ?></span>
PHP
<span class="label label-info"><?= $panel->data['php']['version'] ?></span>
</a>
</div>
<div class="yii-debug-toolbar-block">
<?= Html::a('PHP ' . $panel->data['php']['version'], ['phpinfo'], ['title' => 'Show phpinfo()']) ?>
</div>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>" title="Total request processing time was <?= $time ?>">Time <span class="label"><?= $time ?></span></a>
</div>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>" title="Peak memory consumption">Memory <span class="label"><?= $memory ?></span></a>
</div>
......@@ -21,7 +21,5 @@ $statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Respons
?>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>" title="Status code: <?= $statusCode ?> <?= $statusText ?>">Status <span class="label <?= $class ?>"><?= $statusCode ?></span></a>
</div>
<div class="yii-debug-toolbar-block">
<a href="<?= $panel->getUrl() ?>">Action <span class="label"><?= $panel->data['action'] ?></span></a>
</div>
......@@ -5,6 +5,7 @@
* @var string $tag
* @var string $position
*/
use yii\helpers\Html;
use yii\debug\panels\ConfigPanel;
$minJs = <<<EOD
......@@ -23,17 +24,25 @@ if (window.localStorage) {
}
EOD;
$url = $panels['request']->getUrl();
$firstPanel = reset($panels);
$url = $firstPanel->getUrl();
?>
<div id="yii-debug-toolbar" class="yii-debug-toolbar-<?= $position ?>">
<div class="yii-debug-toolbar-block title">
<a href="<?= Html::url(['index']) ?>">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
Yii Debugger
</a>
</div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
<?= $panel->getSummary() ?>
<?php endforeach; ?>
<span class="yii-debug-toolbar-toggler" onclick="<?= $minJs ?>"></span>
</div>
<div id="yii-debug-toolbar-min">
<a href="<?= $url ?>" title="Open Yii Debugger" id="yii-debug-toolbar-logo">
<img width="29" height="30" alt="" src="<?= ConfigPanel::getYiiLogo() ?>">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
</a>
<span class="yii-debug-toolbar-toggler" onclick="<?= $maxJs ?>"></span>
</div>
......@@ -17,9 +17,14 @@ $this->title = 'Yii Debugger';
?>
<div class="default-view">
<div id="yii-debug-toolbar" class="yii-debug-toolbar-top">
<div class="yii-debug-toolbar-block title">
<?= Html::a('Yii Debugger', ['index'], ['title' => 'Back to main debug page']) ?>
</div>
<div class="yii-debug-toolbar-block title">
<a href="<?= Html::url(['index']) ?>">
<img width="29" height="30" alt="" src="<?= \yii\debug\Module::getYiiLogo() ?>">
Yii Debugger
</a>
</div>
<?php foreach ($panels as $panel): ?>
<?= $panel->getSummary() ?>
<?php endforeach; ?>
......@@ -27,7 +32,7 @@ $this->title = 'Yii Debugger';
<div class="container">
<div class="row">
<div class="col-lg-2">
<div class="col-lg-2 col-md-2">
<div class="list-group">
<?php
foreach ($panels as $id => $panel) {
......@@ -39,7 +44,7 @@ $this->title = 'Yii Debugger';
?>
</div>
</div>
<div class="col-lg-10">
<div class="col-lg-10 col-md-10">
<div class="callout callout-danger">
<?php
$count = 0;
......
......@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($result);
$model = $class::instantiate($result);
$class::populateRecord($model, $result);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord
$command = static::getDb()->createCommand();
$result = $command->get(static::index(), static::type(), $primaryKey, $options);
if ($result['exists']) {
$model = static::create($result);
$model = static::instantiate($result);
static::populateRecord($model, $result);
$model->afterFind();
return $model;
}
......@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord
$models = [];
foreach($result['docs'] as $doc) {
if ($doc['exists']) {
$model = static::create($doc);
$model = static::instantiate($doc);
static::populateRecord($model, $doc);
$model->afterFind();
$models[] = $model;
}
......@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord
}
/**
* 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.
* @inheritdoc
*/
public static function create($row)
public static function populateRecord($record, $row)
{
$record = parent::create($row['_source']);
parent::populateRecord($record, $row['_source']);
$pk = static::primaryKey()[0];
if ($pk === '_id') {
$record->$pk = $row['_id'];
$record->_id = $row['_id'];
}
$record->_score = isset($row['_score']) ? $row['_score'] : null;
$record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available...
return $record;
}
/**
* Creates an active record instance.
*
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
*
* 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.
* This array consists of the following keys:
* - `_source`: refers to the attributes of the record.
* - `_type`: the type this record is stored in.
* - `_index`: the index this record is stored in.
* @return static the newly created active record
*/
public static function instantiate($row)
{
return new static;
}
/**
......
......@@ -5,9 +5,12 @@ Yii Framework 2 elasticsearch extension Change Log
----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2324: Fixed QueryBuilder bug when building a query with "query" option (mintao)
- Enh #1313: made index and type available in `ActiveRecord::instantiate()` to allow creating records based on elasticsearch type when doing cross index/type search (cebe)
- Enh #1382: Added a debug toolbar panel for elasticsearch (cebe)
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -55,8 +55,10 @@ class QueryBuilder extends \yii\base\Object
$parts['from'] = (int) $query->offset;
}
if (empty($parts['query'])) {
if (empty($query->query)) {
$parts['query'] = ["match_all" => (object)[]];
} else {
$parts['query'] = $query->query;
}
$whereFilter = $this->buildCondition($query->where);
......
......@@ -9,8 +9,8 @@ namespace yii\faker;
use Yii;
use yii\console\Exception;
use yii\helpers\FileHelper;
use yii\helpers\Console;
use yii\helpers\FileHelper;
/**
* This command manage fixtures creations based on given template.
......@@ -69,7 +69,7 @@ use yii\helpers\Console;
* ~~~
*
* In the code above "users" is template name, after this command run, new file named same as template
* will be created under the `$fixturePath` folder.
* will be created under the `$fixtureDataPath` folder.
* You can generate fixtures for all templates by specifying keyword "all"
*
* ~~~
......@@ -77,7 +77,7 @@ use yii\helpers\Console;
* ~~~
*
* This command will generate fixtures for all template files that are stored under $templatePath and
* store fixtures under $fixturePath with file names same as templates names.
* store fixtures under `$fixtureDataPath` with file names same as templates names.
*
* You can specify how many fixtures per file you need by the second parameter. In the code below we generate
* all fixtures and in each file there will be 3 rows (fixtures).
......@@ -95,8 +95,8 @@ use yii\helpers\Console;
* //read templates from the other path
* yii fixture/generate all --templatePath=@app/path/to/my/custom/templates
*
* //generate fixtures into other folders, but be sure that this folders exists or you will get notice about that.
* yii fixture/generate all --fixturePath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3
* //generate fixtures into other folders
* yii fixture/generate all --fixtureDataPath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3
* ~~~
*
* You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker);
......@@ -148,24 +148,24 @@ class FixtureController extends \yii\console\controllers\FixtureController
*/
public $defaultAction = 'generate';
/**
* Alias to the template path, where all tables templates are stored.
* @var string
* @var string Alias to the template path, where all tables templates are stored.
*/
public $templatePath = '@tests/unit/templates/fixtures';
/**
* Language to use when generating fixtures data.
* @var string
* @var string Alias to the fixture data path, where data files should be written.
*/
public $fixtureDataPath = '@tests/unit/fixtures/data';
/**
* @var string Language to use when generating fixtures data.
*/
public $language;
/**
* Additional data providers that can be created by user and will be added to the Faker generator.
* @var array Additional data providers that can be created by user and will be added to the Faker generator.
* More info in [Faker](https://github.com/fzaninotto/Faker.) library docs.
* @var array
*/
public $providers = [];
/**
* Faker generator instance
* @var \Faker\Generator
* @var \Faker\Generator Faker generator instance
*/
private $_generator;
......@@ -177,7 +177,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function globalOptions()
{
return array_merge(parent::globalOptions(), [
'templatePath', 'language'
'templatePath', 'language', 'fixtureDataPath'
]);
}
......@@ -201,7 +201,7 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function actionGenerate(array $file, $times = 2)
{
$templatePath = Yii::getAlias($this->templatePath);
$fixturePath = Yii::getAlias($this->fixturePath);
$fixtureDataPath = Yii::getAlias($this->fixtureDataPath);
if ($this->needToGenerateAll($file[0])) {
$files = FileHelper::findFiles($templatePath, ['only' => ['*.php']]);
......@@ -233,9 +233,10 @@ class FixtureController extends \yii\console\controllers\FixtureController
}
$content = $this->exportFixtures($fixtures);
$filePath = realpath($fixturePath . '/' . $fixtureFileName);
file_put_contents($filePath, $content);
$this->stdout("Fixture file was generated under: $filePath\n", Console::FG_GREEN);
FileHelper::createDirectory($fixtureDataPath);
file_put_contents($fixtureDataPath . '/'. $fixtureFileName, $content);
$this->stdout("Fixture file was generated under: $fixtureDataPath\n", Console::FG_GREEN);
}
}
......@@ -357,9 +358,9 @@ class FixtureController extends \yii\console\controllers\FixtureController
public function confirmGeneration($files)
{
$this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW);
$this->stdout(realpath(Yii::getAlias($this->fixturePath)) . "\n\n", Console::FG_GREEN);
$this->stdout("\t" . Yii::getAlias($this->fixtureDataPath) . "\n\n", Console::FG_GREEN);
$this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW);
$this->stdout(realpath(Yii::getAlias($this->templatePath)) . "\n\n", Console::FG_GREEN);
$this->stdout("\t" . Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN);
foreach ($files as $index => $fileName) {
$this->stdout(" " . ($index + 1) . ". " . basename($fileName) . "\n", Console::FG_GREEN);
......
......@@ -95,13 +95,14 @@ php yii fixture/generate users
//also a short version of this command ("generate" action is default)
php yii fixture users
//to generate fixtures for several tables, use "," as a separator, for example:
php yii fixture users,profile,some_other_table
//to generate several fixtures data files, use "," as a separator, for example:
php yii fixture users,profile,some_other_name
```
In the code above "users" is template name, after this command run, new file named same as template
will be created under the fixtures path (by default ```@tests/unit/fixtures```) folder.
You can generate fixtures for all templates by specifying keyword ```all```.
You can generate fixtures for all templates by specifying keyword ```all```. You dont need to worry about if data file
directory already created or not, if not - it will be created by these command.
```php
php yii fixture/generate all
......@@ -124,8 +125,8 @@ php yii fixture/generate users 5 --language='ru_RU'
//read templates from the other path
php yii fixture/generate all --templatePath='@app/path/to/my/custom/templates'
//generate fixtures into other folders, but be sure that this folders exists or you will get notice about that.
php yii fixture/generate all --fixturePath='@tests/unit/fixtures/subfolder1/subfolder2/subfolder3'
//generate fixtures into other directory.
php yii fixture/generate all --fixtureDataPath='@tests/acceptance/fixtures/data'
```
You also can create your own data providers for custom tables fields, see [Faker]((https://github.com/fzaninotto/Faker)) library guide for more info;
......
......@@ -5,10 +5,13 @@ Yii Framework 2 gii extension Change Log
----------------------------
- Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue)
- Bug #1904: Fixed autocomplete to work with underscore inputs "_" (tonydspaniard)
- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
- Bug: fixed controller in crud template to avoid returning query in findModel() (cebe)
- Enh #1624: generate rules for unique indexes (lucianobaraglia)
- Enh #1818: Do not display checkbox column if all rows are empty (johonunu)
- Enh #1897: diff markup is now copy paste friendly (samdark)
- Enh #2327: better visual representation of changed files, added header and refresh button to diff modal (thiagotalma)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -35,9 +35,10 @@ yii.gii = (function ($) {
};
var initPreviewDiffLinks = function () {
$('.preview-code,.diff-code').on('click', function () {
$('.preview-code, .diff-code, .modal-refresh').on('click', function () {
var $modal = $('#preview-modal');
var $link = $(this);
$modal.find('.modal-refresh').attr('href', $link.prop('href'));
$modal.find('.modal-title').text($link.data('title'));
$modal.find('.modal-body').html('Loading ...');
$modal.modal('show');
......@@ -70,6 +71,15 @@ yii.gii = (function ($) {
};
return {
autocomplete: function (counter, data) {
var datum = new Bloodhound({
datumTokenizer: function(d){return Bloodhound.tokenizers.whitespace(d.word);},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: data
});
datum.initialize();
jQuery('.typeahead-'+counter).typeahead(null,{displayKey: 'word', source: datum.ttAdapter()});
},
init: function () {
initHintBlocks();
initStickyInputs();
......
......@@ -63,7 +63,10 @@ class ActiveField extends \yii\widgets\ActiveField
{
static $counter = 0;
$this->inputOptions['class'] .= ' typeahead-' . (++$counter);
$this->form->getView()->registerJs("jQuery('.typeahead-{$counter}').typeahead({local: " . Json::encode($data) . "});");
foreach ($data as &$item) {
$item = array('word' => $item);
}
$this->form->getView()->registerJs("yii.gii.autocomplete($counter, " . Json::encode($data) . ");");
return $this;
}
}
......@@ -80,8 +80,8 @@ class Generator extends \yii\gii\Generator
return array_merge(parent::rules(), [
[['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
[['controller', 'baseClass'], 'required'],
[['controller'], 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'],
[['actions'], 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'],
[['controller'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-\\/]*$/', 'message' => 'Only a-z, 0-9, dashes (-) and slashes (/) are allowed.'],
[['actions'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-,\\s]*$/', 'message' => 'Only a-z, 0-9, dashes (-), spaces and commas are allowed.'],
[['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
[['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
]);
......
......@@ -252,7 +252,7 @@ class Generator extends \yii\gii\Generator
try {
$db = $this->getDbConnection();
$uniqueIndexes = $db->getSchema()->findUniqueIndexes($table);
foreach ($uniqueIndexes as $indexName => $uniqueColumns) {
foreach ($uniqueIndexes as $uniqueColumns) {
// Avoid validating auto incrementable columns
if (!$this->isUniqueColumnAutoIncrementable($table, $uniqueColumns)) {
$attributesCount = count($uniqueColumns);
......
......@@ -4,11 +4,9 @@ use yii\helpers\Html;
/**
* @var \yii\web\View $this
* @var \yii\gii\Generator[] $generators
* @var \yii\gii\Generator $activeGenerator
* @var string $content
*/
$generators = Yii::$app->controller->module->generators;
$activeGenerator = Yii::$app->controller->generator;
$this->title = 'Welcome to Gii';
?>
<div class="default-index">
......
......@@ -33,7 +33,18 @@ use yii\gii\CodeFile;
</thead>
<tbody>
<?php foreach ($files as $file): ?>
<tr class="<?= $file->operation ?>">
<?php
if ($file->operation === CodeFile::OP_OVERWRITE) {
$trClass = 'warning';
} elseif ($file->operation === CodeFile::OP_SKIP) {
$trClass = 'active';
} elseif ($file->operation === CodeFile::OP_CREATE) {
$trClass = 'success';
} else {
$trClass = '';
}
?>
<tr class="<?= "$file->operation $trClass" ?>">
<td class="file">
<?= Html::a(Html::encode($file->getRelativePath()), ['preview', 'file' => $file->id], ['class' => 'preview-code', 'data-title' => $file->getRelativePath()]) ?>
<?php if ($file->operation === CodeFile::OP_OVERWRITE): ?>
......@@ -70,7 +81,7 @@ use yii\gii\CodeFile;
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Modal title</h4>
<h4><a class="modal-refresh glyphicon glyphicon-refresh" href="#"></a> <span class="modal-title">Modal title</span></h4>
</div>
<div class="modal-body">
<p>Please wait ...</p>
......
......@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture
/**
* Loads the fixture data.
* The default implementation will first reset the DB table and then populate it with the data
* returned by [[getData()]].
* Data will be batch inserted into the given collection.
*/
public function load()
{
$this->resetCollection();
parent::load();
$data = $this->getData();
$this->getCollection()->batchInsert($data);
foreach ($data as $alias => $row) {
......@@ -66,6 +66,17 @@ class ActiveFixture extends BaseActiveFixture
}
}
/**
* Unloads the fixture.
*
* The default implementation will clean up the colection by calling [[resetCollection()]].
*/
public function unload()
{
$this->resetCollection();
parent::unload();
}
protected function getCollection()
{
return $this->db->getCollection($this->getCollectionName());
......
......@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -8,9 +8,7 @@
namespace yii\mongodb;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\db\BaseActiveRecord;
use yii\base\UnknownMethodException;
use yii\db\StaleObjectException;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
......
......@@ -8,8 +8,6 @@
namespace yii\mongodb;
use Yii;
use yii\mongodb\Connection;
use yii\mongodb\Query;
use yii\base\InvalidConfigException;
/**
......
......@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......@@ -287,7 +288,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
if (count($this->where) == 1) {
$pks = (array) reset($this->where);
} else {
foreach($this->where as $column => $values) {
foreach($this->where as $values) {
if (is_array($values)) {
// TODO support composite IN for composite PK
throw new NotSupportedException('Find by composite PK is not supported by redis ActiveRecord.');
......
......@@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -8,7 +8,6 @@
namespace yii\redis;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\db\Exception;
use yii\helpers\Inflector;
......
......@@ -336,7 +336,7 @@ EOF;
}
$columnAlias = $this->addColumn($column, $columns);
$parts = [];
foreach ($values as $i => $value) {
foreach ($values as $value) {
if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null;
}
......
......@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord
}
/**
* 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.
* @inheritdoc
*/
public static function create($row)
public static function populateRecord($record, $row)
{
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$column = $columns[$name];
if ($column->isMva) {
$value = explode(',', $value);
}
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
if (isset($columns[$name]) && $columns[$name]->isMva) {
$row[$name] = explode(',', $value);
}
}
$record->setOldAttributes($record->getAttributes());
return $record;
parent::populateRecord($record, $row);
}
/**
......
......@@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -8,7 +8,6 @@
namespace yii\sphinx;
use yii\base\Object;
use yii\base\InvalidParamException;
/**
* IndexSchema represents the metadata of a Sphinx index.
......
......@@ -146,7 +146,7 @@ class Schema extends Object
return $this->_indexes[$name] = $index;
}
}
return $this->_indexes[$name] = $index = $this->loadIndexSchema($realName);
return $this->_indexes[$name] = $this->loadIndexSchema($realName);
}
/**
......
......@@ -26,6 +26,7 @@ Yii Framework 2 Change Log
- Bug #1710: OpenId auth client does not request required attributes correctly (klimov-paul)
- Bug #1798: Fixed label attributes for array fields (zhuravljov)
- Bug #1800: Better check for `$_SERVER['HTTPS']` in `yii\web\Request::getIsSecureConnection()` (ginus, samdark)
- Bug #1812: Hide potential warning message due to race condition occurring to `Session::regenerateID()` call (qiangxue)
- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue)
- Bug #1868: Added ability to exclude tables from FixtureController apply/clear actions. (Ragazzo)
- Bug #1869: Fixed tables clearing. `TRUNCATE` changed to `DELETE` to avoid postgresql tables checks (and truncating all tables) (Ragazzo)
......@@ -41,6 +42,9 @@ Yii Framework 2 Change Log
- Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue)
- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue)
- Bug #2303: Fixed the bug that `yii\base\Theme::pathMap` did not support dynamic update with path aliases (qiangxue)
- Bug #2324: Fixed QueryBuilder bug when building a query with "query" option (mintao)
- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
......@@ -49,6 +53,7 @@ Yii Framework 2 Change Log
- Bug: Fixed URL parsing so it's now properly giving 404 for URLs like `http://example.com//////site/about` (samdark)
- Bug: Fixed `HelpController::getModuleCommands` issue where it attempts to scan a module's controller directory when it doesn't exist (jom)
- Bug: Fixed an issue with Filehelper and not accessable directories which resulted in endless loop (cebe)
- Bug: Fixed `$model->load($data)` returned `true` if `$data` and `formName` were empty (samdark)
- Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard)
- Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard)
- Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue)
......@@ -99,6 +104,7 @@ Yii Framework 2 Change Log
- Enh #2132: Allow url of CSS and JS files registered in yii\web\View to be url alias (cebe)
- Enh #2144: `Html` helper now supports rendering "data" attributes (qiangxue)
- Enh #2156: `yii migrate` now automatically creates `migrations` directory if it does not exist (samdark)
- Enh: Added support for using arrays as option values for console commands (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight)
......@@ -115,7 +121,9 @@ Yii Framework 2 Change Log
- Enh: Added `yii\web\View::POS_LOAD` (qiangxue)
- Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue)
- Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue)
- Enh:#2211: Added typecast database types into php types (dizews)
- Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07)
- Enh #2325: Adding support for the `X-HTTP-Method-Override` header in `yii\web\Request::getMethod()` (pawzar)
- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue)
- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
......@@ -141,6 +149,7 @@ Yii Framework 2 Change Log
- Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue)
- Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue)
- Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
......@@ -154,6 +163,7 @@ Yii Framework 2 Change Log
- Chg: Renamed `yii\web\Request::acceptedLanguages` to `acceptableLanguages` (qiangxue)
- Chg: Removed implementation of `Arrayable` from `yii\Object` (qiangxue)
- Chg: Renamed `ActiveRecordInterface::createActiveRelation()` to `createRelation()` (qiangxue)
- Chg: The scripts in asset bundles are now registered in `View` at the end of `endBody()`. It was done in `endPage()` previously (qiangxue)
- New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul)
- New #706: Added `yii\widgets\Pjax` and enhanced `GridView` to work with `Pjax` to support AJAX-update (qiangxue)
- New #1393: [Codeception testing framework integration](https://github.com/yiisoft/yii2-codeception) (Ragazzo)
......
......@@ -61,17 +61,23 @@
applyFilter: function () {
var $grid = $(this);
var settings = $grid.data('yiiGridView').settings;
var data = $(settings.filterSelector).serialize();
var url = settings.filterUrl;
if (url.indexOf('?') >= 0) {
url += '&' + data;
} else {
url += '?' + data;
}
var data = {};
$.each($(settings.filterSelector).serializeArray(), function () {
data[this.name] = this.value;
});
$.each(yii.getQueryParams(settings.filterUrl), function (name, value) {
if (data[name] === undefined) {
data[name] = value;
}
});
var pos = settings.filterUrl.indexOf('?');
var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos);
$grid.find('form.gridview-filter-form').remove();
var $form = $('<form action="' + url + '" method="get" class="gridview-filter-form" style="display:none" data-pjax></form>').appendTo($grid);
$.each(yii.getQueryParams(url), function (name, value) {
$.each(data, function (name, value) {
$form.append($('<input type="hidden" name="t" value="" />').attr('name', name).val(value));
});
$form.submit();
......
......@@ -721,7 +721,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
public function load($data, $formName = null)
{
$scope = $formName === null ? $this->formName() : $formName;
if ($scope == '') {
if ($scope == '' && !empty($data)) {
$this->setAttributes($data);
return true;
} elseif (isset($data[$scope])) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment