Commit a884c80f by Alexander Makarov

Merge branch 'master'

parents 37222045 92bd71cd
......@@ -4,3 +4,8 @@ imports:
tools:
external_code_coverage:
timeout: 2100 # Timeout in seconds.
tools:
# disable copy paste detector and simliarity analyzer as they have no real value
# and a huge bunch of false-positives
php_sim: false
php_cpd: false
......@@ -111,7 +111,7 @@ To make your database up to date, you can run in needed test folder `yii migrate
if you are starting from `frontend` tests then you should run `yii migrate` in each suite folder `acceptance`, `functional`, `unit`
it will upgrade your database to the last state according migrations.
To be able to run acceptance tests you need a running webserver. For this you can use the php bultin server and run it in the directory where your main project folder is located. For example if your application is located in `/www/advanced` all you need to is:
To be able to run acceptance tests you need a running webserver. For this you can use the php builtin server and run it in the directory where your main project folder is located. For example if your application is located in `/www/advanced` all you need to is:
`cd /www` and then `php -S 127.0.0.1:8080` because the default configuration of acceptance tests expects the url of the application to be `/advanced/`.
If you already have a server configured or your application is not located in a folder called `advanced`, you may need to adjust the `TEST_ENTRY_URL` in `frontend/tests/_bootstrap.php` and `backend/tests/_bootstrap.php`.
......
......@@ -372,6 +372,20 @@ echo $this->render('_profile', [
]);
```
When you call `render()` to render a partial in a current view, you may use different formats to refer to the partial.
The most commonly used format is the so-called relative view name which is as shown in the above example.
The partial view file is relative to the directory containing the current view. If the partial is located under
a subdirectory, you should include the subdirectory name in the view name, e.g., `public/_profile`.
You may use path alias to specify a view, too. For example, `@app/views/common/_profile`.
And you may also use the so-called absolute view names, e.g., `/user/_profile`, `//user/_profile`.
An absolute view name starts with a single slashes or double slashes. If it starts with a single slash,
the view file will be looked for under the view path of the currently active module. Otherwise, it will
will be looked for under the application view path.
### Accessing context
Views are generally used either by controller or by widget. In both cases the object that called view rendering is
......
......@@ -268,13 +268,11 @@ class ApiRenderer extends BaseApiRenderer implements ViewContextInterface
}
/**
* Finds the view file corresponding to the specified relative view name.
* @param string $view a relative view name. The name does NOT start with a slash.
* @return string the view file path. Note that the file may not exist.
* @inheritdoc
*/
public function findViewFile($view)
public function getViewPath()
{
return Yii::getAlias('@yii/apidoc/templates/html/views/' . $view);
return Yii::getAlias('@yii/apidoc/templates/html/views');
}
/**
......
......@@ -7,7 +7,7 @@ $extensions = $panel->getExtensions();
<h1>Configuration</h1>
<?php
echo $this->render('panels/config/table', [
echo $this->render('table', [
'caption' => 'Application Configuration',
'values' => [
'Yii Version' => $panel->data['application']['yii'],
......@@ -18,13 +18,13 @@ echo $this->render('panels/config/table', [
]);
if (!empty($extensions)) {
echo $this->render('panels/config/table', [
echo $this->render('table', [
'caption' => 'Installed Extensions',
'values' => $extensions,
]);
}
echo $this->render('panels/config/table', [
echo $this->render('table', [
'caption' => 'PHP Configuration',
'values' => [
'PHP Version' => $panel->data['php']['version'],
......
......@@ -11,27 +11,27 @@ echo Tabs::widget([
'items' => [
[
'label' => 'Parameters',
'content' => $this->render('panels/request/table', ['caption' => 'Routing', 'values' => ['Route' => $panel->data['route'], 'Action' => $panel->data['action'], 'Parameters' => $panel->data['actionParams']]])
. $this->render('panels/request/table', ['caption' => '$_GET', 'values' => $panel->data['GET']])
. $this->render('panels/request/table', ['caption' => '$_POST', 'values' => $panel->data['POST']])
. $this->render('panels/request/table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']])
. $this->render('panels/request/table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']])
. $this->render('panels/request/table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]),
'content' => $this->render('table', ['caption' => 'Routing', 'values' => ['Route' => $panel->data['route'], 'Action' => $panel->data['action'], 'Parameters' => $panel->data['actionParams']]])
. $this->render('table', ['caption' => '$_GET', 'values' => $panel->data['GET']])
. $this->render('table', ['caption' => '$_POST', 'values' => $panel->data['POST']])
. $this->render('table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']])
. $this->render('table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']])
. $this->render('table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]),
'active' => true,
],
[
'label' => 'Headers',
'content' => $this->render('panels/request/table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']])
. $this->render('panels/request/table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']])
'content' => $this->render('table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']])
. $this->render('table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']])
],
[
'label' => 'Session',
'content' => $this->render('panels/request/table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']])
. $this->render('panels/request/table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']])
'content' => $this->render('table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']])
. $this->render('table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']])
],
[
'label' => '$_SERVER',
'content' => $this->render('panels/request/table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]),
'content' => $this->render('table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]),
],
],
]);
......@@ -395,8 +395,9 @@ class ActiveRecord extends BaseActiveRecord
}
$this->_version = $response['_version'];
$this->_score = null;
$this->setOldAttributes($values);
$this->afterSave(true);
$this->setOldAttributes($values);
return true;
}
......
......@@ -9,6 +9,7 @@ Yii Framework 2 elasticsearch extension Change Log
- 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)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (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)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
......
......@@ -242,10 +242,10 @@ abstract class ActiveRecord extends BaseActiveRecord
}
$newId = static::getCollection()->insert($values);
$this->setAttribute('_id', $newId);
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $value);
}
$values['_id'] = $newId;
$this->afterSave(true);
$this->setOldAttributes($values);
return true;
}
......@@ -262,7 +262,6 @@ abstract class ActiveRecord extends BaseActiveRecord
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false);
return 0;
}
$condition = $this->getOldPrimaryKey(true);
......@@ -281,10 +280,10 @@ abstract class ActiveRecord extends BaseActiveRecord
throw new StaleObjectException('The object being updated is outdated.');
}
$this->afterSave(false);
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name));
}
$this->afterSave(false);
return $rows;
}
......
......@@ -772,6 +772,7 @@ class Collection extends Object
protected function normalizeConditionKeyword($key)
{
static $map = [
'AND' => '$and',
'OR' => '$or',
'IN' => '$in',
'NOT IN' => '$nin',
......@@ -898,13 +899,13 @@ class Collection extends Object
*/
public function buildAndCondition($operator, $operands)
{
$result = [];
$operator = $this->normalizeConditionKeyword($operator);
$parts = [];
foreach ($operands as $operand) {
$condition = $this->buildCondition($operand);
$result = array_merge_recursive($result, $condition);
$parts[] = $this->buildCondition($operand);
}
return $result;
return [$operator => $parts];
}
/**
......
......@@ -147,10 +147,10 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
$newId = $collection->insert($values);
}
$this->setAttribute('_id', $newId);
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $value);
}
$values['_id'] = $newId;
$this->afterSave(true);
$this->setOldAttributes($values);
return true;
}
......@@ -167,7 +167,6 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false);
return 0;
}
......@@ -219,10 +218,10 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
}
}
$this->afterSave(false);
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name));
}
$this->afterSave(false);
return $rows;
}
......
......@@ -146,8 +146,8 @@ class ActiveRecord extends BaseActiveRecord
}
$db->executeCommand('HMSET', $args);
$this->setOldAttributes($values);
$this->afterSave(true);
$this->setOldAttributes($values);
return true;
}
......
......@@ -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)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
......
......@@ -416,10 +416,9 @@ abstract class ActiveRecord extends BaseActiveRecord
if (!$command->execute()) {
return false;
}
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $value);
}
$this->afterSave(true);
$this->setOldAttributes($values);
return true;
}
......@@ -512,7 +511,6 @@ abstract class ActiveRecord extends BaseActiveRecord
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false);
return 0;
}
......@@ -554,10 +552,10 @@ abstract class ActiveRecord extends BaseActiveRecord
}
}
$this->afterSave(false);
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name));
}
$this->afterSave(false);
return $rows;
}
......
......@@ -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)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
......
......@@ -156,6 +156,8 @@ Yii Framework 2 Change Log
- Enh #2735: Added support for `DateTimeInterface` in `Formatter` (ivokund)
- Enh #2756: Added support for injecting custom `isEmpty` check for all validators (qiangxue)
- Enh #2775: Added `yii\base\Application::bootstrap` and `yii\base\BootstrapInterface` to support running bootstrap classes when starting an application (qiangxue)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe)
- Enh #2910: Added `Application::end()` (qiangxue)
- 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)
......@@ -226,6 +228,7 @@ Yii Framework 2 Change Log
- Removed `yii\web\Controller::getCanonicalUrl`, use `yii\helpers\Url::canonical` instead.
- Chg #2691: Null parameters will not be included in the generated URLs by `UrlManager` (gonimar, qiangxue)
- Chg #2734: `FileCache::keyPrefix` defaults to empty string now (qiangxue)
_ Chg #2912: Relative view files will be looked for under the directory containing the view currently being rendered (qiangxue)
- 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)
......
......@@ -60,11 +60,39 @@ abstract class Application extends Module
const EVENT_AFTER_ACTION = 'afterAction';
/**
* Application state used by [[state]]: application just started.
*/
const STATE_BEGIN = 0;
/**
* Application state used by [[state]]: application is initializing.
*/
const STATE_INIT = 1;
/**
* Application state used by [[state]]: application is triggering [[EVENT_BEFORE_REQUEST]].
*/
const STATE_BEFORE_REQUEST = 2;
/**
* Application state used by [[state]]: application is handling the request.
*/
const STATE_HANDLING_REQUEST = 3;
/**
* Application state used by [[state]]: application is triggering [[EVENT_AFTER_REQUEST]]..
*/
const STATE_AFTER_REQUEST = 4;
/**
* Application state used by [[state]]: application is about to send response.
*/
const STATE_SENDING_RESPONSE = 5;
/**
* Application state used by [[state]]: application has ended.
*/
const STATE_END = 6;
/**
* @var string the namespace that controller classes are in. If not set,
* it will use the "app\controllers" namespace.
*/
public $controllerNamespace = 'app\\controllers';
/**
* @var string the application name.
*/
......@@ -144,6 +172,11 @@ abstract class Application extends Module
* it means the application is handling some exception and extra care should be taken.
*/
public $exception;
/**
* @var integer the current application state during a request handling life cycle.
* This property is managed by the application. Do not modify this property.
*/
public $state;
/**
* @var string Used to reserve memory for fatal error handler.
......@@ -160,6 +193,8 @@ abstract class Application extends Module
{
Yii::$app = $this;
$this->state = self::STATE_BEGIN;
$this->preInit($config);
$this->registerErrorHandlers();
......@@ -223,6 +258,8 @@ abstract class Application extends Module
*/
public function init()
{
$this->state = self::STATE_INIT;
$this->initExtensions($this->extensions);
foreach ($this->bootstrap as $class) {
/** @var BootstrapInterface $bootstrap */
......@@ -309,12 +346,30 @@ abstract class Application extends Module
*/
public function run()
{
$this->trigger(self::EVENT_BEFORE_REQUEST);
$response = $this->handleRequest($this->getRequest());
$this->trigger(self::EVENT_AFTER_REQUEST);
$response->send();
try {
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
return $response->exitStatus;
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
/**
......@@ -460,6 +515,15 @@ abstract class Application extends Module
}
/**
* Returns the response component.
* @return \yii\web\Response|\yii\console\Response the response component
*/
public function getResponse()
{
return $this->get('response');
}
/**
* Returns the view object.
* @return View|\yii\web\View the view object that is used to render various view files.
*/
......@@ -506,6 +570,15 @@ abstract class Application extends Module
}
/**
* Returns the asset manager.
* @return \yii\web\AssetManager the asset manager component
*/
public function getAssetManager()
{
return $this->get('assetManager');
}
/**
* Returns the core application components.
* @see set
*/
......@@ -514,15 +587,44 @@ abstract class Application extends Module
return [
'log' => ['class' => 'yii\log\Dispatcher'],
'errorHandler' => ['class' => 'yii\base\ErrorHandler'],
'view' => ['class' => 'yii\web\View'],
'formatter' => ['class' => 'yii\base\Formatter'],
'i18n' => ['class' => 'yii\i18n\I18N'],
'mail' => ['class' => 'yii\swiftmailer\Mailer'],
'urlManager' => ['class' => 'yii\web\UrlManager'],
'view' => ['class' => 'yii\web\View'],
'assetManager' => ['class' => 'yii\web\AssetManager'],
];
}
/**
* Terminates the application.
* This method replaces the `exit()` function by ensuring the application life cycle is completed
* before terminating the application.
* @param integer $status the exit status (value 0 means normal exit while other values mean abnormal exit).
* @param Response $response the response to be sent. If not set, the default application [[response]] component will be used.
* @throws ExitException if the application is in testing mode
*/
public function end($status = 0, $response = null)
{
if ($this->state === self::STATE_BEFORE_REQUEST || $this->state === self::STATE_HANDLING_REQUEST) {
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
}
if ($this->state !== self::STATE_SENDING_RESPONSE && $this->state !== self::STATE_END) {
$this->state = self::STATE_END;
$response = $response ? : $this->getResponse();
$response->send();
}
if (YII_ENV_TEST) {
throw new ExitException($status);
} else {
exit($status);
}
}
/**
* Handles uncaught PHP exceptions.
*
* This method is implemented as a PHP exception handler.
......@@ -531,6 +633,10 @@ abstract class Application extends Module
*/
public function handleException($exception)
{
if ($exception instanceof ExitException) {
return;
}
$this->exception = $exception;
// disable error capturing to avoid recursive errors while handling exceptions
......
......@@ -289,7 +289,7 @@ class Controller extends Component implements ViewContextInterface
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* These parameters will not be available in the layout.
* @return string the rendering result.
......@@ -368,17 +368,6 @@ class Controller extends Component implements ViewContextInterface
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @return string the view file path. Note that the file may not exist.
*/
public function findViewFile($view)
{
return $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
/**
* Finds the applicable layout file.
* @param View $view the view object to render the layout file.
* @return string|boolean the layout file path, or false if layout is not needed.
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* ExitException represents a normal termination of an application.
*
* Do not catch ExitException. Yii will handle this exception to terminate the application gracefully.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ExitException extends \Exception
{
/**
* @var integer the exit status code
*/
public $statusCode;
/**
* Constructor.
* @param integer $status the exit status code
* @param string $message error message
* @param integer $code error code
* @param \Exception $previous The previous exception used for the exception chaining.
*/
public function __construct($status = 0, $message = null, $code = 0, \Exception $previous = null)
{
$this->statusCode = $status;
parent::__construct($message, $code, $previous);
}
}
......@@ -98,7 +98,7 @@ class View extends Component
/**
* @var array the view files currently being rendered. There may be multiple view files being
* rendered at a moment because one may render a view file within another.
* rendered at a moment because one view may be rendered within another.
*/
private $_viewFiles = [];
......@@ -127,13 +127,17 @@ class View extends Component
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within current module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
* - resolving any other format will be performed via [[ViewContext::findViewFile()]].
* - relative view (e.g. "index"): the view name does not start with `@` or `/`. The corresponding view file will be
* looked for under the [[ViewContextInterface::getViewPath()|view path]] of the view `$context`.
* If `$context` is not given, it will be looked for under the directory containing the view currently
* being rendered (i.e., this happens when rendering a view within another view).
*
* @param string $view the view name. Please refer to [[Controller::findViewFile()]]
* and [[Widget::findViewFile()]] on how to specify this parameter.
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
* @param object $context the context that the view should use for rendering the view. If null,
* existing [[context]] will be used.
* @param object $context the context to be assigned to the view and can later be accessed via [[context]]
* in the view. If the context implements [[ViewContextInterface]], it may also be used to locate
* the view file corresponding to a relative view name.
* @return string the rendering result
* @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
* @see renderFile()
......@@ -148,10 +152,12 @@ class View extends Component
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @param object $context the context that the view should be used to search the view file. If null,
* existing [[context]] will be used.
* @param object $context the context to be assigned to the view and can later be accessed via [[context]]
* in the view. If the context implements [[ViewContextInterface]], it may also be used to locate
* the view file corresponding to a relative view name.
* @return string the view file path. Note that the file may not exist.
* @throws InvalidCallException if [[context]] is required and invalid.
* @throws InvalidCallException if a relative view name is given while there is no active context to
* determine the corresponding view file.
*/
protected function findViewFile($view, $context = null)
{
......@@ -168,16 +174,12 @@ class View extends Component
} else {
throw new InvalidCallException("Unable to locate view file for view '$view': no active controller.");
}
} elseif ($context instanceof ViewContextInterface) {
$file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
} elseif (($currentViewFile = $this->getViewFile()) !== false) {
$file = dirname($currentViewFile) . DIRECTORY_SEPARATOR . $view;
} else {
// context required
if ($context === null) {
$context = $this->context;
}
if ($context instanceof ViewContextInterface) {
$file = $context->findViewFile($view);
} else {
throw new InvalidCallException("Unable to locate view file for view '$view': no active view context.");
}
throw new InvalidCallException("Unable to resolve view file for view '$view': no active view context.");
}
if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
......@@ -213,6 +215,7 @@ class View extends Component
public function renderFile($viewFile, $params = [], $context = null)
{
$viewFile = Yii::getAlias($viewFile);
if ($this->theme !== null) {
$viewFile = $this->theme->applyTo($viewFile);
}
......
......@@ -10,7 +10,7 @@ namespace yii\base;
/**
* ViewContextInterface is the interface that should implemented by classes who want to support relative view names.
*
* The method [[findViewFile()]] should be implemented to convert a relative view name into a file path.
* The method [[getViewPath()]] should be implemented to return the view path that may be prefixed to a relative view name.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
......@@ -18,9 +18,7 @@ namespace yii\base;
interface ViewContextInterface
{
/**
* Finds the view file corresponding to the specified relative view name.
* @param string $view a relative view name. The name does NOT start with a slash.
* @return string the view file path. Note that the file may not exist.
* @return string the view path that may be prefixed to a relative view name.
*/
public function findViewFile($view);
public function getViewPath();
}
......@@ -173,7 +173,7 @@ class Widget extends Component implements ViewContextInterface
*
* If the view name does not contain a file extension, it will use the default one `.php`.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidParamException if the view file does not exist.
......@@ -206,15 +206,4 @@ class Widget extends Component implements ViewContextInterface
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
/**
* Finds the view file based on the given view name.
* File will be searched under [[viewPath]] directory.
* @param string $view the view name.
* @return string the view file path. Note that the file may not exist.
*/
public function findViewFile($view)
{
return $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
}
......@@ -149,15 +149,6 @@ class Application extends \yii\base\Application
}
/**
* Returns the response component.
* @return Response the response component
*/
public function getResponse()
{
return $this->get('response');
}
/**
* Runs a controller action specified by a route.
* This method parses the specified route and creates the corresponding child module(s), controller and action
* instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
......
......@@ -62,7 +62,7 @@ namespace yii\db;
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
* These methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation and [[onCondition()]] which adds a condition that
* is to be added to relational querys join condition.
* is to be added to relational query join condition.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
......@@ -106,6 +106,40 @@ class ActiveQuery extends Query implements ActiveQueryInterface
/**
* @inheritdoc
*/
public function prepareBuild($builder)
{
if (!empty($this->joinWith)) {
$this->buildJoinWith();
$this->joinWith = null; // clean it up to avoid issue https://github.com/yiisoft/yii2/issues/2687
}
if (empty($this->from)) {
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
$tableName = $modelClass::tableName();
$this->from = [$tableName];
}
if (empty($this->select) && !empty($this->join)) {
foreach ((array)$this->from as $alias => $table) {
if (is_string($alias)) {
$this->select = ["$alias.*"];
} elseif (is_string($table)) {
if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $table, $matches)) {
$alias = $matches[2];
} else {
$alias = $table;
}
$this->select = ["$alias.*"];
}
break;
}
}
}
/**
* @inheritdoc
*/
public function prepareResult($rows)
{
if (empty($rows)) {
......@@ -246,10 +280,6 @@ class ActiveQuery extends Query implements ActiveQueryInterface
}
if ($this->sql === null) {
if (!empty($this->joinWith)) {
$this->buildJoinWith();
$this->joinWith = null; // clean it up to avoid issue https://github.com/yiisoft/yii2/issues/2687
}
list ($sql, $params) = $db->getQueryBuilder()->build($this);
} else {
$sql = $this->sql;
......
......@@ -445,15 +445,14 @@ class ActiveRecord extends BaseActiveRecord
if ($this->getAttribute($name) === null) {
$id = $db->getLastInsertID($table->sequenceName);
$this->setAttribute($name, $id);
$this->setOldAttribute($name, $id);
$values[$name] = $id;
break;
}
}
}
foreach ($values as $name => $value) {
$this->setOldAttribute($name, $value);
}
$this->afterSave(true);
$this->setOldAttributes($values);
return true;
}
......
......@@ -24,7 +24,8 @@ use yii\base\InvalidCallException;
* @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 array $oldAttributes The old attribute values (name-value pairs). Note that the type of this
* property differs in getter and setter. See [[getOldAttributes()]] and [[setOldAttributes()]] for details.
* @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.
......@@ -477,6 +478,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* Sets the old attribute values.
* All existing old attribute values will be discarded.
* @param array|null $values old attribute values to be set.
* If set to `null` this record is considered to be [[isNewRecord|new]].
*/
public function setOldAttributes($values)
{
......@@ -564,7 +566,6 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
}
}
return $attributes;
}
......@@ -654,7 +655,6 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
if ($runValidation && !$this->validate($attributes)) {
return false;
}
return $this->updateInternal($attributes);
}
......@@ -684,8 +684,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$attrs[] = $name;
}
}
return $this->update(false, $attrs);
return $this->updateInternal($attrs);
}
/**
......@@ -700,7 +699,6 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false);
return 0;
}
$condition = $this->getOldPrimaryKey(true);
......@@ -719,10 +717,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
throw new StaleObjectException('The object being updated is outdated.');
}
$this->afterSave(false);
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
$this->afterSave(false);
return $rows;
}
......@@ -751,7 +749,6 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$this->_attributes[$name] += $value;
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
return true;
} else {
return false;
......
......@@ -124,6 +124,16 @@ class Query extends Component implements QueryInterface
}
/**
* Prepares for building SQL.
* This method is called by [[QueryBuilder]] when it starts to build SQL from a query object.
* You may override this method to do some final preparation work when converting a query into a SQL statement.
* @param QueryBuilder $builder
*/
public function prepareBuild($builder)
{
}
/**
* Starts a batch query.
*
* A batch query supports fetching data in batches, which can keep the memory usage under a limit.
......
......@@ -64,12 +64,13 @@ class QueryBuilder extends \yii\base\Object
*/
public function build($query, $params = [])
{
$query->prepareBuild($this);
$params = empty($params) ? $query->params : array_merge($params, $query->params);
list ($select, $from) = $this->adjustSelectFrom($query);
$clauses = [
$this->buildSelect($select, $params, $query->distinct, $query->selectOption),
$this->buildFrom($from, $params),
$this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
$this->buildFrom($query->from, $params),
$this->buildJoin($query->join, $params),
$this->buildWhere($query->where, $params),
$this->buildGroupBy($query->groupBy),
......@@ -89,44 +90,6 @@ class QueryBuilder extends \yii\base\Object
}
/**
* Adjusts the select and from parts of the query when it is an ActiveQuery.
* When ActiveQuery does not specify "from", or if it is a join query without explicit "select",
* certain adjustments need to be made. This method is put here so that QueryBuilder can
* support sub-queries.
* @param Query $query
* @return array the select and from parts.
*/
protected function adjustSelectFrom($query)
{
$select = $query->select;
$from = $query->from;
if ($query instanceof ActiveQuery && (empty($select) || empty($from))) {
/** @var ActiveRecord $modelClass */
$modelClass = $query->modelClass;
$tableName = $modelClass::tableName();
if (empty($from)) {
$from = [$tableName];
}
if (empty($select) && !empty($query->join)) {
foreach ((array)$from as $alias => $table) {
if (is_string($alias)) {
$select = ["$alias.*"];
} elseif (is_string($table)) {
if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $table, $matches)) {
$alias = $matches[2];
} else {
$alias = $tableName;
}
$select = ["$alias.*"];
}
break;
}
}
}
return [$select, $from];
}
/**
* Creates an INSERT SQL statement.
* For example,
*
......
......@@ -19,7 +19,6 @@ use yii\db\ActiveRecord;
*/
class QueryBuilder extends \yii\db\QueryBuilder
{
private $sql;
/**
......@@ -27,12 +26,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
*/
public function build($query, $params = [])
{
$query->prepareBuild($this);
$params = empty($params) ? $query->params : array_merge($params, $query->params);
list ($select, $from) = $this->adjustSelectFrom($query);
$clauses = [
$this->buildSelect($select, $params, $query->distinct, $query->selectOption),
$this->buildFrom($from, $params),
$this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
$this->buildFrom($query->from, $params),
$this->buildJoin($query->join, $params),
$this->buildWhere($query->where, $params),
$this->buildGroupBy($query->groupBy),
......
......@@ -50,6 +50,12 @@ use yii\base\Component;
* Yii::$app->log->targets['file']->enabled = false;
* ~~~
*
* @property integer $flushInterval How many messages should be logged before they are sent to targets. This
* method returns the value of [[Logger::flushInterval]].
* @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used.
* @property integer $traceLevel How many application call stacks should be logged together with each message.
* This method returns the value of [[Logger::traceLevel]]. Defaults to 0.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
......@@ -61,9 +67,25 @@ class Dispatcher extends Component
*/
public $targets = [];
/**
* @var Logger the logger. If not set, [[\Yii::getLogger()]] will be used.
* @var Logger the logger.
*/
private $_logger;
/**
* @inheritdoc
*/
public $logger;
public function __construct($config = [])
{
if (isset($config['logger'])) {
$this->setLogger($config['logger']);
unset($config['logger']);
}
// connect logger and dispatcher
$this->getLogger();
parent::__construct($config);
}
/**
* Initializes the logger by registering [[flush()]] as a shutdown function.
......@@ -77,11 +99,30 @@ class Dispatcher extends Component
$this->targets[$name] = Yii::createObject($target);
}
}
}
if ($this->logger === null) {
$this->logger = Yii::getLogger();
/**
* Gets the connected logger.
* If not set, [[\Yii::getLogger()]] will be used.
* @property Logger the logger. If not set, [[\Yii::getLogger()]] will be used.
* @return Logger the logger.
*/
public function getLogger()
{
if ($this->_logger === null) {
$this->setLogger(Yii::getLogger());
}
$this->logger->dispatcher = $this;
return $this->_logger;
}
/**
* Sets the connected logger.
* @param Logger $value the logger.
*/
public function setLogger($value)
{
$this->_logger = $value;
$this->_logger->dispatcher = $this;
}
/**
......@@ -90,7 +131,7 @@ class Dispatcher extends Component
*/
public function getTraceLevel()
{
return $this->logger->traceLevel;
return $this->getLogger()->traceLevel;
}
/**
......@@ -101,7 +142,7 @@ class Dispatcher extends Component
*/
public function setTraceLevel($value)
{
$this->logger->traceLevel = $value;
$this->getLogger()->traceLevel = $value;
}
/**
......@@ -110,7 +151,7 @@ class Dispatcher extends Component
*/
public function getFlushInterval()
{
return $this->logger->flushInterval;
return $this->getLogger()->flushInterval;
}
/**
......@@ -123,7 +164,7 @@ class Dispatcher extends Component
*/
public function setFlushInterval($value)
{
$this->logger->flushInterval = $value;
$this->getLogger()->flushInterval = $value;
}
/**
......
......@@ -39,11 +39,6 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
*/
const EVENT_AFTER_SEND = 'afterSend';
/**
* @var string directory containing view files for this email messages.
* This can be specified as an absolute path or path alias.
*/
public $viewPath = '@app/mail';
/**
* @var string|boolean HTML layout view name. This is the layout used to render HTML mail body.
* The property can take the following values:
*
......@@ -104,6 +99,10 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
* @var \yii\base\View|array view instance or its array configuration.
*/
private $_view = [];
/**
* @var string the directory containing view files for composing mail messages.
*/
private $_viewPath;
/**
* @param array|View $view view instance or its array configuration that will be used to
......@@ -159,7 +158,7 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/mail/contact");
* - a relative view name (e.g. "contact"): the actual view file will be resolved by [[findViewFile()]]
* - a relative view name (e.g. "contact") located under [[viewPath]].
*
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return MessageInterface message instance.
......@@ -319,14 +318,24 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
}
/**
* Finds the view file corresponding to the specified relative view name.
* This method will return the view file by prefixing the view name with [[viewPath]].
* @param string $view a relative view name. The name does NOT start with a slash.
* @return string the view file path. Note that the file may not exist.
* @return string the directory that contains the view files for composing mail messages
* Defaults to '@app/mail'.
*/
public function getViewPath()
{
if ($this->_viewPath === null) {
$this->setViewPath('@app/mail');
}
return $this->_viewPath;
}
/**
* @param string $path the directory that contains the view files for composing mail messages
* This can be specified as an absolute path or a path alias.
*/
public function findViewFile($view)
public function setViewPath($path)
{
return Yii::getAlias($this->viewPath) . DIRECTORY_SEPARATOR . $view;
$this->_viewPath = Yii::getAlias($path);
}
/**
......
......@@ -124,24 +124,6 @@ class Application extends \yii\base\Application
}
/**
* Returns the request component.
* @return Request the request component
*/
public function getRequest()
{
return $this->get('request');
}
/**
* Returns the response component.
* @return Response the response component
*/
public function getResponse()
{
return $this->get('response');
}
/**
* Returns the session component.
* @return Session the session component
*/
......@@ -160,15 +142,6 @@ class Application extends \yii\base\Application
}
/**
* Returns the asset manager.
* @return AssetManager the asset manager component
*/
public function getAssetManager()
{
return $this->get('assetManager');
}
/**
* @inheritdoc
*/
public function coreComponents()
......@@ -178,7 +151,6 @@ class Application extends \yii\base\Application
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'assetManager' => ['class' => 'yii\web\AssetManager'],
], parent::coreComponents());
}
}
......@@ -13,6 +13,16 @@ use yii\helpers\Json;
/**
* Parses a raw HTTP request using [[\yii\helpers\Json::decode()]]
*
* To enable parsing for JSON requests you can configure [[Request::parsers]] using this class:
*
* ```php
* 'request' => [
* 'parsers' => [
* 'application/json' => 'yii\web\JsonParser',
* ]
* ]
* ```
*
* @author Dan Schmidt <danschmidt5189@gmail.com>
* @since 2.0
*/
......
......@@ -46,8 +46,8 @@ use yii\base\InvalidConfigException;
*
* @property string|integer $id The unique identifier for the user. If null, it means the user is a guest.
* This property is read-only.
* @property IdentityInterface $identity The identity object associated with the currently logged-in user.
* Null is returned if the user is not logged in (not authenticated).
* @property IdentityInterface|null $identity The identity object associated with the currently logged-in
* user. `null` is returned if the user is not logged in (not authenticated).
* @property boolean $isGuest Whether the current user is a guest. This property is read-only.
* @property string $returnUrl The URL that the user should be redirected to after login. Note that the type
* of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details.
......
......@@ -101,6 +101,56 @@ class QueryRunTest extends MongoDbTestCase
$this->assertEquals('address5', $rows[1]['address']);
}
public function testCombinedInAndCondition()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->where([
'name' => ['name1', 'name5']
])
->andWhere(['name' => 'name1'])
->all($connection);
$this->assertEquals(1, count($rows));
$this->assertEquals('name1', $rows[0]['name']);
}
public function testCombinedInLikeAndCondition()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->where([
'name' => ['name1', 'name5', 'name10']
])
->andWhere(['LIKE', 'name', '/me1/'])
->andWhere(['name' => 'name10'])
->all($connection);
$this->assertEquals(1, count($rows));
$this->assertEquals('name10', $rows[0]['name']);
}
public function testNestedCombinedInAndCondition()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('customer')
->where([
'and',
['name' => ['name1', 'name2', 'name3']],
['name' => 'name1']
])
->orWhere([
'and',
['name' => ['name4', 'name5', 'name6']],
['name' => 'name6']
])
->all($connection);
$this->assertEquals(2, count($rows));
$this->assertEquals('name1', $rows[0]['name']);
$this->assertEquals('name6', $rows[1]['name']);
}
public function testOrder()
{
$connection = $this->getConnection();
......
......@@ -678,7 +678,7 @@ trait ActiveRecordTestTrait
$this->afterSave();
$this->assertNotNull($customer->id);
$this->assertFalse(static::$afterSaveNewRecord);
$this->assertTrue(static::$afterSaveNewRecord);
$this->assertTrue(static::$afterSaveInsert);
$this->assertFalse($customer->isNewRecord);
}
......
......@@ -8,6 +8,9 @@ namespace yiiunit\framework\log;
use yii\log\Logger;
use yiiunit\TestCase;
/**
* @group log
*/
class LoggerTest extends TestCase
{
......
......@@ -10,6 +10,9 @@ use yii\log\Logger;
use yii\log\Target;
use yiiunit\TestCase;
/**
* @group log
*/
class TargetTest extends TestCase
{
public static $messages;
......
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